Effective Java Third edition--18. Combination better than inheritance

Source: Internet
Author: User
Tags addall instance method wrapper

Tips
"Effective Java, third Edition" an English version has been published, the second edition of this book presumably many people have read, known as one of the four major Java books, but the second edition of 2009 published, to now nearly 8 years, but with Java 6, 7, 8, and even 9 of the release, the Java language has undergone profound changes.
In the first time here translated into Chinese version. For everyone to learn to share.

18. Combination is better than inheritance

Inheritance is an effective way to implement code reuse, but it is not always the best tool. Improper use can lead to fragile software. It is safe to use inheritance in a package, where the implementation of the subclass and the parent class is under the control of the same programmer. It is also safe to use inheritance for classes that are specifically designed for inheritance and that have document descriptions (entry 19). However, it is dangerous to inherit from the ordinary concrete class across the package-level boundary. As a reminder, this book uses the term "inheritance" to denote implementation inheritance (when one class inherits another). The issues discussed in this project do not apply to interface inheritance (when a class implements an interface or when an interface inherits another interface).

Unlike method invocations, inheritance breaks the encapsulation [Snyder86]. In other words, a subclass relies on the implementation details of its parent class to ensure its proper functionality. The implementation of the parent class may change from the release version, and if so, the subclass may be corrupted even if its code does not change anything. Therefore, a subclass must be updated with its superclass, unless the author of the parent class specifically designs it for the purposes of the inheritance, and the description of the due document.

For specific instructions, assume that there is a HashSet program in use. In order to adjust the performance of the program, you need to query HashSe how many elements have been added since it was created (do not confuse the number of elements with the current one, and the number will drop when the element is deleted). To provide this functionality, a HashSet variant is written that preserves the number of attempts to insert an element and exports an access method for the number of insertions. The HashSet class contains two methods for adding elements, respectively, add and addAll so we override both methods:

// Broken - Inappropriate use of inheritance!public class InstrumentedHashSet<E> extends HashSet<E> {    // The number of attempted element insertions    private int addCount = 0;    public InstrumentedHashSet() {    }    public InstrumentedHashSet(int initCap, float loadFactor) {        super(initCap, loadFactor);    }    @Override public boolean add(E e) {        addCount++;        return super.add(e);    }    @Override public boolean addAll(Collection<? extends E> c) {        addCount += c.size();        return super.addAll(c);    }    public int getAddCount() {        return addCount;    }}

This class looks reasonable, but it doesn't work properly. Suppose you create an instance and use a addAll method to add three elements. Incidentally, note that the following code uses the static factory method added in Java 9 List.of to create a list, and if you are using an earlier version, use Arrays.asList :

InstrumentedHashSet<String> s = new InstrumentedHashSet<>();s.addAll(List.of("Snap", "Crackle", "Pop"));

getAddCountThe result we expect the method to return is 3, but it actually returns 6. Where's the problem? HashSetinternally, the addAll method is implemented based on its add approach, which is justified even if the HashSet document does not have its implementation details named. InstrumentedHashSetthe addAll method in first addCount sets the property to 3 and then uses super.addAll HashSet the implementation that the method called addAll . Then, in turn, the method that is overridden in the class is called InstrumentedHashSet once for add each element. The three calls were added to addCount 1, so there was a total increase of 6: addAll each added element was calculated two times by means of the method.

We can addAll "fix" the subclass by eliminating the rewrite of the method. Although the generated class works correctly, it relies on its correct method, because HashSet the addAll method is implemented on top of its add methods. This "self-use (Self-use)" is an implementation detail that is not guaranteed to be applicable in all implementations of the Java platform and can vary with the release version. Thus, the resulting InstrumentedHashSet class is fragile.

Slightly better, the overriding addAll method iterates through the specified collection and invokes the method once for each element add . Regardless HashSet of addAll whether the method is add implemented on its method, the correct result is guaranteed because HashSet the addAll implementation is no longer called. However, this technology does not solve all the problems. This is equivalent to re-implementing the parent class method, which may not be able to determine whether or not it is used (self-use), which is difficult, time-consuming, error-prone, and may degrade performance. In addition, this approach does not always work because subclasses cannot access some private properties, so some methods cannot be implemented.

