ソフトウェアの設計とアーキテクチャ - 構造パターン

さらに詳しく知りたい場合は、私の個人的なウェブサイト「Pupil Space」をご覧ください。

構造パターンは、クラスまたはオブジェクトを特定のレイアウトでより大きな構造に編成する方法を記述します。積み木と同じように、単純な積み木を組み合わせることで、複雑で強力な構造を形成できます。

ここに画像の説明を挿入

構造パターンはクラス構造パターンとオブジェクト構造パターンに分類できます。

  • クラス構造パターンはクラスの組み合わせを考慮したもので、複数のクラスを組み合わせて大きなシステムを構築することができますが、クラス構造パターンには一般に継承関係と実装関係のみが存在します。
  • オブジェクト構造モデルはクラスとオブジェクトの組み合わせを考慮しており、関連関係を通じて、あるクラス内に別のクラスのインスタンス オブジェクトが定義され、オブジェクトを通じてそのメソッドが呼び出されます。「複合再利用の原則」に従って、システム内の継承関係を関連関係に置き換えようとするため、ほとんどの構造パターンはオブジェクト構造パターンになります。

構造パターンは以下の7種類に分類されます。

  • プロキシモード
  • アダプターパターン
  • デコレータパターン
  • ブリッジモード
  • 外観モード
  • コンビネーションモード
  • フライウェイトモード

1: プロキシモード

次の 2 つの状況のように、クライアントはオブジェクトを直接操作できなくても、そのオブジェクトと対話する必要がある場合があります。

  • オブジェクトが大きな画像の場合、表示に時間がかかるため、実際の画像を置き換える画像プロキシを作成する必要があります
  • オブジェクトがリモート サーバー上にある場合、ネットワーク速度のせいでオブジェクトを直接操作すると遅くなる可能性があるため、最初にプロキシを使用してそのオブジェクトを置き換えることができます。

この変化にどう対処すればよいでしょうか? もともと対話が困難だった 2 つのオブジェクト間で妨げられない通信を可能にするメカニズムを提供するにはどうすればよいでしょうか? 要件の変化に応じてシステムの構造が簡単に変わらないようにするにはどうすればよいでしょうか? これがプロキシモードです。

プロキシ モード: 他のオブジェクトにプロキシを提供して、このオブジェクトへのアクセスを制御します。

エージェント モードは 3 つの役割に分かれています。

  • 抽象テーマ クラス: インターフェイスまたは抽象クラスを介して、実際のテーマとプロキシ オブジェクトによって実装されるビジネス メソッドを宣言します。
  • 実テーマクラス:具体的な業務を抽象テーマで実現し、プロキシオブジェクトで表現される実オブジェクトであり、最終的に参照されるオブジェクトである。
  • プロキシ クラス: 実際のテーマと同じインターフェイスを提供し、内部に実際のテーマへの参照が含まれており、実際のテーマの機能にアクセス、制御、または拡張できます。

Java のエージェントは、エージェント クラスの生成時間に従ってさまざまなタイプに分類されます。

  • 静的プロキシ: 静的プロキシ プロキシ クラスはコンパイル時に生成されます。
  • 動的プロキシ: 動的プロキシ プロキシ クラスは、Java 実行時に動的に生成されます。動的エージェントには 2 つのタイプがあります。
    • JDKプロキシ
    • CGLib プロキシ

1.1: 静的プロキシ

以下は駅での切符販売の例です。

public class ProxyDemo1 {
    
    
    public static void main(String[] args) {
    
    
        ProxyPoint1 proxyPoint = new ProxyPoint1();
        proxyPoint.sell();
    }
}

// 售票接口
interface SellTickets1 {
    
    
    void sell();
}

// 火车站
class TrainStation1 implements SellTickets1 {
    
    
    @Override
    public void sell() {
    
    
        System.out.println("火车站卖票");
    }
}

// 代售点卖票
class ProxyPoint1 implements SellTickets1 {
    
    
    // 声明火车站类对象
    private TrainStation1 trainStation = new TrainStation1();

    @Override
    public void sell() {
    
    
        System.out.println("收取服务费");
        trainStation.sell();
    }
}

上記のコードから、メイン クラス (テスト クラスとして機能) が ProxyPoint クラス オブジェクトに直接アクセスする、つまり、ProxyPoint がアクセス オブジェクトとターゲット オブジェクトの間の仲介者として機能することがわかります。同時に販売方法も強化(手数料は代理店ポイントによる)。

1.2: 動的プロキシ

1.2.1: JDK プロキシ

Java は動的プロキシ クラス Proxy を提供します。これは、プロキシ オブジェクトを作成してプロキシ オブジェクトを取得するための静的メソッド (newProxyInstance メソッド) を提供します。

鉄道駅でのチケット販売を改善するためのコードは次のとおりです。

public class ProxyDemo2 {
    
    
    public static void main(String[] args) {
    
    
        ProxyFactory2 proxyFactory = new ProxyFactory2();
        SellTickets2 proxyObject = proxyFactory.getProxyObject();
        proxyObject.sell();
    }
}

// 售票接口
interface SellTickets2 {
    
    
    void sell();
}

// 火车站
class TrainStation2 implements SellTickets2 {
    
    
    @Override
    public void sell() {
    
    
        System.out.println("火车站卖票");
    }
}

// 代理工厂,用于创建代理对象
class ProxyFactory2 {
    
    
    private TrainStation2 station = new TrainStation2();

