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.
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
- def oracle(Object o) { return 'object' }
- def oracle(String o) { return 'string' }
- Object x = 1
- Object y = 'foo'
- assert 'object' == oracle(x)
- assert 'string' == oracle(y) // This would return object in Java
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
- class Equalizer {
- boolean equals(Equalizer e){
- return true
- }
- }
- Object same = new Equalizer()
- Object other = new Object()
- assert new Equalizer().equals( same )
- assert ! new Equalizer().equals( other )
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:
- public class Equalizer { // Java
- public boolean equals(Object obj)
- {
- if (obj == null) return false;
- if (!(obj instanceof Equalizer)) return false;
- Equalizer w = (Equalizer) obj;
- return true; // custom logic here
- }
- }
Note.
Groovy, in contrast, comes with a single and consistent implementation of dispatching methods by the dynamic types of their arguments.
沒有留言:
張貼留言