デザインパターン_構造パターン-《デコレータパターン》
ダークホースプログラマによるJavaデザインパターンの詳細解説、23のJavaデザインパターン(図表+フレームワークソースコード解析+実戦)をノートとしてまとめています。
概要
ファーストフードレストランの例から始めましょう。
ファストフード店では、焼きそばやチャーハンなどのファストフードを提供していますが、卵やハム、ベーコンなどのおかずを追加することができます。もちろん、追加のおかずは有料です。通常、各おかずの価格は異なりますが、したがって、合計金額を計算するのはさらに面倒になります。
継承の使用に関する問題:
-
スケーラビリティが低い
別の材料 (ハム ソーセージ) を追加したい場合は、FriedRice と FriedNoodles のサブクラスをそれぞれ定義する必要があることがわかります。ファーストフード カテゴリ (焼きビーフン) を追加する場合は、さらにサブカテゴリを定義する必要があります。
-
サブクラスが多すぎます
意味
- 既存のオブジェクトの構造を変更せずに、オブジェクトに動的に何らかの役割を追加する(つまり機能を追加する)方式を指します。
構造
デコレータ パターンの役割:
- 抽象コンポーネント (コンポーネント) の役割: 追加の責任を受け入れる準備ができているオブジェクトを標準化するための抽象インターフェイスを定義します。
- 具体的なコンポーネントの役割: 抽象コンポーネントを実装し、役割を装飾することでそれにいくつかの責任を追加します。
- 抽象装飾 (デコレータ) の役割: 抽象コンポーネントを継承または実装し、具象コンポーネントのインスタンスを含み、そのサブクラスを通じて具象コンポーネントの機能を拡張できます。このキャラクターは です
装饰者
。 - 具体的なデコレータの役割: 抽象的な装飾の関連メソッドを実装し、具体的なコンポーネント オブジェクトに追加の責任を追加します。
ケース
デコレーター パターンを使用してファーストフード レストランのケースを改善し、デコレーター パターンの本質を体験します。
クラス図は次のとおりです。
- FastFood は抽象的なコンポーネントの役割です。
- FriedRice (チャーハン) と FriedNoodles (焼きそば) は、特定のコンポーネントの役割です。
- ガーニッシュ(食材)は最も重要な抽象的な装飾の役割であり、
装饰者
ファストフードを継承すると同時にファストフードを集約します。 - エッグ(卵)とベーコン(ベーコン)は特定の装飾的な役割です。
コードは以下のように表示されます:
-
抽象コンポーネントの役割とスナックのインターフェイス
public abstract class FastFood { private float price; // 价格 private String desc; // 描述 public FastFood() { } public FastFood(float price, String desc) { this.price = price; this.desc = desc; } public void setPrice(float price) { this.price = price; } public float getPrice() { return price; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public abstract float cost(); // 获取价格 }
-
具体的な成分の役割 - チャーハン
public class FriedRice extends FastFood { public FriedRice() { super(10, "炒饭"); } public float cost() { return getPrice(); } }
-
特定成分の役割 - 焼きそば
public class FriedNoodles extends FastFood { public FriedNoodles() { super(12, "炒面"); } public float cost() { return getPrice(); } }
-
抽象装飾ロール (デコレータークラス) - コンポーネントクラス
public 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; } }
-
特定の装飾の役割 - 卵のトッピング
public class Egg extends Garnish { public Egg(FastFood fastFood) { super(fastFood, 1, "鸡蛋"); } public float cost() { return getPrice() + getFastFood().getPrice(); } @Override public String getDesc() { return super.getDesc() + getFastFood().getDesc(); } }
-
特定の装飾の役割 - ベーコンのトッピング
public class Bacon extends Garnish { public Bacon(FastFood fastFood) { super(fastFood, 2, "培根"); } @Override public float cost() { return getPrice() + getFastFood().getPrice(); } @Override public String getDesc() { return super.getDesc() + getFastFood().getDesc(); } }
-
テストクラス
public class Client { public static void main(String[] args) { // 点一份炒饭 FastFood food = new FriedRice(); // 花费的价格 System.out.println(food.getDesc() + " " + food.cost() + "元"); System.out.println("========"); // 点一份加鸡蛋的炒饭 FastFood food1 = new FriedRice(); food1 = new Egg(food1); // 花费的价格 System.out.println(food1.getDesc() + " " + food1.cost() + "元"); System.out.println("========"); // 点一份加培根的炒面 FastFood food2 = new FriedNoodles(); food2 = new Bacon(food2); // 花费的价格 System.out.println(food2.getDesc() + " " + food2.cost() + "元"); } }
出力
炒饭 10.0元 ======== 鸡蛋炒饭 11.0元 ======== 培根炒面 14.0元
利点
-
デコレータ パターンは、継承よりも柔軟な拡張機能を実現でき、使いやすく、さまざまなデコレータ オブジェクトを組み合わせることで、異なる動作状態で多様な結果を得ることができます。デコレータ パターンは継承よりも拡張可能であり、オープンとクローズの原則に従います。継承は静的な追加責任であり、デコレータは動的な追加責任です。
-
デコレータ モードは、継承の代替モードであり、実装クラスの機能を動的に拡張できます。
使用するシーン
- 継承によってシステムの拡張ができない場合、または継承によってシステムの拡張や保守が困難な場合。
- 継承を使用できない状況には、主に 2 つのカテゴリがあります。
- 1 つ目のカテゴリは、システム内に多数の独立した拡張機能があり、それぞれの組み合わせをサポートするために多数のサブカテゴリが生成され、サブカテゴリの数が爆発的に増加することです。
- 2 番目のカテゴリは、クラス定義 (最終クラスなど) を継承できないためです。
- 継承を使用できない状況には、主に 2 つのカテゴリがあります。
- 他のオブジェクトに影響を与えることなく、動的かつ透過的に個々のオブジェクトに責任を追加します。
- オブジェクトの機能要件を動的に追加したり、動的に取り消したりできる場合。
要約する
- デコレーターデザインパターンの利点
- 装飾パターンと継承関係の目的は両方とも、オブジェクトの機能を拡張することです
装饰模式可以提供⽐继承更多的灵活性
。 - さまざまな特定の装飾クラスと、これらの装飾クラスの順列と組み合わせを使用すると、さまざまな動作の多くの組み合わせを作成できます。元のコードを変更する必要はなく、「開始と終了の原則」に準拠します。
- 装飾パターンと継承関係の目的は両方とも、オブジェクトの機能を拡張することです
- デコレータ設計パターンの欠点
- デコレーションモードは多くのサブクラスを追加するため、過度に使用するとプログラムが非常に複雑になります(多層パッケージ化)。
- システムが複雑になり、学習と理解が難しくなります。
JDKソースコード解析 - IOストリーム
IO ストリームのラッパー クラスは、デコレータ パターン、BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter を使用します。
BufferedWriter の使用方法を説明するために、BufferedWriter を例に挙げてみましょう。
public class Demo {
public static void main(String[] args) throws Exception {
// 创建BufferedWriter对象
// 创建FileWriter对象
FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt");
BufferedWriter bw = new BufferedWriter(fw);
// 写数据
bw.write("hello Buffered");
bw.close();
}
}
実際に使用するとデコレータ パターンのように感じられます。その構造を見てみましょう。
BufferedWriterはWriterを継承し、同時にWriterを集約するデコレータパターンです。
まとめ:
BufferedWriter は、デコレータ パターンを使用して Writer サブ実装クラスを強化し、バッファ バッファを追加してデータ書き込みの効率を向上させます。
プロキシモードとデコレータモードの違い
静的プロキシ モードとデコレータ モードの違いは次のとおりです。
- 同じ点
- ターゲットクラスと同じビジネスインターフェースを実装する必要がある
- 両方のクラスでターゲット オブジェクトを宣言します
- ターゲットクラスを変更せずにターゲットメソッドを拡張できる
- 違い
- 目的が違う
- デコレータは対象オブジェクトを強化するためのものです
- 静的プロキシは対象オブジェクトを保護し、非表示にします。
- ターゲット オブジェクトのビルドを取得するためのさまざまな場所
- デコレーターは外部から渡され、構築メソッドを介して渡すことができます。
- 静的プロキシはプロキシ クラス内に作成され、ターゲット オブジェクトを非表示にします。
- 目的が違う