    public SellTickets2 getProxyObject() {
    
    
        return (SellTickets2)Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                (proxy, method, args) -> {
    
    
                    /*
                     * proxy:与Proxy.newProxyInstance返回值为同一对象
                     * method:对接口中的方法进行封装的method对象
                     * args:调用方法的实际参数
                     */
                    System.out.println("收取服务费");
                    return method.invoke(station, args);
                }
        );
    }
}

1.2.2: CGLIB プロキシ

SellTickets インターフェイスが定義されていない場合は、TrainStation (駅クラス) のみが定義されます。明らかに、JDK プロキシは使用できません。JDk 動的プロキシでは、インターフェイスをプロキシするためにインターフェイスを定義する必要があるためです。

CGLIB は、強力で高性能なコード生成パッケージです。これは、インターフェイスを実装していないクラスにプロキシを提供し、JDK の動的プロキシを適切に補完します。

CGLIB はサードパーティによって提供されるパッケージであるため、jar パッケージの座標を導入する必要があります。

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

鉄道駅でのチケット販売を改善するためのコードは次のとおりです。

public class ProxyDemo3 {
    
    
    public static void main(String[] args) {
    
    
        ProxyFactory3 proxyFactory3 = new ProxyFactory3();
        TrainStation3 proxyObject = proxyFactory3.getProxyObject();
        proxyObject.sell();
    }
}

// 火车站
class TrainStation3 {
    
    
    public void sell() {
    
    
        System.out.println("火车站卖票");
    }
}

// 代理对象工厂
class ProxyFactory3 implements MethodInterceptor {
    
    
    private TrainStation3 station = new TrainStation3();

    public TrainStation3 getProxyObject() {
    
    
        // 创建Enhancer对象,类似于JDK代理中的Proxy类
        Enhancer enhancer = new Enhancer();
        // 设置父类的字节码对象
        enhancer.setSuperClass(TrainStation3.class);
        // 设置回调函数
        enhancer.setCallback(this);
        // 创建代理对象
        return (proxyObject)enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects. MethodProxy methodProxy) {
    
    
        System.out.println("收取服务费");
        return method.invoke(station, objects);
    }
}

1.3: 概要

3 つのエージェントの比較:

  • JDK プロキシと CGLIB プロキシ: CGLIB を使用して動的プロキシを実装します。CGLIB の最下層は ASM バイトコード生成フレームワークを採用し、バイトコード テクノロジを使用してプロキシ クラスを生成します。JDK1.6 より前では、Java リフレクションを使用するよりも効率的です。唯一注意すべき点は、CGLIB の原理はプロキシされたクラスのサブクラスを動的に生成することであるため、CGLIB は Final として宣言されたクラスまたはメソッドをプロキシできないことです。JDK1.6、JDK1.7、およびJDK1.8がJDK動的プロキシを段階的に最適化した後、呼び出し数が少ない場合はJDKプロキシの効率がCGLIBプロキシよりも高くなりますが、大量の呼び出しが行われる場合のみJDK1 .6 および JDK1.7 は CGLIB プロキシよりも若干効率が劣りますが、JDK1.8 に関しては、JDK プロキシは CGLib プロキシよりも効率的です。したがって、インターフェイスがある場合は JDK 動的プロキシを使用し、インターフェイスがない場合は CGLIB プロキシを使用します。
  • 動的プロキシと静的プロキシ: 静的プロキシと比較した場合、動的プロキシの最大の利点は、インターフェイスで宣言されたすべてのメソッドが呼び出しプロセッサ (InvocationHandler.invoke) の集中メソッドに転送されることです。これにより、インターフェースメソッドが多数ある場合でも、静的プロキシのようにメソッドごとに転送する必要がなく、柔軟に対応することができます。インターフェイスがメソッドを追加する場合、静的プロキシ モードでこのメソッドを実装する必要があるすべての実装クラスに加えて、すべてのプロキシ クラスもこのメソッドを実装する必要があります。コードのメンテナンスが複雑になりました。この問題は、動的プロキシでは発生しません。

プロキシ モードの利点:

  • プロキシ モードは、クライアントとターゲット オブジェクトの間の仲介者として機能し、ターゲット オブジェクトを保護します。
  • プロキシ オブジェクトはターゲット オブジェクトの機能を拡張できます。
  • プロキシ モードはクライアントをターゲット オブジェクトから分離できるため、システムの結合をある程度軽減できます。

プロキシ モードの欠点:

  • システムの複雑さが増す

使用するシーン:

  • リモート プロキシ: ローカル サービスはネットワーク経由でリモート サービスを要求します。ローカルからリモートへの通信を実現するには、ネットワーク通信を実装し、起こり得る例外を処理する必要があります。優れたコード設計と保守性を実現するために、ネットワーク通信部分を非表示にし、ローカル サービスへのインターフェイスのみを公開します。これにより、通信部分の詳細にあまり注意を払うことなく、リモート サービスによって提供される機能にアクセスできます。
  • 仮想プロキシ: 高価なオブジェクトを作成する必要がある場合にオブジェクト情報をキャッシュします。
  • 保護プロキシ: 元のオブジェクトへのアクセスを制御します。必要に応じて、異なるユーザーに異なるレベルのアクセスを与えることができます。
  • スマート参照プロキシ (スマート参照プロキシ): オブジェクトが参照されると、トラフィックや訪問回数の記録など、追加の操作が提供されます。
  • ファイアウォール プロキシ: プロキシ機能を使用するようにブラウザを設定すると、ファイアウォールはブラウザのリクエストをインターネットに転送し、インターネットが応答を返すと、プロキシ サーバーはそれをブラウザに転送します。

