<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## Chapter 4. Classes and Interfaces(類和接口) ### Item 18: Favor composition over inheritance(優先選擇復合而不是繼承) Inheritance is a powerful way to achieve code reuse, but it is not always the best tool for the job. Used inappropriately, it leads to fragile software. It is safe to use inheritance within a package, where the subclass and the superclass implementations are under the control of the same programmers. It is also safe to use inheritance when extending classes specifically designed and documented for extension (Item 19). Inheriting from ordinary concrete classes across package boundaries, however, is dangerous. As a reminder, this book uses the word “inheritance” to mean implementation inheritance (when one class extends another). The problems discussed in this item do not apply to interface inheritance (when a class implements an interface or when one interface extends another). 繼承是實現代碼復用的一種強大方法,但它并不總是最佳的工具。使用不當會導致軟件變得脆弱。在同一個包中使用繼承是安全的,其中子類和超類實現由相同的程序員控制。在對專為擴展而設計和文檔化的類時使用繼承也是安全的([Item-19](/Chapter-4/Chapter-4-Item-19-Design-and-document-for-inheritance-or-else-prohibit-it.md))。然而,對普通的具體類進行跨越包邊界的繼承是危險的。作為提醒,本書使用「繼承」一詞來表示實現繼承(當一個類擴展另一個類時)。本條目中討論的問題不適用于接口繼承(當類實現接口或一個接口擴展另一個接口時)。 Unlike method invocation, inheritance violates encapsulation [Snyder86]. In other words, a subclass depends on the implementation details of its superclass for its proper function. The superclass’s implementation may change from release to release, and if it does, the subclass may break, even though its code has not been touched. As a consequence, a subclass must evolve in tandem with its superclass, unless the superclass’s authors have designed and documented it specifically for the purpose of being extended. 與方法調用不同,繼承破壞了封裝 [Snyder86]。換句話說,子類的功能正確與否依賴于它的超類的實現細節。超類的實現可能在版本之間發生變化,如果發生了變化,子類可能會崩潰,即使子類的代碼沒有被修改過。因此,子類必須與其超類同步發展,除非超類是專門為擴展的目的而設計的,并具有很明確的文檔說明。 To make this concrete, let’s suppose we have a program that uses a HashSet. To tune the performance of our program, we need to query the HashSet as to how many elements have been added since it was created (not to be confused with its current size, which goes down when an element is removed). To provide this functionality, we write a HashSet variant that keeps count of the number of attempted element insertions and exports an accessor for this count. The HashSet class contains two methods capable of adding elements, add and addAll, so we override both of these methods: 為了使問題更具體一些,讓我們假設有一個使用 HashSet 的程序。為了優化程序的性能,我們需要查詢 HashSet,以確定自創建以來添加了多少元素(不要與當前的大小混淆,當元素被刪除時,當前的大小會遞減)。為了提供這個功能,我們編寫了一個變量,它記錄試圖插入 HashSet 的元素數量,并為這個計數變量導出一個訪問器。HashSet 類包含兩個能夠添加元素的方法,add 和 addAll,因此我們覆蓋這兩個方法: ``` // 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. Suppose we create an instance and add three elements using the addAll method. Incidentally, note that we create a list using the static factory method List.of, which was added in Java 9; if you’re using an earlier release, use Arrays.asList instead: 這個類看起來是合理的,但是它不起作用。假設我們創建了一個實例,并使用 addAll 方法添加了三個元素。順便說一下,我們使用 Java 9 中添加的靜態工廠方法 `List.of` 創建了一個列表;如果你使用的是早期版本,那么使用 `Arrays.asList`: ``` InstrumentedHashSet<String> s = new InstrumentedHashSet<>(); s.addAll(List.of("Snap", "Crackle", "Pop")); ``` We would expect the getAddCount method to return three at this point, but it returns six. What went wrong? Internally, HashSet’s addAll method is implemented on top of its add method, although HashSet, quite reasonably, does not document this implementation detail. The addAll method in Instrumented-HashSet added three to addCount and then invoked HashSet’s addAll implementation using super.addAll. This in turn invoked the add method, as overridden in InstrumentedHashSet, once for each element. Each of these three invocations added one more to addCount, for a total increase of six: each element added with the addAll method is double-counted. 我們希望 getAddCount 方法此時返回 3,但它返回 6。到底是哪里出了錯?在 HashSet 內部,addAll 方法是基于 add 方法實現的,盡管 HashSet 理所當然的沒有記錄這個實現細節。InstrumentedHashSet 中的 addAll 方法使 addCount 變量增加了 3,然后通過 `super.addAll` 調用 HashSet 的 addAll 實現。這相當于反過來調用在 InstrumentedHashSet 中被覆蓋的 add 方法,每個元素一次。這三個調用中的每一個都使 addCount 變量增加了 1,總共增加了 6,即使用 addAll 方法添加的每個元素都被重復計數。 We could “fix” the subclass by eliminating its override of the addAll method. While the resulting class would work, it would depend for its proper function on the fact that HashSet’s addAll method is implemented on top of its add method. This “self-use” is an implementation detail, not guaranteed to hold in all implementations of the Java platform and subject to change from release to release. Therefore, the resulting InstrumentedHashSet class would be fragile. 我們可以通過移除子類覆蓋的 addAll 方法來「修復」。雖然生成的類可以工作,但它的正確功能取決于 HashSet 的 addAll 方法是基于 add 方法實現的事實。這種「自用」是實現細節,不能保證在 Java 平臺的所有實現中都保留,并且在不同的版本中可能會有變化。因此,生成的 InstrumentedHashSet 類是脆弱的。 It would be slightly better to override the addAll method to iterate over the specified collection, calling the add method once for each element. This would guarantee the correct result whether or not HashSet’s addAll method were implemented atop its add method because HashSet’s addAll implementation would no longer be invoked. This technique, however, does not solve all our problems. It amounts to reimplementing superclass methods that may or may not result in self-use, which is difficult, time-consuming, errorprone,and may reduce performance. Additionally, it isn’t always possible because some methods cannot be implemented without access to private fields inaccessible to the subclass. 如果通過覆蓋 addAll 方法來遍歷指定的集合,對每個元素調用一次 add 方法會稍好一些。無論 HashSet 的 addAll 方法是否基于 add 方法實現都能保證得到正確結果,因為 HashSet 的 addAll 實現將不再被調用。然而,這種技術并不能解決我們所有的問題。它相當于重新實現超類方法,這可能會導致「自用」,也可能不會,這是困難的、耗時的、容易出錯的,并且可能會降低性能。此外,這并不總是可行的,如果子類無法訪問某些私有字段,這些方法就無法實現。 A related cause of fragility in subclasses is that their superclass can acquire new methods in subsequent releases. Suppose a program depends for its security on the fact that all elements inserted into some collection satisfy some predicate.This can be guaranteed by subclassing the collection and overriding each method capable of adding an element to ensure that the predicate is satisfied before adding the element. This works fine until a new method capable of inserting an element is added to the superclass in a subsequent release. Once this happens, it becomes possible to add an “illegal” element merely by invoking the new method, which is not overridden in the subclass. This is not a purely theoretical problem. Several security holes of this nature had to be fixed when Hashtable and Vector were retrofitted to participate in the Collections Framework. 子類脆弱的一個原因是他們的超類可以在后續版本中獲得新的方法。假設一個程序的安全性取決于插入到某個集合中的所有元素滿足某個斷言。這可以通過子類化集合和覆蓋每個能夠添加元素的方法來確保在添加元素之前滿足斷言。這可以很好地工作,直到在后續版本中向超類中添加能夠插入元素的新方法。一旦發生這種情況,只需調用新方法就可以添加「非法」元素,而新方法在子類中不會被覆蓋。這不是一個純粹的理論問題。當 Hashtable 和 Vector 被重新改裝以加入 Collections 框架時,必須修復幾個這種性質的安全漏洞。 Both of these problems stem from overriding methods. You might think that it is safe to extend a class if you merely add new methods and refrain from overriding existing methods. While this sort of extension is much safer, it is not without risk. If the superclass acquires a new method in a subsequent release and you have the bad luck to have given the subclass a method with the same signature and a different return type, your subclass will no longer compile [JLS, 8.4.8.3]. If you’ve given the subclass a method with the same signature and return type as the new superclass method, then you’re now overriding it, so you’re subject to the problems described earlier. Furthermore, it is doubtful that your method will fulfill the contract of the new superclass method, because that contract had not yet been written when you wrote the subclass method. 這兩個問題都源于覆蓋方法。你可能認為,如果只添加新方法,并且不覆蓋現有方法,那么擴展類是安全的。雖然這種擴展會更安全,但也不是沒有風險。如果超類在隨后的版本中獲得了一個新方法,而子類具有一個相同簽名和不同返回類型的方法,那么你的子類將不能編譯 [JLS, 8.4.8.3]。如果給子類一個方法,該方法具有與新超類方法相同的簽名和返回類型,那么現在要覆蓋它,因此你要面對前面描述的問題。此外,你的方法是否能夠完成新的超類方法的約定是值得懷疑的,因為在你編寫子類方法時,該約定還沒有被寫入。 Luckily, there is a way to avoid all of the problems described above. Instead of extending an existing class, give your new class a private field that references an instance of the existing class. This design is called composition because the existing class becomes a component of the new one. Each instance method in the new class invokes the corresponding method on the contained instance of the existing class and returns the results. This is known as forwarding, and the methods in the new class are known as forwarding methods. The resulting class will be rock solid, with no dependencies on the implementation details of the existing class. Even adding new methods to the existing class will have no impact on the new class. To make this concrete, here’s a replacement for InstrumentedHashSet that uses the composition-and-forwarding approach. Note that the implementation is broken into two pieces, the class itself and a reusable forwarding class, which contains all of the forwarding methods and nothing else: 幸運的是,有一種方法可以避免上述所有問題。與其擴展現有類,不如為新類提供一個引用現有類實例的私有字段。這種設計稱為復合,因為現有的類是新類的一個組件。新類中的每個實例方法調用現有類實例的對應方法,并返回結果。這稱為轉發,新類中的方法稱為轉發方法。生成的類將非常堅固,不依賴于現有類的實現細節。即使向現有類添加新方法,也不會對新類產生影響。為了說明問題,這里有一個使用復合和轉發方法的案例,用以替代 InstrumentedHashSet。注意,實現被分成兩部分,類本身和一個可復用的轉發類,其中包含所有的轉發方法,沒有其他內容: ``` // Wrapper class - uses composition in place of inheritance 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; } } // Reusable forwarding class 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(); } } ``` The design of the InstrumentedSet class is enabled by the existence of the Set interface, which captures the functionality of the HashSet class. Besides being robust, this design is extremely flexible. The InstrumentedSet class implements the Set interface and has a single constructor whose argument is also of type Set. In essence, the class transforms one Set into another, adding the instrumentation functionality. Unlike the inheritance-based approach, which works only for a single concrete class and requires a separate constructor for each supported constructor in the superclass, the wrapper class can be used to instrument any Set implementation and will work in conjunction with any preexisting constructor: InstrumentedSet 類的設計是通過 Set 接口來實現的,這個接口可以捕獲 HashSet 類的功能。除了健壯外,這個設計非常靈活。InstrumentedSet 類實現了 Set 接口,并且有一個參數也是 Set 類型的構造函數。實際上,該類具有「插裝」功能,可間接將一種 Set 的轉化為另一種 Set。基于繼承的方法只適用于單個具體類,并且需要為超類中每個受支持的構造函數提供單獨的構造函數,與此不同的是,包裝器類可用于插裝任何 Set 實現,并將與任何現有構造函數一起工作: **譯注:instrumentation 譯為「插裝」,類比硬盤(Set)插裝到主板(ForwardingSet),無論硬盤如何更新換代,但是用于存儲的功能不會變。外設(客戶端)通過主板的 USB2.0 口(InstrumentedSet2.0)或 USB3.0 口(InstrumentedSet3.0),與硬盤交互,使用其存儲功能。** ``` Set<Instant> times = new InstrumentedSet<>(new TreeSet<>(cmp)); Set<E> s = new InstrumentedSet<>(new HashSet<>(INIT_CAPACITY)); ``` The InstrumentedSet class can even be used to temporarily instrument a set instance that has already been used without instrumentation: InstrumentedSet 類甚至還可以用來臨時配置一個沒有「插裝」功能的 Set 實例: ``` static void walk(Set<Dog> dogs) { InstrumentedSet<Dog> iDogs = new InstrumentedSet<>(dogs); ... // Within this method use iDogs instead of dogs } ``` The InstrumentedSet class is known as a wrapper class because each InstrumentedSet instance contains (“wraps”) another Set instance. This is also known as the Decorator pattern [Gamma95] because the InstrumentedSet class “decorates” a set by adding instrumentation. Sometimes the combination of composition and forwarding is loosely referred to as delegation. Technically it’s not delegation unless the wrapper object passes itself to the wrapped object [Lieberman86; Gamma95]. InstrumentedSet 類被稱為包裝器類,因為每個 InstrumentedSet 實例都包含(「包裝」)了另一個 Set 實例。這也稱為裝飾者模式 [Gamma95],因為 InstrumentedSet 類通過添加「插裝」來「裝飾」一個集合。有時復合和轉發的組合被不當地稱為委托。嚴格來說,除非包裝器對象將自身傳遞給包裝對象,否則它不是委托 [Lieberman86; Gamma95]。 The disadvantages of wrapper classes are few. One caveat is that wrapper classes are not suited for use in callback frameworks, wherein objects pass selfreferences to other objects for subsequent invocations (“callbacks”). Because a wrapped object doesn’t know of its wrapper, it passes a reference to itself (this) and callbacks elude the wrapper. This is known as the SELF problem [Lieberman86]. Some people worry about the performance impact of forwarding method invocations or the memory footprint impact of wrapper objects. Neither turn out to have much impact in practice. It’s tedious to write forwarding methods, but you have to write the reusable forwarding class for each interface only once, and forwarding classes may be provided for you. For example, Guava provides forwarding classes for all of the collection interfaces [Guava]. 包裝器類的缺點很少。一個需要注意的點是:包裝器類不適合在回調框架中使用,在回調框架中,對象為后續調用(「回調」)將自定義傳遞給其他對象。因為包裝對象不知道它對應的包裝器,所以它傳遞一個對它自己的引用(this),回調避開包裝器。這就是所謂的「自用」問題。有些人擔心轉發方法調用的性能影響或包裝器對象的內存占用影響。這兩種方法在實踐中都沒有多大影響。編寫轉發方法很麻煩,但是你必須只為每個接口編寫一次可復用的轉發類,而且可能會為你提供轉發類。例如,Guava 為所有的集合接口提供了轉發類 [Guava]。 Inheritance is appropriate only in circumstances where the subclass really is a subtype of the superclass. In other words, a class B should extend a class A only if an “is-a” relationship exists between the two classes. If you are tempted to have a class B extend a class A, ask yourself the question: Is every B really an A? If you cannot truthfully answer yes to this question, B should not extend A. If the answer is no, it is often the case that B should contain a private instance of A and expose a different API: A is not an essential part of B, merely a detail of its implementation. 只有子類確實是超類的子類型的情況下,繼承才合適。換句話說,兩個類 A、B 之間只有 B 滿足「is-a」關系時才應該擴展 A。如果你想讓 B 擴展 A,那就問問自己:每個 B 都是 A 嗎?如果不能對這個問題給出肯定回答,B 不應該擴展 A;如果答案是否定的,通常情況下,B 應該包含 A 的私有實例并暴露不同的 API:A 不是 B 的基本組成部分,而僅僅是其實現的一個細節。 There are a number of obvious violations of this principle in the Java platform libraries. For example, a stack is not a vector, so Stack should not extend Vector. Similarly, a property list is not a hash table, so Properties should not extend Hashtable. In both cases, composition would have been preferable. 在 Java 庫中有許多明顯違反這一原則的地方。例如,stack 不是 vector,因此 Stack 不應該繼承 Vector。類似地,property 列表不是 hash 表,因此 Properties 不應該繼承 Hashtable。在這兩種情況下,復合都是可取的。 If you use inheritance where composition is appropriate, you needlessly expose implementation details. The resulting API ties you to the original implementation, forever limiting the performance of your class. More seriously,by exposing the internals you let clients access them directly. At the very least, it can lead to confusing semantics. For example, if p refers to a Properties instance, then p.getProperty(key) may yield different results from p.get(key): the former method takes defaults into account, while the latter method, which is inherited from Hashtable, does not. Most seriously, the client may be able to corrupt invariants of the subclass by modifying the superclass directly. In the case of Properties, the designers intended that only strings be allowed as keys and values, but direct access to the underlying Hashtable allows this invariant to be violated. Once violated, it is no longer possible to use other parts of the Properties API (load and store). By the time this problem was discovered, it was too late to correct it because clients depended on the use of non-string keys and values. 如果在復合適用的地方使用了繼承,就會不必要地暴露實現細節。生成的 API 將你與原始實現綁定在一起,永遠限制了類的性能。更嚴重的是,通過暴露內部組件,你可以讓客戶端直接訪問它們。至少,它會導致語義混亂。例如,如果 p 引用了一個 Properties 類的實例,那么 `p.getProperty(key)` 可能會產生與 `p.get(key)` 不同的結果:前者考慮了默認值,而后者(從 Hashtable 繼承而來)則不會。最嚴重的是,客戶端可以通過直接修改超類來破壞子類的不變量。對于 Properties 類,設計者希望只允許字符串作為鍵和值,但是直接訪問底層 Hashtable 允許違反這個不變性。一旦違反,就不再可能使用 Properties API 的其他部分(加載和存儲)。當發現這個問題時,已經來不及糾正了,因為客戶端依賴于非字符串鍵和值的使用。 There is one last set of questions you should ask yourself before deciding to use inheritance in place of composition. Does the class that you contemplate extending have any flaws in its API? If so, are you comfortable propagating those flaws into your class’s API? Inheritance propagates any flaws in the superclass’s API, while composition lets you design a new API that hides these flaws. 在決定使用繼承而不是復合之前,你應該問自己最后一組問題。你打算擴展的類在其 API 中有任何缺陷嗎?如果是這樣,你是否愿意將這些缺陷傳播到類的 API 中?繼承傳播超類 API 中的任何缺陷,而復合允許你設計一個新的 API 來隱藏這些缺陷。 To summarize, inheritance is powerful, but it is problematic because it violates encapsulation. It is appropriate only when a genuine subtype relationship exists between the subclass and the superclass. Even then, inheritance may lead to fragility if the subclass is in a different package from the superclass and the superclass is not designed for inheritance. To avoid this fragility, use composition and forwarding instead of inheritance, especially if an appropriate interface to implement a wrapper class exists. Not only are wrapper classes more robust than subclasses, they are also more powerful. 總而言之,繼承是強大的,但是它是有問題的,因為它打破了封裝。只有當子類和超類之間存在真正的子類型關系時才合適。即使這樣,如果子類與超類在不同的包中,并且超類不是為繼承而設計的,繼承也可能導致程序脆弱。為了避免這種缺陷,應使用復合和轉發,特別是存在適當接口能實現包裝器類時更應如此。包裝類不僅比子類更健壯,而且功能更強大。 --- **[Back to contents of the chapter(返回章節目錄)](/Chapter-4/Chapter-4-Introduction.md)** - **Previous Item(上一條目):[Item 17: Minimize mutability(減少可變性)](/Chapter-4/Chapter-4-Item-17-Minimize-mutability.md)** - **Next Item(下一條目):[Item 19: Design and document for inheritance or else prohibit it(繼承要設計良好并且具有文檔,否則禁止使用)](/Chapter-4/Chapter-4-Item-19-Design-and-document-for-inheritance-or-else-prohibit-it.md)**
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看