設計モードの単例モード及びプロトタイプモード

9584 ワード

単一モード:
Singleton Pattern(Singleton Pattern)はJavaの中で最も簡単な設計モードの一つである.このタイプの設計モードは、オブジェクトを作成するための最適な方法を提供する作成モードに属します.このモードは、独自のオブジェクトを作成し、単一のオブジェクトのみが作成されることを保証する単一のクラスに関連します.このクラスは、クラスのオブジェクトをインスタンス化する必要がなく、直接アクセスできる独自のオブジェクトにアクセスする方法を提供します.
≪シーンの適用|Apply Scene|emdw≫:クラスにインスタンスが1つしかないことを保証し、グローバル・アクセス・ポイントを提供します.Springの単一例モードは、グローバルなアクセスポイントBeanFactoryを提供する後半文を完了しました.ただし、Springは任意のJavaオブジェクトを管理しているため、コンストラクタレベルから単一の例を制御していません.SpringのデフォルトのBeanはすべて単一の例です.
システムの一部のクラスでは、1つのインスタンスだけが重要です.たとえば、1つのシステムに複数の印刷タスクが存在してもよいが、1つの作業中のタスクしか存在しません.1つのシステムにはウィンドウマネージャまたはファイルシステムが1つしかありません.システムには、タイマツールまたはID(シーケンス番号)ジェネレータが1つしかありません.Windowsでは、タスクマネージャを1つだけ開くことができます.メカニズムを使用してウィンドウオブジェクトを一意化しないと、複数のウィンドウがポップアップされ、これらのウィンドウの表示内容が完全に一致している場合は、重複オブジェクトであり、メモリリソースを浪費します.これらのウィンドウに表示される内容が一致しないと、ある瞬間にシステムに複数の状態があり、実際と一致しないことを意味し、ユーザーに誤解をもたらし、どちらが真実なのか分からない.したがって、システム内のオブジェクトの一意性、すなわちクラスに1つのインスタンスしか存在しないことを確認することが重要になる場合があります.
クラスに1つのインスタンスしかなく、このインスタンスがアクセスしやすいことを保証するにはどうすればいいですか?グローバル変数を定義すると、オブジェクトがいつでもアクセスできるようになりますが、複数のオブジェクトをインスタンス化することはできません.より良い解決策は、クラス自身がその唯一のインスタンスを保存する責任を負うことです.このクラスは、他のインスタンスが作成されないことを保証し、インスタンスにアクセスする方法を提供します.これが単例モードのモード動機である.
Singleton(Singleton)は、一般的な設計モードです.Javaアプリケーションでは、インスタンスが1つしか存在しないJVMで、単一のオブジェクトが保証されます.このようなパターンにはいくつかのメリットがあります.
1、一部のクラスは頻繁に作成され、一部の大規模なオブジェクトでは、これは大きなシステムオーバーヘッドです.
2、newオペレータを省き、システムメモリの使用頻度を低減し、GC圧力を軽減する.
3、取引所の核心取引エンジンのようなクラスがあり、取引の流れを制御している.もしこのクラスが複数作成できれば、システムは完全に混乱している.(例えば、1つの軍隊に複数の司令官が同時に指揮すると、必ず混乱する)ため、コア取引サーバが独立してプロセス全体を制御することを保証するには、単一のモデルを使用するしかない.
単例モードのいくつかの実現方法:
主な実現方式は:怠け者式、餓漢式、二重検査ロック方式、登録登録式、静的内部類、列挙式である.スレッドが安全な怠け者式、餓漢式、二重ロック方式、静的内部類方式は移動してください:https://www.cnblogs.com/wuzhenzhao/p/9923309.html.次に登録式と列挙式を見てみましょう.
登録登録式の実装:
//Spring    ,          
public class BeanFactory {

    private BeanFactory(){}

    //       ,              
    private static Map ioc = new ConcurrentHashMap();

    public static Object getBean(String className){

        if(!ioc.containsKey(className)){
            Object obj = null;
            try {//          (           ),       
                obj = Class.forName(className).newInstance();
                ioc.put(className,obj);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return obj;
        }else{//                
            return ioc.get(className);
        }
    }
} 

列挙:
この実装方式はまだ広く採用されていないが,これは単一のモードを実現するための最良の方法である.より簡潔で、シーケンス化メカニズムを自動的にサポートし、複数回のインスタンス化を絶対に防止します.
//      ,               ?
//     API   
public enum Color {
    RED(){
       private int r = 255;
       private int g = 0;
       private int b = 0;

    },BLACK(){
        private int r = 0;
        private int g = 0;
        private int b = 0;
    },WHITE(){
        private int r = 255;
        private int g = 255;
        private int b = 255;
    };
}

反射メカニズムによって一例の破壊を引き起こす:
次のようにして、単一の例を破壊することができます.このとき、s 1とs 2は一致しない2つのオブジェクトであることがわかります.このような状況を避けるために、コンストラクション関数が2回正常に呼び出されることを防止します.コンストラクション関数でインスタンス化回数を統計し、コンストラクション関数呼び出し回数を識別するグローバル変数を設定する必要があります.一度より大きいと異常を投げ出す.
public class Singleton {
    private static Singleton instance = new Singleton();  
 
    private Singleton() {}
 