2: アダプターモード

ヨーロッパ諸国に旅行する場合、そのソケットは下の写真の一番左にあり、これがヨーロッパ標準です。そして、私たちが使用するプラグは、下の写真の一番右にあるものです。したがって、ラップトップや携帯電話をローカルで直接充電することはできません。したがって、ソケットコンバータが必要です。コンバータの最初の側はローカルソケットに差し込まれ、もう一方の側は充電用であり、プラグをローカルで使用できるようになります。このような例は世の中にたくさんあり、携帯電話の充電器 (220 倍の電圧を 5 倍の電圧に変換)、カード リーダーなどが実際にアダプター モードを使用しています。

ここに画像の説明を挿入

アダプター パターン: クラスのインターフェイスを顧客が望む別のインターフェイスに変換し、インターフェイスの互換性がないために連携できなかったクラスが連携できるようにします。

アプリケーション:

  • インターフェイスが要件を満たしていない既存のクラスを使用する
  • 他の無関係なクラスまたは予期しないクラス (つまり、インターフェイスが必ずしも互換性があるとは限らないクラス) と連携できる再利用可能なクラスを作成します。
  • 既存のサブクラスをいくつか使用しますが、それぞれのインターフェイスに合わせてサブクラス化することはできません。オブジェクトアダプターはその親クラスインターフェースを適応させることができます

ここに画像の説明を挿入

アダプター パターンには、次の主な役割が含まれています。

  • ターゲット インターフェイス: 現在のシステム ビジネスによって予期されるインターフェイス。抽象クラスまたはインターフェイスにすることができます。
  • アダプター クラス: 既存のコンポーネント ライブラリ内のアクセスおよび適応されるコンポーネント インターフェイスです。
  • アダプター クラス: アダプター オブジェクトを継承または参照することによってアダプター インターフェイスをターゲット インターフェイスに変換するコンバーターであり、顧客がターゲット インターフェイスの形式でアダプターにアクセスできるようにします。

アダプタモードにはクラスアダプタモードとオブジェクトアダプタモードがあり、前者は後者に比べてクラス間の結合度が高く、プログラマは既存のコンポーネントライブラリ内の関連コンポーネントの内部構造を理解する必要があるため、応用例が比較的少ない。

2.1: クラスアダプター

実装方法:現行システムのビジネスインタフェースを実現するアダプタクラスを定義すると同時に、既存コンポーネントライブラリ内の既存コンポーネントを継承します。

たとえば、既存のコンピュータは SD カードのみを読み取ることができますが、TF カードの内容を読み取りたい場合は、アダプター モードを使用する必要があります。TF カードの内容を読み取るためのカード リーダーを作成します。コードは以下のように表示されます:

public class AdapterDemo1 {
    
    
    public static void main(String[] args) {
    
    
        Computer computer = new Computer();
        String msg = computer.readSD(new SDAdapterTF());
        System.out.println(msg);
    }
}

interface TFCard {
    
    
    String readTF();

    void writeTF(String msg);
}

// 具体TF卡
class TFCardImpl implements TFCard {
    
    
    @Override
    public String readTF() {
    
    
        return "hello TFCard";
    }

    @Override
    public void writeTF(String msg) {
    
    
        System.out.println("TFCard write msg:" + msg);
    }
}

// 目标接口
interface SDCard {
    
    
    String readSD();

    void writeSD(String msg);
}

// 具体SD卡
class SDCardImpl implements SDCard {
    
    

    @Override
    public String readSD() {
    
    
        return "hello SDCard";
    }

    @Override
    public void writeSD(String msg) {
    
    
        System.out.println("SDCard write msg:" + msg);
    }
}

// 计算机类
class Computer {
    
    
    // 从SD卡中读取数据
    public String readSD(SDCard sdCard) {
    
    
        return sdCard.readSD();
    }
}

// 适配器类
class SDAdapterTF extends TFCardImpl implements SDCard {
    
    
    @Override
    public String readSD() {
    
    
        return readTF();
    }

    @Override
    public void writeSD(String msg) {
    
    
        writeTF(msg);
    }
}

クラス アダプター パターンは、構成再利用の原則に違反します。クラス アダプタは、クライアント クラスにインターフェイス仕様がある場合に使用できますが、それ以外の場合は使用できません。

2.2: オブジェクトアダプター

実現方法: オブジェクトアダプタモードは、既存のコンポーネントライブラリに実装されているコンポーネントをアダプタクラスに導入することで、現行システムのビジネスインターフェースを同時に実現します。

上記のカード リーダーのケースを改善するためのコードは次のとおりです。

public class AdapterDemo2 {
    
    
    public static void main(String[] args) {
    
    
        Computer2 computer = new Computer2();
        String msg = computer.readSD(new SDAdapterTF2(new TFCardImpl2()));
        System.out.println(msg);
    }
}

interface TFCard2 {
    
    
    String readTF();

    void writeTF(String msg);
}

// 具体TF卡
class TFCardImpl2 implements TFCard2 {
    
    
    @Override
    public String readTF() {
    
    
        return "hello TFCard";
    }

    @Override
    public void writeTF(String msg) {
    
    
        System.out.println("TFCard write msg:" + msg);
    }
}

// 目标接口
interface SDCard2 {
    
    
    String readSD();

    void writeSD(String msg);
}

// 具体SD卡
class SDCardImpl2 implements SDCard2 {
    
    

    @Override
    public String readSD() {
    
    
        return "hello SDCard";
    }

    @Override
    public void writeSD(String msg) {
    
    
        System.out.println("SDCard write msg:" + msg);
    }
}

