程式扎記: [ In Action ] Dynamic object orientation - Advanced OO features

標籤

2014年3月9日 星期日

[ In Action ] Dynamic object orientation - Advanced OO features

Preface: 
Before beginning to embrace further parts of the Groovy libraries that make fundamental use of the OO features we have been discussing, we first stop to briefly explore other OO concepts that change once you enter the Groovy world. We will cover inheritance and interfaces, which will be familiar from Java, and multimethods, which will give you a taste of the dynamic object orientation coming later. 

Using inheritance: 
You have seen how to explicitly add your own fields, methods, and constructors into your class definitions. Inheritance allows you to implicitly add fields and methods from a base class. The mechanism is useful in a range of use cases. We leave it up to others to describe its benefits and warn you about the potential overuse of this feature. We simply let you know that all the inheritance features of Java are available in Groovy and also work (almost seamlessly) between Groovy and Java. 

Groovy classes can extend Groovy and Java classes and interfaces alike. Java classes can also extend Groovy classes and interfaces. You need to compile your Java and Groovy classes in a particular order for this to work. See section 11.4.2 for more details. The only other thing you need to be aware of is that Groovy is more dynamic than Java when it selects which methods to invoke for you. This feature is known as multimethods and is discussed further in section 7.3.3

Using interfaces: 
A frequently advocated style of Java programming involves using Java’s interfacemechanism. Code written using this style refers to the dependent classes that it uses solely by interface. The dependent classes can be safely changed later without requiring changes to the original program. If a developer accidentally tries to change one of the classes for another that doesn’t comply with the interface, this discrepancy is detected at compile time. Groovy fully supports the Java interface mechanism. 

Some argue that interfaces alone are not strong enough, and design-by-contract is more important for achieving safe object substitution and allowing nonbreaking changes to your libraries. Judicious use of abstract methods and inheritance becomes just as important as using interfaces. Groovy’s support for Java’s abstract methods, its automatically enabled assert statement, and its built-in ready access to test methods mean that it is ideally suited to also support this stricter approach. 

Still others argue that dynamic typing is the best approach, leading to much less typing and less scaffolding code without much reduced safety—which should be covered by tests in any case. The good news is that Groovy supports this style as well. To give you a flavor of how this would impact you in everyday coding, consider how you would build a plug-in mechanism in Java and Groovy. 

In Java, you would normally write an interface for the plug-in mechanism and then an implementation class for each plug-in that implements that interface. In Groovy, dynamic typing allows you to more easily create and use implementations that meet a certain need. You are likely to be able to create just two classes as part of developing two plug-in implementations. In general, you have a lot less scaffolding code and a lot less typing. 
FOR THE GEEKS. 
If you decide to make heavy use of interfaces, Groovy provides ways to make them more dynamic. If you have an interface MyInterface with a single method and a closure myClosure , you can use the as keyword to coerce the closure to be of type MyInterface. Similarly, if you have an interface with several methods, you can create a map of closures keyed on the method names and coerce the map to your interface type. See the Groovy wiki for more details.

In summary, if you’ve come from the Java world, you may be used to following a strict style of coding that strongly encourages interfaces. When using Groovy, you are not compelled to stick with any one style. In many situations, you can minimize the amount of typing by making use of dynamic typing; and if you really need it, the full use of interfaces is available

Multimethods: 
Remember that Groovy’s mechanics of method lookup take the dynamic type of method arguments into account, whereas Java relies on the static type. This Groovy feature is called multimethods. Listing 7.16 shows two methods, both called oracle , that are distinguishable only by their argument types. They are called two times with arguments of the same static type but different dynamic types. 
- Listing 7.16 Multimethods: method lookup relies on dynamic types 
  1. def oracle(Object o) { return 'object' }  
  2. def oracle(String o) { return 'string' }  
  3. Object x = 1  
  4. Object y = 'foo'  
  5. assert 'object' == oracle(x)  
  6. assert 'string' == oracle(y) // This would return object in Java  
The x argument is of static type Object and of dynamic type Integer . The y argument is of static type Object but of dynamic type String. Both arguments are of the same static type, which would make the equivalent Java program dispatch both to oracle(Object) . Because Groovy dispatches by the dynamic type, the specialized implementation of oracle(String) is used in the second case

With this capability in place, you can better avoid duplicated code by being able to override behavior more selectively. Consider the equals implementation in listing 7.17 that overrides Object’s default equals method only for the argument type Equalizer. (Groovy 會判斷 argument's dynamic type 然後 dispatch 到合適的 method!
- Listing 7.17 Multimethods to selectively override equals 
  1. class Equalizer {  
  2.     boolean equals(Equalizer e){  
  3.         return true  
  4.     }  
  5. }  
  6. Object same  = new Equalizer()  
  7. Object other = new Object()  
  8. assert   new Equalizer().equals( same  )  
  9. assert ! new Equalizer().equals( other )  
When an object of type Equalizer is passed to the equals method, the specialized implementation is chosen. When an arbitrary object is passed, the default implementation of its superclass Object.equals is called, which implements the equality check as a reference identity check. 

The net effect is that the caller of the equals method can be fully unaware of the difference. From a caller’s perspective, it looks like equals(Equalizer) would overrideequals(Object) , which would be impossible to do in Java. Instead, a Java programmer has to write it like this: 
  1. public class Equalizer {             // Java   
  2.     public boolean equals(Object obj)  
  3.     {  
  4.         if (obj == null)                 return false;  
  5.         if (!(obj instanceof Equalizer)) return false;  
  6.         Equalizer w = (Equalizer) obj;  
  7.         return true;                 // custom logic here  
  8.     }  
  9. }  
This is unfortunate, because the logic of how to correctly override equals needs to be duplicated for every custom type in Java. This is another example where Java uses the static type Object and leaves the work of dynamic type resolution to the programmer
Note. 
Wherever there’s a Java API that uses the static type Object , this code effectively loses the strength of static typing. You will inevitably find it used with typecasts, compromising compile-time type safety. This is why the Java type concept is called weak static typing: You lose the merits of static typing without getting the benefits of a dynamically typed language such as multimethods.

Groovy, in contrast, comes with a single and consistent implementation of dispatching methods by the dynamic types of their arguments.

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!