    public static Singleton getInstance() {
        return instance;
    }
}

public class Test {
    public static void main(String[] args) throws Exception{
        Singleton s1 = Singleton.getInstance();
 
        Constructor constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton s2 = constructor.newInstance();
 
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
 
    }
}

逆シーケンス化の場合、単一の破壊が発生します.
理由:逆シーケンス化により、パラメータなしのコンストラクションメソッドを反射して新しいオブジェクトが作成されます.ケースを見てみましょう.
まず、シーケンス化インタフェースを実装する単一のクラスを構築します.
//           
public class Seriable implements Serializable {
    //                        
    //      IO ,       (     、  IO)
    //             

    //    
    //            ,   IO 
    //  IO    ,           Java  
    //             new


    public  final static Seriable INSTANCE = new Seriable();
    private Seriable(){}

    public static  Seriable getInstance(){
        return INSTANCE;
    }

//    private  Object readResolve(){
//        return  INSTANCE;
//    }

}

テスト:
public class SeriableTest {
    public static void main(String[] args) {

        Seriable s1 = null;
        Seriable s2 = Seriable.getInstance();

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("Seriable.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();


            FileInputStream fis = new FileInputStream("Seriable.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (Seriable)ois.readObject();
            ois.close();

            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

ここで出力されるインタフェースでは、2つの例が等しくないことがわかります.これは1つの例を破壊しているのではないでしょうか.これはなぜですか.ObjectInputStreamでreadObjectに行ったときの処理を簡単に見てみましょう.彼の呼び出しチェーンは以下の通りです.
readObject()-->readObject 0(boolean unshared)-->checkResolve(readOrdinaryObject(unshared))は、readOrdinaryObject(unshared)メソッドで次のコードの一部を見つけることができます.
if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

最も重要なコールは2つあります.
hasReadResolveMethod:serializableまたはexternalizableインタフェースを実装したクラスにreadResolveが含まれている場合はtrueを返します.
invokeReadResolve:逆シーケンス化するクラスを反射的に呼び出すreadResolveメソッド.
したがって,主にSingletonでreadResolveメソッドを定義し,このメソッドで返すオブジェクトの生成ポリシーを指定することで,一例の破壊を防止できることが原理的に明らかになった.
プロトタイプ:
プロトタイプ・モード(Prototype Pattern)は、重複するオブジェクトを作成し、パフォーマンスを保証します.このタイプの設計モードは、オブジェクトを作成するための最適な方法を提供する作成モードに属します.このモードは、現在のオブジェクトのクローンを作成するためのプロトタイプインタフェースを実現します.このモードは、オブジェクトを直接作成するコストが大きい場合に使用します.
プロトタイプモードは1種の作成型設計モードで、Prototypeモードは1つのオブジェクトが更に別のカスタマイズ可能なオブジェクトを作成することを許可して、まったくどのように作成する細部を知る必要はなくて、動作原理は:1つのプロトタイプオブジェクトをその発動して作成するオブジェクトに伝えることを通じて、この作成するオブジェクトはプロトタイプオブジェクトにそれら自身をコピーするように要求することによって作成を実施します.JAvaが提供するCloneableインタフェースを実装します.
クローンオブジェクトをcloneで作成する際のコピーの深さの問題は、浅いコピーと深いコピーに分けられます.
浅いコピー:コピーのレベルが浅い.変数をコピーできます.オブジェクト内にオブジェクトがある場合は、オブジェクトのアドレスしかコピーできません.
深度コピー:オブジェクトと値をコピーします.2つのオブジェクトが任意の値を変更しても、もう1つの値は変更されません.これは、深度コピーであり、変数をコピーしたり、現在のオブジェクトの内部オブジェクトをコピーしたりすることができます.
まず、浅いコピー方式のプロトタイプモードを見て、エンティティクラス実装Cloneableインタフェースを作成します.
public class Prototype implements Cloneable {

    public String name;

    public ArrayList list = new ArrayList();

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

テスト:コードを実行すると、出力結果がtrueと2であることがわかります.pインスタンスのlistプロパティを変更すると、cloneオブジェクトのlistも変化し、同じアドレスを指します.
public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Prototype p = new Prototype();
        p.name = "helloworld";
        p.list.add("1");
        Prototype clone = (Prototype)p.clone();
        p.list.add("2");
        System.out.println(p.list==clone.list);
        System.out.println(clone.list.size());
    }
}

しかし、多くの実際のビジネスシーンでは、控訴を望んでいません.では、深いコピーを実現するにはどうすればいいのでしょうか.シーケンス化によって、ストリームの読み取りによって行うことができます.単一のモードでは、逆シーケンス化によって単一のモードを破壊することができるからです.エンティティークラスを次のように変更してテストクラスを実行すると、深いコピーが完了します.
public class Prototype implements Cloneable , Serializable {

    public String name;

    public ArrayList list = new ArrayList();

    @Override
    protected Object clone() {
//        return super.clone();
        return deepClone();
    }

    public Object deepClone(){
        try{

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);

            Prototype copy = (Prototype)ois.readObject();

            return copy;

        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
} 

利点:1、性能が向上する.2、構造関数の制約を避ける.
欠点:1、クローン作成方法を搭載するには、クラスの機能を全面的に考慮する必要があります.これは新しいクラスでは難しくありませんが、既存のクラスでは必ずしも容易ではありません.特に、クラスリファレンスがシリアル化された間接オブジェクトをサポートしていない場合、またはループ構造を含むものを参照する場合です.2、Cloneableインタフェースを実現しなければならない.
転載先:https://www.cnblogs.com/wuzhenzhao/p/10288795.html