// 计算机类
class Computer2 {
    
    
    // 从SD卡中读取数据
    public String readSD(SDCard2 sdCard) {
    
    
        return sdCard.readSD();
    }
}

// 适配器类
class SDAdapterTF2 implements SDCard2 {
    
    
    // 声明适配者类
    private final TFCard2 tfCard;

    public SDAdapterTF2(TFCard2 tfCard) {
    
    
        this.tfCard = tfCard;
    }

    @Override
    public String readSD() {
    
    
        return tfCard.readTF();
    }

    @Override
    public void writeSD(String msg) {
    
    
        tfCard.writeTF(msg);
    }
}

3: デコレータモード

ファーストフードレストランの例から始めましょう。ファストフード店では、焼きそばやチャーハンなどのファストフードを提供していますが、卵やハム、ベーコンなどのおかずを追加することができます。もちろん、追加のおかずは有料です。通常、各おかずの価格は異なりますが、したがって、合計金額を計算するのはさらに面倒になります。

デコレータパターン(Decorator Pattern)は、オブジェクトの構造を変更せずに、オブジェクトに動的に何らかの役割を追加する(つまり、機能を追加する)パターンです。

デコレータ パターンの役割

  • 抽象コンポーネントの役割: 追加の責任を受け入れる準備ができているオブジェクトを標準化するための抽象インターフェイスを定義します。
  • 具体的なコンポーネントの役割: 抽象コンポーネントを実装し、装飾役割を通じてそれらにいくつかの責任を追加します。
  • 抽象装飾の役割: 抽象コンポーネントを継承または実装し、具象コンポーネントのインスタンスを含み、そのサブクラスを通じて具象コンポーネントの機能を拡張できます。
  • 具体的な装飾の役割: 抽象的な装飾の関連メソッドを実装し、具体的なコンポーネント オブジェクトに追加の責任を追加します。

サンプルコードは次のとおりです。

public class DecoratorDemo1 {
    
    
    public static void main(String[] args) {
    
    
        // 点一份炒饭
        FastFood food = new FriedRice();
        System.out.println(food.getDesc() + "的价格:" + food.cost());

        // 再加一份鸡蛋
        food = new Egg(food);
        System.out.println(food.getDesc() + "的价格:" + food.cost());

        // 再加一份培根
        food = new Bacon(food);
        System.out.println(food.getDesc() + "的价格:" + food.cost());
    }
}

abstract class FastFood {
    
    
    private float price;
    private String desc;

    public FastFood() {
    
    }

    public FastFood(float price, String desc) {
    
    
        this.price = price;
        this.desc = desc;
    }

    public float getPrice() {
    
    
        return price;
    }

    public void setPrice(float price) {
    
    
        this.price = price;
    }

    public String getDesc() {
    
    
        return desc;
    }

    public void setDesc(String desc) {
    
    
        this.desc = desc;
    }

    public abstract float cost();
}

// 炒饭
class FriedRice extends FastFood {
    
    
    public FriedRice() {
    
    
        super(10, "炒饭");
    }

    @Override
    public float cost() {
    
    
        return getPrice();
    }
}

// 炒面
class FriedNoodles extends FastFood {
    
    
    public FriedNoodles() {
    
    
        super(12, "炒面");
    }

    @Override
    public float cost() {
    
    
        return getPrice();
    }
}

// 装饰类
abstract class Garnish extends FastFood {
    
    
    // 声明快餐类的变量
    private FastFood fastFood;

    public FastFood getFastFood() {
    
    
        return fastFood;
    }

    public void setFastFood(FastFood fastFood) {
    
    
        this.fastFood = fastFood;
    }

    public Garnish(FastFood fastFood, float price, String desc) {
    
    
        super(price, desc);
        this.fastFood = fastFood;
    }
}

// 配料类(鸡蛋)
class Egg extends Garnish {
    
    

    public Egg(FastFood fastFood) {
    
    
        super(fastFood, 1, "鸡蛋");
    }

    @Override
    public float cost() {
    
    
        // 计算价格
        return getPrice() + getFastFood().cost();
    }

    @Override
    public String getDesc() {
    
    
        return super.getDesc() + getFastFood().getDesc();
    }
}

// 配料类(培根)
class Bacon extends Garnish {
    
    

    public Bacon(FastFood fastFood) {
    
    
        super(fastFood, 2, "培根");
    }

    @Override
    public float cost() {
    
    
        // 计算价格
        return getPrice() + getFastFood().cost();
    }

    @Override
    public String getDesc() {
    
    
        return super.getDesc() + getFastFood().getDesc();
    }
}

デコレータ パターンの利点:

  • デコレータ パターンは、継承よりも柔軟な拡張機能を実現でき、使いやすく、さまざまなデコレータ オブジェクトを組み合わせることで、異なる動作状態で多様な結果を得ることができます。デコレータ パターンは継承よりも拡張可能であり、オープンとクローズの原則に従います。継承は静的な追加責任であり、デコレータは動的な追加責任です。
  • 装飾クラスと装飾クラスは互いに結合することなく独立して開発することができ、装飾モードは継承の代替モードであり、実装クラスの機能を動的に拡張することができます。

