第13条:クラスとメンバーのアクセス性を最小化する

10924 ワード

第13条:クラスとメンバーのアクセス性を最小化する
概要
設計されたモジュールと設計されていないモジュールを区別するには、外部の他のモジュールにとって、内部データやその他の詳細が隠されているかどうかが最も重要な要素です.設計されたモジュールは、すべての実装の詳細を隠し、そのAPIを実装から明確に分離します.その後、モジュール間ではAPIのみで通信が行われ、1つのモジュールは他のモジュール内部の動作を知る必要はありません.この概念は情報隠蔽(information hiding)またはパッケージ(encapsulation)と呼ばれ、ソフトウェア設計の基本原則の一つである.
メリット
情報隠蔽が非常に重要である理由の多くは、システムを構成するモジュール間の結合関係に効果的に接触することができ、これらのモジュールが独立して開発、テスト、最適化、使用、理解、修正できるという事実に由来している.これにより、これらのモジュールは並列に開発できるため、システム開発の速度を速めることができます.プログラマはこれらのモジュールをより速く理解し、デバッグ時に他のモジュールに影響を与えないため、メンテナンスの負担も軽減されます.情報隠蔽自体は、内部でも外部でもより良いパフォーマンスをもたらすことはありませんが、パフォーマンスを効果的に調整することができます.システムを完了し、どのモジュールがシステムのパフォーマンスに影響を及ぼすかを分析することで、他のモジュールの正確性に影響を与えることなく、それらのモジュールをさらに最適化することができます.情報隠蔽は、モジュール間が密接に接続されていないため、ソフトウェアの再利用性を向上させ、これらのモジュールを開発するために使用される環境に加えて、他の環境でも役立ちます.最後に、情報隠蔽は、システム全体が使用できなくても、これらの独立したモジュールが使用可能であるため、大規模なシステムの再構築のリスクを低減する.
Javaプログラミング言語は、情報の非表示を支援する多くのメカニズムを提供しています.アクセス制御(access control)メカニズムは、クラス、インタフェース、およびメンバーのアクセス性を決定します.エンティティのアクセス性は、エンティティ宣言が存在する場所と、エンティティ宣言に表示されるアクセス修飾子(private、protected、public)によって決まります.これらの修飾子を正しく使用することは、情報の非表示を実現するために非常に重要です.
第1のルールは簡単です.できるだけ各クラスまたはメンバーが外部からアクセスされないようにします.すなわち、作成しているソフトウェアの対応機能と一致する、可能な限り最小限のアクセスレベルを使用する必要があります.つまり、露出しなければ露出しないということです.
最上位レベルの(ネストされていない)クラスとインタフェースでは、パケットレベルプライベート(package-private)とパブリック(public)の2つの可能なアクセスレベルしかありません.クラスやインタフェースをパケットプライベートレベルにすることで、実際には、パケットがエクスポートしたAPIの一部ではなく、このパケットの実装の一部となり、以降のリリースでは、既存のクライアントプログラムに影響を及ぼす心配がなく、変更、置換、または削除することができる.もしあなたがそれを公有にしたら、互換性を保証するために永遠にそれをサポートする責任があります.
パケット・レベルのプライベート・最上位クラス(またはインタフェース)がクラスの内部でのみ使用される場合は、そのクラスを使用する唯一のプライベート・ネスト・クラスとして使用することを考慮する必要があります.これにより、パッケージ内のすべてのクラスから使用するクラスにアクセス可能な範囲を縮小できます.しかしながら、不要な公有クラスのアクセス性を低減することは、パケットプライベートレベルの最上位クラスを低減するよりも重要である.公有クラスはパケットのAPIの一部であり、パケットプライベートの最上位クラスはすでにこのパケットの実現の一部であるからである.APIは外部に露出するため,これらの隠蔽性を調節することがより重要である.
メンバー(ドメイン、メソッド、ネストされたクラス、ネストされたインタフェース)には、4つの可能なアクセス・レベルがあります.次に、アクセス性の増加順に羅列します.
  • プライベート(private):このメンバーにアクセスできるのは、メンバーを宣言する最上位クラス内のみです.
  • パッケージ・レベルプライベート(package-private):メンバーのパッケージ内の任意のクラスがこのメンバーにアクセスできることを宣言します.通常はデフォルト(default)アクセスレベルと呼ばれ、メンバーにアクセス修飾子を指定しない場合はこのアクセスレベルが使用されます.
  • 保護された(protected):メンバーのクラスを宣言するサブクラスはアクセス可能であり、メンバーのパッケージ内の任意のクラスもアクセス可能であることを宣言します.
  • 公有(public):どこでもアクセスできます.

  • アクセス・レベルに関する推奨事項
    クラスのAPIをよく設計すると、API以外のメンバーをすべてプライベートにすべきだと感じるかもしれません.実際には、同じパッケージ内の別のクラスが本当にメンバーにアクセスする必要がある場合にのみ、private修飾子を削除して、メンバーをパッケージレベルのプライベートにする必要があります.
    共有クラスのメンバーの場合、アクセス・レベルがパッケージ・レベルのプライベートから保護レベルに変更されると、アクセス性が大幅に向上します.保護されたメンバーはクラスのエクスポートされたAPIの一部であり、常にサポートされている必要があります.エクスポートされたクラスの保護されたメンバーは、クラスが実装の詳細に対する公開承諾を表し、保護されたメンバーはできるだけ少なく使用する必要があります.
    メソッドのアクセス性を低下させる能力を制限するルールがあります.メソッドがスーパークラスのメソッドの1つを上書きしている場合、サブクラスのアクセス・レベルはスーパークラスのアクセス・レベルを下回ることはできません.これにより、スーパークラスのインスタンスを使用できる場所でもサブクラスの実力を使用できるようになります.このルールに違反した場合、サブクラスをコンパイルしようとすると、コンパイラはエラーメッセージを生成します.このルールには、クラスがいくつかのポートを実装している場合、インタフェース内のすべてのクラスメソッドもこのクラスで公有として宣言する必要があります.なぜなら、インタフェース内のすべてのメソッドが公開アクセスレベルを非表示にしているからです.すなわち,親メソッドを書き換える場合,アクセス権限を拡大することはできず,アクセス権限を縮小または不変にするしかない.親クラスのpublicメソッドは、サブクラスにprotectedのアクセス権mを示すことができるが、親クラスのprotectedはサブクラスにpublicの権限を示すことができない.
    テストを容易にするために、クラス、インタフェース、またはメンバー変数にアクセスしやすくしてみましょう.ある程度はいいです.テストのために公有クラスのプライベートメンバーをパッケージレベルのプライベートメンバーに変更するのは許容できますが、アクセス権をさらに拡大するのは適切ではありません.テストを被テスト部分のパッケージの一部として実行する代わりに、protectedアクセスレベルの要素を簡単にテストすることができ、モジュールのパッケージ性を破壊することはありません.
    インスタンスドメインは決して共有されてはいけません.ドメインが非finalである場合、または可変オブジェクトを指すfinal参照である場合、このドメインを公有と呼ぶと、このドメインに格納されている値を制限する能力は放棄される.これは、このドメインを可変に強制する能力も放棄したことを意味します.同時に、このドメインが変更されたとき、あなたもそれに対していかなる行動を取る能力を失いました.したがって、共通の可変ドメインを含むクラスはスレッドセキュリティではありません.
    同様の推奨は静的ドメインにも適用されますが、例外があります.定数がクラスが提供する抽象全体の一部を構成すると仮定し,これらの定数を公有の静的finalドメインによって暴露することができる.従来、このドメインの名前は大文字で構成され、単語間は下線で区切られています.重要な点は、これらのドメインには基本タイプの値が含まれているか、可変オブジェクトへの参照が含まれていることです.finalドメインに可変オブジェクトの参照が含まれている場合、finalドメインの特性は失われ、自身の参照は変更できませんが、指向するオブジェクトは変更できます.この結果は深刻です.
    長さが0以外の配列は常に可変であるため、クラスは公有の静的final配列ドメインを有するか、またはこのようなドメインへのアクセス方法を返すか、これはほとんどエラーである.クラスにこのようなドメインまたはアクセスメソッドがある場合、クライアントは配列内のコンテンツを変更できます.これはセキュリティ・ホールの一般的な根源です.
    public static final Thing[] VALUES = { ... };
    

    多くのIDEは、プライベート配列ドメインへの参照を返すアクセスメソッドを生成することに注意してください.この問題を修正するには、2つの方法があります.1つは、パブリックメール配列をプライベートにし、パブリック可変リストを追加することです.
    private static final Thing[] PRIVATE_VALUES = { ... };
    public static final List VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
    

    CollectionsについてJAvaのunmodifiableListメソッドは、ソースコードでは次のようになります.
    public static  List unmodifiableList(List extends T> list) {
        return (list instanceof RandomAccess ? 
            new UnmodifiableRandomAccessList<> (list) :
            new UnmodifiableList<>(list));
    }
    
    RandomAccess.javaというインタフェースについて述べたが、このインタフェースは公式にはこのように説明されている.
        Marker interface used by List implementations to indicate that they
        support fast (generally constant time) random access. The primary purpose 
        of this interface is to allow generic algorithms to alter their behavior
        to provide good performance when applied to either random or sequential
        access lists.
    
        The best algorithms for manipulating random access lists (such as
        ArrayList) can produce quadratic behavior when applied to sequential
        access lists (such as LinkedList). Generic list algorithms are encouraged
        to check whether the given list is an instanceof this interface before
        applying an algorithm that would provide poor performance if it were
        applied to a sequential access list, and to alter their behavior if
        necessary to guarantee acceptable performance.
    
        It is recognized that the distinction between random and sequential
        access is often fuzzy. For example, some List implementations provide
        asymptotically linear access times if they get huge, but constant access
        times in practice. Such a List implementation should generally implement
        this interface. As a rule of thumb, a List implementation should
        implement this interface if, for typical instances of the class, this
        loop:
    
         for (int i=0, n=list.size(); i < n; i++)
             list.get(i);
    
        runs faster than this loop:
             for (Iterator i=list.iterator(); i.hasNext(); )
                 i.next();
    
        This interface is a member of the Java Collections Framework.
    

    RandomAccessインタフェースはCollectionsフレームワークの一部であり、リストとそのサブクラスのクラスインスタンスがランダムな高速アクセスをサポートすることを識別するために使用されます.このインタフェースの最初の目的は、ランダムアクセスのlist(例えばArrayList)または順次アクセスのlist(LinkedList)に使用される場合、より高いパフォーマンスを提供するために、汎用アルゴリズムが特定の動作を変更することを可能にすることである.ランダムアクセスリストを操作するためのアクセスアルゴリズムがリストに順次アクセスするために使用されると、二次アクセスが発生し、効率が低下するため、アクセスするアルゴリズムの内部では、現在アクセスしているリストがlist instanceof RandomAccessという条件を満たしているかどうかをチェックし、より効率的なアクセスアルゴリズムを選択することができる.
    UnmodifiableListクラスは、リストのパッケージクラスであり、名前から見て修正不可能なリストであり、この内部でgetクラスのみを提供する方法であり、すべてのsetの方法がカバーされ、異常が投げ出され、この性質を修正できないことが保証される.実装コードは次のとおりです.
    /**
     * @serial include
     */
    static class UnmodifiableList extends UnmodifiableCollection
                                  implements List {
        private static final long serialVersionUID = -283967356065247728L;
    
        final List extends E> list;
    
        UnmodifiableList(List extends E> list) {
            super(list);
            this.list = list;
        }
    
        public boolean equals(Object o) {return o == this || list.equals(o);}
        public int hashCode()           {return list.hashCode();}
    
        public E get(int index) {return list.get(index);}
        public E set(int index, E element) {
            throw new UnsupportedOperationException();
        }
        public void add(int index, E element) {
            throw new UnsupportedOperationException();
        }
        public E remove(int index) {
            throw new UnsupportedOperationException();
        }
        public int indexOf(Object o)            {return list.indexOf(o);}
        public int lastIndexOf(Object o)        {return list.lastIndexOf(o);}
        public boolean addAll(int index, Collection extends E> c) {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public void replaceAll(UnaryOperator operator) {
            throw new UnsupportedOperationException();
        }
        @Override
        public void sort(Comparator super E> c) {
            throw new UnsupportedOperationException();
        }
    
        public ListIterator listIterator()   {return listIterator(0);}
    
        public ListIterator listIterator(final int index) {
            return new ListIterator() {
                private final ListIterator extends E> i
                    = list.listIterator(index);
    
                public boolean hasNext()     {return i.hasNext();}
                public E next()              {return i.next();}
                public boolean hasPrevious() {return i.hasPrevious();}
                public E previous()          {return i.previous();}
                public int nextIndex()       {return i.nextIndex();}
                public int previousIndex()   {return i.previousIndex();}
    
                public void remove() {
                    throw new UnsupportedOperationException();
                }
                public void set(E e) {
                    throw new UnsupportedOperationException();
                }
                public void add(E e) {
                    throw new UnsupportedOperationException();
                }
    
                @Override
                public void forEachRemaining(Consumer super E> action) {
                    i.forEachRemaining(action);
                }
            };
        }
    
        public List subList(int fromIndex, int toIndex) {
            return new UnmodifiableList<>(list.subList(fromIndex, toIndex));
        }
    
        /**
         * UnmodifiableRandomAccessList instances are serialized as
         * UnmodifiableList instances to allow them to be deserialized
         * in pre-1.4 JREs (which do not have UnmodifiableRandomAccessList).
         * This method inverts the transformation.  As a beneficial
         * side-effect, it also grafts the RandomAccess marker onto
         * UnmodifiableList instances that were serialized in pre-1.4 JREs.
         *
         * Note: Unfortunately, UnmodifiableRandomAccessList instances
         * serialized in 1.4.1 and deserialized in 1.4 will become
         * UnmodifiableList instances, as this method was missing in 1.4.
         *       Serializable       ,      UnmodifiableCollection
         *      ,                     。
         */
        private Object readResolve() {
            return (list instanceof RandomAccess
                    ? new UnmodifiableRandomAccessList<>(list)
                    : this);
        }
    }
    

    UnmodifiableRandomAccessListクラスとUnmodifiableListはほとんど違いがなく、唯一の違いはRandomAccessというインタフェースを実現し、このタイプのListがランダムアクセスできるアルゴリズムでアクセスできることを識別することである.
    もう1つの方法は、配列をプライベートにし、プライベート配列のバックアップを返す公開メールメソッドを追加することです.
    private static final Thing[] PRIVATE_VALUES = { ... };
    public static final Thing[] values() {
        return PRIVATE_VALUES.clone();
    }