Java複合は継承より優先されます

16203 ワード

コンポジットが継承より優れている
継承はカプセル化を破った(サブクラス依存親の特定の機能の実装の詳細)
合理的な使用継承の状況:
  • パッケージ内で
  • を使用
  • 親クラスは継承のために設計され、is-a関係
  • が存在するというドキュメントの説明がある.
    継承は、子が本当に親の子タイプである場合にのみ適用されます.
    2つのクラスAとBについては、両者の間に「is-a」関係が存在する限り、クラスBはクラスAを拡張することができる.
    継承メカニズムは、親API内のすべての欠陥をサブクラスに伝播し、複合は、これらの欠陥を隠すために新しいAPIを設計することを可能にする.
    複合(composition):既存のクラスを拡張するのではなく、新しいクラスにプライベートドメインを追加し、既存のクラスのインスタンスを参照します.
    転送(fowarding):新しいクラスの各インスタンスメソッドは、含まれる既存のクラスインスタンスに対応するメソッドを呼び出し、結果を返すことができます.
      1 public class FowardSet implements Set {   #   ,    
      2 
      3     //
      4     private final Set set;
      5 
      6     public FowardSet(Set set){
      7         this.set = set;
      8     }
      9 
     10 
     11     /*
     12      *    
     13      */
     14     @Override
     15     public int size() {
     16         return set.size();
     17     }
     18 
     19     @Override
     20     public boolean isEmpty() {
     21         return set.isEmpty();
     22     }
     23 
     24     @Override
     25     public boolean contains(Object o) {
     26         return set.contains(o);
     27     }
     28 
     29     @NotNull
     30     @Override
     31     public Iterator iterator() {
     32         return set.iterator();
     33     }
     34 
     35     @NotNull
     36     @Override
     37     public Object[] toArray() {
     38         return set.toArray();
     39     }
     40 
     41     @NotNull
     42     @Override
     43     public  T[] toArray(T[] a) {
     44         return set.toArray(a);
     45     }
     46 
     47     @Override
     48     public boolean add(E e) {
     49         return set.add(e);
     50     }
     51 
     52     @Override
     53     public boolean remove(Object o) {
     54         return set.remove(o);
     55     }
     56 
     57     @Override
     58     public boolean containsAll(Collection> c) {
     59         return set.containsAll(c);
     60     }
     61 
     62     @Override
     63     public boolean addAll(Collection extends E> c) {
     64         return set.addAll(c);
     65     }
     66 
     67     @Override
     68     public boolean retainAll(Collection> c) {
     69         return set.retainAll(c);
     70     }
     71 
     72     @Override
     73     public boolean removeAll(Collection> c) {
     74         return set.removeAll(c);
     75     }
     76 
     77     @Override
     78     public void clear() {
     79         set.clear();
     80     }
     81 
     82     @Override
     83     public boolean equals(Object obj) {
     84         return set.equals(obj);
     85     }
     86 
     87     @Override
     88     public String toString() {
     89         return set.toString();
     90     }
     91 
     92     @Override
     93     public int hashCode() {
     94         return set.hashCode();
     95     }
     96 }
     97 
     98 /*
     99  *    (wrapper class),       
    100  */
    101 public class InstrumentedSet extends FowardSet {
    102     private int addCount=0;
    103 
    104     public InstrumentedSet(Set set) {
    105         super(set);
    106     }
    107 
    108     @Override
    109     public boolean add(E e) {
    110         addCount++;
    111         return super.add(e);
    112     }
    113 
    114     @Override
    115     public boolean addAll(Collection extends E> c) {
    116         addCount+=c.size();
    117         return super.addAll(c);
    118     }
    119 
    120     public int getAddCount() {
    121         return addCount;
    122     }
    123 }

     
    上記の例では、FowardingSetは転送クラスであり、被包装クラスでもあるが、InstrumentedSetは依頼モードではなく装飾者モードを採用した包装クラスである.
    デコレーションモード:
    装飾者モードは組み合わせに似ていて、任意に組み合わせたり、制定したりすることができます.新しいニーズがあるときは、アクセサリーを追加してOKです.必要に応じてコンポーネントを追加することで、既存のコードを変更することなく新しい機能を拡張したり修正したりすることができるようになります.それともその設計原則--open for extension,close for modification.
    動的に対象に責任を付加する.機能を拡張するには、装飾者は継承よりも弾力性のある代替案を提供します.装飾者と被装飾者の間には同じタイプ、すなわち共通のスーパークラスが必要である.ここで継承を適用するのは、実装方法のレプリケーションではなく、実装タイプのマッチングです.装飾者と被装飾者は同じタイプであるため、装飾者が被装飾者に取って代わることができ、被装飾者に装飾者独自の行為を持たせることができる.装飾者モデルの理念に基づいて、私たちはいつでも、新しい装飾者が新しい行為を増やすことを実現することができます.継承であれば、新しい動作を追加するたびに、元のプログラムを変更します.
    できるだけ機能を独立に分割してデカップリングし、それぞれの新しい機能を分離して装飾者と呼ばれ、必要に応じて組み合わせて使用することができ、単独で継承を使用するように複数の機能を結合するのではなく、読書と使用が不便であり、拡張を利用しない.例えば、インタフェースAが機能1 2 3 4 5を持っている場合、単に継承を使用すれば、では,構造をオブジェクト向けプログラミングに適合させるためには,10個のサブクラスに組み合わせられ,機能がさらに拡張されると,数が恐ろしくなり,利用者に記憶され理解されにくくなる.デコレーションモードを使用すると、数人のデコレーションを実現し、必要に応じて組み合わせて使用するだけでよい.
    パッケージクラスはコールバックフレームワーク(callback framework)で使用するのに適していません.SELFの問題が発生します.
    コールバックフレームワークでは、オブジェクトは自身の参照を他のオブジェクトに渡し、後続の呼び出し(コールバック)に使用します.
    SELF問題:包装されたオブジェクトは外の包装オブジェクトを知らないので、自分への参照(this)を伝え、コールバック時に外の包装オブジェクトを避けました.
    簡単に言えば、継承機能は非常に強力ですが、パッケージの原則に反して多くの問題があります.サブクラスとスーパークラスにサブタイプ関係がある場合にのみ、継承を使用するのが適切であるが、サブクラスとスーパークラスが異なるパッケージにあり、スーパークラスが継承のために設計されていない場合、継承は脆弱性をもたらし、このような脆弱性を回避するために、継承の代わりに適合と転送メカニズムを使用することができ、特に適切なインタフェースが存在してパッケージクラスを実現する場合に使用することができる.包装類はサブ類より丈夫であるだけでなく、機能も強い.
    継承と複合の間でどのように選択しますか?抽象的には、子クラスと親クラスが確かに「is-a」関係にある場合にのみ継承を使用し、そうでない場合は複合を使用します.あるいは,比較的現実的な点では,サブクラスがスーパークラスの部分的な挙動を実現するだけであれば,複合を用いることを考慮する.