使用するシーン:

  • 継承によってシステムを拡張できない場合、または継承によってシステムの拡張や保守が困難な場合に使用されます。継承を使用できない状況には、主に 2 つのカテゴリがあります。
    • 1 つ目は、システム内に独立した拡張機能が多数存在し、それぞれの組み合わせをサポートするために多数のサブカテゴリが生成され、サブカテゴリの数が爆発的に増加することです。
    • 2 番目のカテゴリは、クラス定義 (最終クラスなど) を継承できないためです。
  • 他のオブジェクトに影響を与えることなく、動的かつ透過的に個々のオブジェクトに責任を追加します。
  • オブジェクトの機能要件を動的に追加したり、動的に取り消したりできる場合。

4: ブリッジモード

ここで、異なるグラフィックを作成する必要があり、各グラフィックの色が異なる場合があります。継承を使用してクラスの関係を設計できます。
ここに画像の説明を挿入

クラスがたくさんあることがわかりますが、別の形や色を追加すると、さらに多くのクラスを作成する必要があります。

想像してみてください。変化する可能性のある複数の次元を持つシステムで継承を使用すると、クラスの爆発と柔軟性のない拡張が発生します。具体的な実装がディメンションに追加されるたびに、複数のサブクラスが追加されます。より柔軟にシステムを設計するために、現時点ではブリッジ モードの使用を検討できます。

ブリッジ モードの定義: 抽象化を実装から分離し、独立して変更できるようにします。これは、継承関係を合成関係に置き換えることによって実装され、それによって抽象化と実装の 2 つの可変次元の結合度が減少します。

ブリッジ モードは次の主な役割で構成されます。

  • 抽象化ロール: 抽象クラスを定義し、実装オブジェクトへの参照を含みます。
  • 拡張抽象化 (洗練された抽象化) ロール: 抽象化ロールのサブクラスであり、親クラスでビジネス メソッドを実装し、構成関係を通じて実現されたロールでビジネス メソッドを呼び出します。
  • 実装者ロール: 拡張抽象ロールによって呼び出すことができる実装者ロールのインターフェイスを定義します。具体的な実装者ロール: 実装者ロールのインターフェイスの具体的な実装を提供します。

以下は一例です。異なるオペレーティング システム プラットフォーム (Windows、Mac、Linux など) でさまざまな形式のビデオ ファイルを再生できるクロスプラットフォームのビデオ プレーヤーを開発する必要があります。一般的なビデオ形式には、RMVB、AVI、WMV などが含まれます。プレーヤーには 2 つの次元が含まれており、ブリッジ モードでの使用に適しています。

public class BridgeDemo {
    
    
    public static void main(String[] args) {
    
    
        // 创建Mac系统对象
        OperatingSystem system = new Mac(new AviFile());
        system.play("三体");
    }
}

// 视频文件接口
interface VideoFile {
    
    
    // 解码内容
    void decode(String fileName);
}

// Avi格式视频文件类
class AviFile implements VideoFile {
    
    
    @Override
    public void decode(String fileName) {
    
    
        System.out.println("avi视频文件:" + fileName);
    }
}

// Rmvb格式视频文件类
class RmvbFile implements VideoFile {
    
    
    @Override
    public void decode(String fileName) {
    
    
        System.out.println("Rmvb视频文件:" + fileName);
    }
}

// 抽象操作系统类
abstract class OperatingSystem {
    
    
    // 声明videoFile变量
    protected VideoFile videoFile;

    public OperatingSystem(VideoFile videoFile) {
    
    
        this.videoFile = videoFile;
    }

    public abstract void play(String fileName);
}

// Windows操作系统
class Windows extends OperatingSystem {
    
    
    public Windows(VideoFile videoFile) {
    
    
        super(videoFile);
    }

    @Override
    public void play(String fileName) {
    
    
        videoFile.decode(fileName);
    }
}

// Mac操作系统
class Mac extends OperatingSystem {
    
    
    public Mac(VideoFile videoFile) {
    
    
        super(videoFile);
    }

    @Override
    public void play(String fileName) {
    
    
        videoFile.decode(fileName);
    }
}

ブリッジモードの利点:

  • システムの拡張性が向上し、元のシステムを変更することなく、変化する 2 つの次元のうち 1 つを任意に拡張できます。たとえば、ビデオ ファイル タイプ wmv がまだ存在する場合、videoFile インターフェイスを実装するために別のクラスを定義するだけで済み、他のクラスを変更する必要はありません。
  • 実装の詳細は顧客に透過的です

使用するシーン:

  • クラスに独立して変化する 2 つの次元があり、両方の次元を拡張する必要がある場合。
  • システムが継承を使用したくない場合、またはマルチレベル継承によりシステム クラスの数が大幅に増加した場合。
  • システムがコンポーネントの抽象的な役割と具体的な役割の間で柔軟性を高める必要がある場合。2 つのレベル間に静的な継承リンクを確立することは避けてください。ブリッジ モードを通じて抽象レベルで関連付け関係を確立できます。

5:外観モード

株の取引をしたことがある人もいるかもしれませんが、実はあまり詳しくない人がほとんどです 証券の知識が十分でないと、株で損をしやすいです 初めて株の取引を始めると、必ずこう思うでしょう。アリペイには多くのファンドがあり、投資家から散在する資金を集め、プロのマネージャーがそれらを管理し、株式、債券、外国為替などに投資します。ファンド投資 トークンの収益は保有者に帰属し、管理機関から一定割合の保管管理手数料がかかります。

ファサード パターン (ファサード パターンとも呼ばれます) は、複数の複雑なサブシステムに一貫したインターフェイスを提供することで、これらのサブシステムにアクセスしやすくするパターンです。このモードには外部への統一インターフェイスがあり、外部アプリケーション プログラムは内部サブシステムの特定の詳細を気にする必要がありません。これにより、アプリケーション プログラムの複雑さが大幅に軽減され、プログラムの保守性が向上します。外観モードは、デメテルの法則の典型的な適用です。