One related reason for the fragility of subclasses is that their parent classes can add new methods in subsequent releases. It is assumed that the security of a program relies on the fact that all elements that are inserted into the set meet a prerequisite. You can ensure this by subclasses of the collection, and then by overriding all methods that add elements to ensure that the prerequisites are met before each element is added. If the parent class does not have a new method for adding elements in subsequent releases, then this is no problem. However, once the parent class has added this new method, it is very likely that the illegal element will be added to the instance of the subclass because of a new method that has not been overridden. This is not a purely theoretical question. The Hashtable addition of Vector classes to the collections framework fixes several security vulnerabilities of a similar nature.

Both of these problems stem from the overriding method. If you just add a new method and do not rewrite the existing method, you might think that inheriting a class is safe. While this extension is more secure, it is not without risk. If the parent class adds a new method to the subsequent version, and you unfortunately give the subclass a method with the same signature and a different return type, then your subclass compilation fails [jls,8.4.8.3]. If you have provided a method for the subclass with the same signature and return type as the new parent class method, you are now overriding it, so you will encounter the problem described earlier. In addition, it is doubtful whether your method will fulfill the convention of the new parent class method, because this convention is not written when you write a subclass method.

Fortunately, there is a way to avoid all of the above problems. Instead of inheriting an existing class, you should add a private property to your new class that is an instance reference to an existing class, called a combination (composition), because the existing class becomes part of the new class. Each instance method in the new class invokes the corresponding method on the containing instance of the existing class and returns the result. This is called forwarding (forwarding), and the method in the new class is called the forwarding method. The resulting classes will be rock solid and do not depend on the implementation details of the existing classes. Even if you add a new method to an existing class, the new class will not have an impact. For specific purposes, the following code uses a combination and forwarding method instead of a InstrumentedHashSet class. Note that the implementation is divided into two parts, the class itself and a reusable forwarding class, which contains all the forwarding methods, and there is no other way:

Reusable forwarding Classimport Java.util.collection;import Java.util.iterator;import Java.util.set;public class    Forwardingset<e> implements set<e> {private final set<e> s;    Public Forwardingset (set<e> s) {this.s = s;    } public void Clear () {s.clear ();    Public Boolean contains (Object o) {return s.contains (o);    } public boolean IsEmpty () {return s.isempty ();    } public int size () {return s.size ();    } public iterator<e> Iterator () {return s.iterator ();    } public boolean Add (e e) {return s.add (e);    public boolean remove (Object o) {return s.remove (o);    } public boolean Containsall (collection<?> c) {return s.containsall (c);    } public boolean addall (collection<? extends e> c) {return s.addall (c);    } public boolean RemoveAll (collection<?> c) {return s.removeall (c); } public boolean Retainall (Collection<?> c) {return s.retainall (c);    } public object[] ToArray () {return S.toarray ();    } public <T> t[] ToArray (t[] a) {return S.toarray (a);    } @Override public boolean equals (Object o) {return s.equals (o);    } @Override public int hashcode () {return s.hashcode ();    } @Override Public String toString () {return s.tostring (); }}
// Wrapper class - uses composition in place of inheritanceimport java.util.Collection;import java.util.Set;public class InstrumentedSet<E> extends ForwardingSet<E> {    private int addCount = 0;    public InstrumentedSet(Set<E> s) {        super(s);    }        @Override public boolean add(E e) {        addCount++;        return super.add(e);    }    @Override public boolean addAll(Collection<? extends E> c) {        addCount += c.size();        return super.addAll(c);    }    public int getAddCount() {        return addCount;    }}

InstrumentedSetThe design of a class is implemented through the existence of a set interface that contains the HashSet functional characteristics of the class. In addition to being powerful, this design is very flexible. The InstrumentedSet class implements the set interface and has a constructor method whose parameters are set types. Essentially, this class Set converts to another type Set , adding the ability to count. Unlike an inheritance-based approach, the method applies only to a single concrete class, and each of the parent classes needs to support the construction method, providing a separate construction method, so the wrapper class can be used to wrap any Set implementation and can be used in conjunction with any pre-existing construction method:

Set<Instant> times = new InstrumentedSet<>(new TreeSet<>(cmp));Set<E> s = new InstrumentedSet<>(new HashSet<>(INIT_CAPACITY));

InstrumentedSetClass can even be used to temporarily replace instances of collections that are used without the count feature:

static void walk(Set<Dog> dogs) {    InstrumentedSet<Dog> iDogs = new InstrumentedSet<>(dogs);    ... // Within this method use iDogs instead of dogs}

InstrumentedSetClasses are called wrapper classes, because each InstrumentedSet instance contains another instance ("wrapper") Set . This is also known as adorner mode [Gamma95], because InstrumentedSet the class "decorates" a collection by adding a count function. Sometimes the combination of combinations and forwards is not precisely called a delegate (delegation). Technically speaking, unless the wrapper object passes itself to the wrapped object, it is not commissioned [Lieberman86; GAMMA95].

The packaging class has few drawbacks. One caveat is that the wrapper class is not intended to be used in the callback framework (callback frameworks), where the object passes self-references to other objects for subsequent calls ("callbacks"). Because a wrapped object does not know the wrapper object outside of it, it passes a reference to itself (this), and the callback does not remember the wrapper object outside. This is called self problem [Lieberman86]. Some people worry about the performance impact of forwarding method calls, as well as the wrapper object's memory footprint. Neither of them has much effect in practice. Writing a forwarding method can be tedious, but you only need to write a reusable forwarding class for each interface and provide a forwarding class. For example, guava provides a forwarding class for all collection interfaces [guava].

Inheritance is appropriate only if the subclass is really a subtype of the parent class. In other words, Class B can inherit class A only if there is a "is-a" relationship between the two classes. If you're trying to get Class B to inherit Class A, ask yourself this question: is each b a? If you can't answer the question truthfully, then B should not inherit a. If the answer is no, then B usually contains a private instance of a, and exposing a different api:a is not an important part of B, but its implementation details.

There are some obvious violations of this principle in the Java Platform Class Library. For example, a stacks instance is not a vector instance, so Stack classes should not inherit Vector classes. Similarly, a property list is not a hash table, so the Properties class should not be inherited Hashtable . In both cases, the combination is preferable.

If inheritance is used where appropriate, the implementation details are exposed unnecessarily. The resulting APIs will be associated with the original implementation, limiting the performance of the class forever. More seriously, by exposing their internals, clients can access them directly. At the very least, it can lead to confusing semantics. For example, if the attribute P points Properties to an instance, then p.getProperty(key) it is possible to p.get(key) return different results: The former considers the default property sheet and the latter is inherited Hashtable , and it does not consider the default property list. Worst of all, the client can break the immutability of subclasses by directly modifying the super-parent class. In the Properties class, the designer wants only strings to be allowed as keys and values, but direct access to the underlying Hashtable allows violation of this invariance. Once violated, other parts of the property API ( load and methods) can no longer be used store . When this problem is discovered, it is too late to correct the problem because the client relies on the use of non-string keys and values.

Before you decide to use inheritance instead of a combination, you should ask yourself the last set of questions. Is there a bug in its API for the class that is trying to inherit? If so, would you like to spread these flaws into your class's API? Inherit any flaws in the API that propagate the parent class, and the combination lets you design a new API that hides these flaws.

In short, inheritance is powerful, but it is problematic because it violates encapsulation. Applies only if there is a true subtype relationship between the subclass and the parent class. Even so, if a subclass is not in the same package as the parent class, and the parent class is not designed for inheritance, inheritance can lead to fragility. To avoid this vulnerability, use compositing and forwarding instead of inheritance, especially if there is an appropriate interface to implement the wrapper class. The wrapper class is not only more robust than subclasses, but also more powerful.

Effective Java Third edition--18. Combination better than inheritance

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.