[Effective Java](03)プライベートビルダーまたはエニュメレーションタイプでSingletonを強化する

3676 ワード

Singletonとは、一度だけ実装されるクラスを指し、それらの本質的に唯一のシステムコンポーネント(ウィンドウマネージャまたはファイルシステム)を表す場合が多い。
  • は、Singletonのクラスにすることは、そのタイプのインターフェースとして機能することができない限り、Singletonにアナログを置換することができないので、クライアントテストを非常に困難にする。
  • 1.Singletonの一般的な方法を実現する
  • 静的メンバ(公有ドメイン方法)
  • public class Singleton {
        public static final Singleton instance = new Singleton();
    
        private Singleton() { } 
    }
    
  • 静的工場方法
  • public class Singleton {
        private static final Singleton instance = new Singleton();
        
        private Singleton() { } 
        
        public static Singleton getInstance() {
            return instance;
        }
    }
    
    注:この2つの方法は、Singletonのグローバル一意性を保証するものではなく、反射機構によってAccessibleObject.setAccessible(true)を設定し、コンストラクタのアクセス属性を変更し、コンストラクタを呼び出して新しい例を生成することができる。例えば:
    Constructor> constructor = Singleton.class.getDeclaredConstructors()[0];
    constructor.setAccessible(true);
    Singleton instance = (Singleton)constructor.newInstance();
    
    このような攻撃を防ぐために、コンストラクタを修正して、インスタンスを作成することを要求された時に異常を投げさせることができます。
    public class Singleton {
        private static int count = 0;
        private static final Singleton instance = new Singleton();  
            
        private Singleton() { 
            if(count > 0) {
                throw new IllegalArgumentException("Cannot create Singleton twice!");
            }
            count++;
        }   
        
        public static Singleton getInstance() {
            return instance;
        }
    }
    
    注:静的変数countは必ずSingletonオブジェクトinstanceの前にある必要があります。そうでないと、もう一回のコンストラクタを呼び出すことができます。
    公有域方法と静的工場方法の比較は以下の通りである。
  • 公有域法の主な利点は、このような方法はSingletonであり、公有の静的ドメインはfinalであり、このドメインはすべて同じオブジェクト参照を含むが、公有域は性能的にはもう何の利点もないことを明確に示している。現代JVMはほとんど静的工場法の呼び出しを内輪化する。
  • 工場法の利点:フレキシブル性を提供する:APIを変更しない前提で、このクラスがSingletonであるかどうかを変更することができる。
  • 2.反プロローグ対応implements Serializableを介してプログレッシブをサポートするだけでは不十分であり、プログレッシブ化されたインスタンスを配置するたびに、このような状況の発生を防ぐために、readResolve方法を追加する必要がある新しい例を作成する。
    Singletonを維持するためには、すべてのインスタンスドメインが瞬時であることを宣言し、readResolve方法を提供しなければならない。
    public class Singleton implements Serializable{
    
        private static int count = 0;
        private static final Singleton instance = new Singleton();  
            
        private Singleton() { 
            if(count > 0) {
                throw new IllegalArgumentException("Cannot create Singleton twice!");
            }
            count++;
            System.out.println(count);
        }   
        
        public static Singleton getInstance() {
            return instance;
        }
        
        
        private Object readResolve() {
            return instance;
        }
        
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            
            Singleton singleton = Singleton.getInstance();
            
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.QyQ"));
            oos.writeObject(singleton);
            oos.close();
    
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Singleton.QyQ"));
            Singleton o = (Singleton) ois.readObject();
            ois.close();
            System.out.println(singleton == o);
        }
    }
    
    3.単一要素の列挙の種類はSingletonを実現します。
    public enum Singleton {
    
        instance;
        
        public void leaveTheBuilding() {
            System.out.println("Come on baby, QyQiaoo");
        }
        
        public static void main(String[] args) {
            Singleton singleton = Singleton.instance;
            singleton.leaveTheBuilding();
        }
    }
    
    この方法は機能的には公有域法に近いが、より簡潔で、無償で序列化機構を提供し、複雑な序列化や反射攻撃に対しても複数回の実用化を防ぐことができる。
  • (Enum防止反射による実用化の原因は未完です。??)
  • (Enumアンチプログレッシブ機構未完・・・)
  • 単一要素エニュメレート・タイプはSingletonを実現するための最良の方法となっている。