ここに画像の説明を挿入

ファサード パターンには主に次の主な役割が含まれます。

  • ファサードの役割: 複数のサブシステムに共通のインターフェイスを提供します。
  • サブシステム (サブシステム) ロール: システムの一部の機能を実装し、顧客はファサード ロールを通じてそれにアクセスできます。

以下は質問例です: Xiao Ming の祖父は 60 歳で、家に一人で住んでいます。彼は毎回照明、テレビ、エアコンをつける必要があります。照明を消し、テレビを消し、テレビも消してください。寝るときにエアコンをつけると操作が面倒になる。そこで、シャオミンさんは祖父のためにスマート家電の開閉を音声で直接制御できるスマートスピーカーを購入しました。

コードは以下のように表示されます:

public class FacadeDemo {
    
    
    public static void main(String[] args) {
    
    
        SmartApplicationFacade facade = new SmartApplicationFacade();
        facade.say("打开家电");
        System.out.println("-----------------");
        facade.say("关闭家电");
    }
}

// 家具——灯
class Light {
    
    
    public void on() {
    
    
        System.out.println("打开灯。。。");
    }

    public void off() {
    
    
        System.out.println("关闭灯。。。");
    }
}

// 家具——电视
class TV {
    
    
    public void on() {
    
    
        System.out.println("打开电视。。。");
    }

    public void off() {
    
    
        System.out.println("关闭电视。。。");
    }
}

// 外观角色——智能音箱
class SmartApplicationFacade {
    
    
    private Light light;
    private TV tv;

    public SmartApplicationFacade() {
    
    
        light = new Light();
        tv = new TV();
    }

    private void on() {
    
    
        light.on();
        tv.on();
    }

    private void off() {
    
    
        light.off();
        light.off();
    }

    public void say(String msg) {
    
    
        if (msg.contains("打开")) {
    
    
            on();
        } else if (msg.contains("关闭")) {
    
    
            off();
        } else {
    
    
            System.out.println("我还听不懂");
        }
    }
}

ファサード モードの利点:

  • サブシステムとクライアント間の結合度が低下するため、サブシステムの変更はそれを呼び出すクライアント クラスに影響を与えません。
  • サブシステム コンポーネントはクライアントから保護されているため、クライアントが処理するオブジェクトの数が減り、サブシステムが使いやすくなります。

ファサード モードの欠点:

  • 開閉の原理に従わず、改造が非常に面倒

使用するシーン:

  • 階層システムを構築する場合、ファサード パターンを使用してサブシステム内の各レイヤーのエントリ ポイントを定義すると、サブシステム間の依存関係が簡素化されます。
  • 複雑なシステムに多くのサブシステムがある場合、アピアランス モードは、外部からアクセスできるシステムの単純なインターフェイスを設計できます。
  • クライアントと複数のサブシステムの間に大規模な接続がある場合、ファサード パターンの導入によりそれらを分離できるため、サブシステムの独立性と移植性が向上します。

6: コンビネーションモード

下図ではファイルシステムとみなすことができますが、このような構造をツリー構造と呼びます。ツリー構造では、特定のメソッドを呼び出すことでツリー全体を走査することができ、葉ノードを見つけたら、その葉ノードに対して関連する操作を実行できます。このツリーは、多数のメンバー オブジェクトを含む大きなコンテナーとして理解でき、これらのメンバー オブジェクトはコンテナー オブジェクトまたはリーフ オブジェクトにすることができます。しかし、コンテナ オブジェクトとリーフ オブジェクトの機能の違いにより、使用時にコンテナ オブジェクトとリーフ オブジェクトを区別する必要がありますが、これでは顧客に無用な手間がかかります。と葉のオブジェクトを一貫して作成します。

ここに画像の説明を挿入

部分全体モードとも呼ばれる複合モードは、類似したオブジェクトのグループを単一のオブジェクトとして扱うために使用されます。Composite パターンは、部分階層と全体階層を表すために使用されるツリー構造に従ってオブジェクトを構成します。このタイプのデザイン パターンは構造パターンであり、オブジェクトのグループのツリー構造を作成します。

組み合わせモードには主に 3 つの役割があります。

  • 抽象ルート ノード (コンポーネント): システムのすべてのレベルでオブジェクトの共通メソッドとプロパティを定義し、いくつかのデフォルトの動作とプロパティを事前定義できます。
  • 複合: ブランチ ノードの動作を定義し、子ノードを保存し、ブランチ ノードとリーフ ノードを組み合わせてツリー構造を形成します。
  • リーフノード (Leaf): リーフノードオブジェクト。その下に分岐はなく、システム階層横断の最小単位です。

以下はその例です。下の図に示すように、他の管理システムにアクセスすると、同様のメニューが表示されることがよくあります。メニューには、メニュー項目 (メニュー項目とは、他のコンテンツが含まれなくなったメニュー項目を指します) を含めることができ、また、他のメニュー項目を含むメニューを含めることもできるため、メニューを記述するには組み合わせモードを使用するのが適切です。メニューに含まれるすべてのメニューとメニュー項目の名前を印刷します。
ここに画像の説明を挿入

コードは以下のように表示されます:

public class CombinationDemo {
    
    
    public static void main(String[] args) {
    
    
        MenuComponent menu00 = new Menu("系统管理", 0);
        MenuComponent menu10 = new Menu("菜单管理", 1);
        MenuComponent menu11 = new Menu("权限配置", 1);
        MenuComponent menu12 = new Menu("角色管理", 1);
        menu00.add(menu10);
        menu00.add(menu11);
        menu00.add(menu12);

        menu10.add(new MenuItem("页面访问", 2));
        menu10.add(new MenuItem("展开菜单", 2));
        menu10.add(new MenuItem("编辑菜单", 2));
        menu10.add(new MenuItem("删除菜单", 2));
        menu10.add(new MenuItem("新增菜单", 2));

        menu11.add(new MenuItem("页面访问", 2));
        menu11.add(new MenuItem("保存提交", 2));

        menu12.add(new MenuItem("页面访问", 2));
        menu12.add(new MenuItem("新增角色", 2));
        menu12.add(new MenuItem("修改角色", 2));

        menu00.print();
    }
}

// 菜单组件:属于抽象根节点
abstract class MenuComponent {
    
    
    // 菜单组件的名称
    protected String name;
    // 菜单组件的层级
    protected int level;

    // 添加子菜单
    public void add(MenuComponent menuComponent) {
    
    
        throw new UnsupportedOperationException();
    }

    // 移除子菜单
    public void remove(MenuComponent menuComponent) {
    
    
        throw new UnsupportedOperationException();
    }

    // 获取指定的子菜单
    public MenuComponent getChild(int index) {
    
    
        throw new UnsupportedOperationException();
    }

    // 获取菜单或者菜单项的名称
    public String getName() {
    
    
        return name;
    }

    // 打印菜单名称的方法(包含子菜单和子菜单项)
    public abstract void print();
}

// 菜单类:属于树枝节点
class Menu extends MenuComponent {
    
    
    // 菜单可以有多个子菜单或者子菜单项
    private final List<MenuComponent> menuComponentList = new ArrayList<>();

    public Menu(String name, int level) {
    
    
        this.name = name;
        this.level = level;
    }

    @Override
    public void add(MenuComponent menuComponent) {
    
    
        menuComponentList.add(menuComponent);
    }

    @Override
    public void remove(MenuComponent menuComponent) {
    
    
        menuComponentList.remove(menuComponent);
    }

    @Override
    public MenuComponent getChild(int index) {
    
    
        return menuComponentList.get(index);
    }

    @Override
    public void print() {
    
    
        for (int i = 0; i < level; i++) {
    
    
            System.out.print("--");
        }

        // 打印菜单名称
        System.out.println(name);

        // 打印子菜单或者子菜单项名称
        for (MenuComponent component : menuComponentList) {
    
    
            component.print();
        }
    }
}

// 菜单项类:属于叶子节点
class MenuItem extends MenuComponent {
    
    
    public MenuItem(String name, int level) {
    
    
        this.name = name;
        this.level = level;
    }

    @Override
    public void print() {
    
    
        for (int i = 0; i < level; i++) {
    
    
            System.out.print("--");
        }

        System.out.println(name);
    }
}

結合モードを使用する場合、抽象コンポーネントクラスの定義形式に応じて、結合モードを透過結合モードと安全結合モードの 2 つの形式に分けることができます。

  • 透明合成モード
    • メンバー オブジェクトの管理に使用されるすべてのメソッドは、抽象ルート ノード ロールで宣言されます。たとえば、この例では、Menucomponent は 、 、addおよびremoveメソッドを宣言しますgetChild。これの利点は、すべてのコンポーネント クラスが同じインターフェイスを持つことを保証できることです。透明複合モードは、複合モードの標準形式でもあります。
    • 欠点は、リーフ オブジェクトとコンテナ オブジェクトは本質的に異なるため、安全性が十分ではないことです。リーフ オブジェクトには次のレベルのオブジェクトを持つことはできません。つまり、メンバー オブジェクトを含めることができないため、 や などのメソッドを提供するのは無意味です。コンパイル段階でエラーは発生しませんが、実行段階でこれらのメソッドが呼び出された場合 (対応するエラー処理コードが提供されていない場合)、エラーが発生する可能性がありますadd()remove()
  • セキュリティ複合モード
    • 抽象コンポーネント ロールではメンバー オブジェクトを管理するメソッドは宣言されていませんが、これらのメソッドはブランチ ノードの Menu クラスで宣言および実装されます。
    • 欠点は、リーフ コンポーネントとコンテナ コンポーネントには異なるメソッドがあり、コンテナ コンポーネント内のメンバ オブジェクトの管理に使用されるメソッドは抽象コンポーネント クラスで定義されていないため、透過性が十分ではないことです。そのため、クライアントは完全にプログラミングすることができません。抽象化であり、リーフ コンポーネントとコンテナ コンポーネントは別の方法で扱う必要があります。

複合パターンの利点:

  • 複合モードでは、階層的な複雑なオブジェクトを明確に定義して、オブジェクトの階層のすべてまたは一部を表現できるため、クライアントは階層の違いを無視でき、階層全体の制御が容易になります。
  • クライアントは、単一のオブジェクトを扱うか複合構造全体を扱うかに関係なく、複合構造またはその中の単一オブジェクトを一貫して使用できるため、クライアント コードが簡素化されます。
  • 「開閉原則」に準拠した既存のクラスライブラリを変更せずに、結合モードで新しいブランチノードとリーフノードを追加できるのは非常に便利です。
  • 結合モードは、オブジェクト指向でツリー構造を実現するための柔軟なソリューションを提供し、リーフ ノードとブランチ ノードを再帰的に組み合わせることで、複雑なツリー構造を形成できますが、ツリー構造の制御は非常に簡単です。

利用シナリオ: ツリー構造に応じて結合モードが生まれるため、ツリー構造が現れるのが結合モードの利用シナリオです。例: ファイル ディレクトリ表示、マルチレベル ディレクトリ表示、その他のツリー構造データ操作。

7: フライ級モード

フライウェイト モード: 共有テクノロジを使用して、多数のきめ細かいオブジェクトの再利用を効果的にサポートします。既存のオブジェクトを共有することで作成する必要があるオブジェクトの数が大幅に減り、多数の同様のオブジェクトによるオーバーヘッドが回避されるため、システム リソースの使用率が向上します。

フライウェイト モードには以下の 2 つの状態があり、アプリケーション内で 2 つの状態を区別し、外部状態を外部化するのがフライウェイト モードの実装です。

  • 内部状態。環境が変化しても変わらない共有可能な部分。
  • 外部状態とは、環境の変化に応じて変化する共有不可能な部分を指します。

フライウェイトモードには主に以下の役割があります。

  • 抽象フライウェイト ロール (Flyweight): 通常は、抽象フライウェイト クラス内の特定のフライウェイト クラスのパブリック メソッドを宣言するインターフェイスまたは抽象クラスです。これらのメソッドは、フライウェイト オブジェクトの内部データ (内部状態) を外部の世界に提供できます。 、同時にこれらのメソッドを介して外部データ (外部状態) を設定することもできます。
  • 具象フライウェイトの役割: フライウェイト オブジェクトと呼ばれる抽象フライウェイト クラスを実装し、具象フライウェイト クラスの内部状態に格納スペースを提供します。通常、シングルトン モードを組み合わせて特定のフライウェイト クラスを設計し、特定のフライウェイト クラスごとに固有のフライウェイト オブジェクトを提供できます。
  • 共有不可能なフライウェイトの役割: 抽象フライウェイト クラスのすべてのサブクラスを共有する必要はなく、共有できないサブクラスは非共有の具体的なフライウェイト クラスとして設計できます。非共有の具体的なフライウェイト クラスが必要な場合、オブジェクトは次の方法で直接作成できます。インスタンス化。
  • Flyweight Factory ロール: Flyweight ロールの作成と管理を担当します。顧客オブジェクトがフライウェイト オブジェクトを要求すると、フライウェイト ファクトリはシステム内に要件を満たすフライウェイト オブジェクトがあるかどうかを確認し、存在する場合はクライアントに提供され、存在しない場合は新しいフライウェイト オブジェクトが提供されます。が作成されます。

以下は質問の例です: 下の図は、よく知られているテトリス ゲームの各ブロックを示しています。テトリス ゲームでは、それぞれの異なるブロックがインスタンス オブジェクトである場合、これらのオブジェクトは多くのメモリ領域を占有します。共有を使用しましょうメタパターンが実装されています。

ここに画像の説明を挿入

コードは以下のように表示されます:

public class FlyweightDemo {
    
    
    public static void main(String[] args) {
    
    
        BoxFactory instance = BoxFactory.getInstance();
        instance.getShape("I").display("灰色");
        instance.getShape("L").display("红色");
        instance.getShape("O").display("绿色");
    }
}

// 抽象享元角色
abstract class AbstractBox {
    
    
    // 获取图形的方法
    public abstract String getShape();

    // 显示图形及颜色
    public void display(String color) {
    
    
        System.out.println("方块形状:" + getShape() + ",颜色:" + color);
    }
}

// 具体享元——I图形类
class IBox extends AbstractBox {
    
    
    @Override
    public String getShape() {
    
    
        return "I";
    }
}

// 具体享元——L图形类
class LBox extends AbstractBox {
    
    
    @Override
    public String getShape() {
    
    
        return "L";
    }
}

// 具体享元——O图形类
class OBox extends AbstractBox {
    
    
    @Override
    public String getShape() {
    
    
        return "O";
    }
}

// 工厂类:将该类设置为单例
class BoxFactory {
    
    
    private HashMap<String, AbstractBox> map;
    private static BoxFactory factory = new BoxFactory();

    // 在构造方法中进行初始化操作
    private BoxFactory() {
    
    
        map = new HashMap<>();
        map.put("I", new IBox());
        map.put("L", new LBox());
        map.put("O", new OBox());
    }

    // 获取实例
    public static BoxFactory getInstance() {
    
    
        return factory;
    }

    // 根据名称获取图形对象
    public AbstractBox getShape(String name) {
    
    
        return map.get(name);
    }
}

フライウェイトモードの利点:

  • メモリ内の類似または同一のオブジェクトの数を大幅に減らし、システム リソースを節約し、システム パフォーマンスを向上させます。
  • フライウェイト モードの外部状態は比較的独立しており、内部状態には影響を与えません。

フライ級モードの欠点:

  • オブジェクトを共有可能にするには、フライウェイトオブジェクトの状態の一部を外部化し、内部状態と外部状態を分離し、プログラムロジックを複雑にする必要がある

使用するシーン:

  • システムには同一または類似のオブジェクトが多数あるため、大量のメモリが消費されます。
  • オブジェクトの状態のほとんどは外部化でき、これらの外部状態をオブジェクトに渡すことができます。
  • Flyweight モードを使用する場合、Flyweight オブジェクトを保存するための Flyweight プールを維持する必要があり、特定のシステム リソースを消費するため、Flyweight オブジェクトを何度も再利用する必要がある場合には Flyweight モードを使用する価値があります。

おすすめ

転載: blog.csdn.net/tongkongyu/article/details/127703517
おすすめ