1. 構造設計パターンの概要と分類
構造設計パターンとは、オブジェクトとクラス間の関係を設計するための一連の設計パターンを指します。彼らは通常、最適な柔軟性と保守性を実現するためにコードを編成する方法を扱います。
構造設計パターンは次のカテゴリに分類できます。
- アダプター パターン (アダプター パターン): クラスのインターフェイスをクライアントが必要とする別のインターフェイスに変換するために使用されます。アダプター パターンを使用すると、インターフェイスに互換性がないために連携できないクラスが連携できるようになります。
- ブリッジ パターン: 抽象部分を実装部分から分離し、相互に影響を与えることなく独立して変更できるようにするために使用されます。
- 複合パターン: オブジェクトをツリー構造に結合して「全体」階層を表すために使用されます。Composite パターンを使用すると、クライアントは複合オブジェクトだけでなく個々のオブジェクトも処理できるようになります。
- デコレーター パターン: オブジェクト インターフェイスを変更せずに、追加の責任をオブジェクトに動的に追加するために使用されます。デコレータ パターンは、サブクラスを生成するよりも柔軟で、拡張が簡単です。
- ファサード パターン: クライアントが簡単に使用できるように、複雑なサブシステムにシンプルなインターフェイスを提供します。
- Flyweight パターン: 多数のきめの細かいオブジェクトを共有することで、メモリ使用量とオブジェクト作成のオーバーヘッドを削減します。
- プロキシ パターン: このオブジェクトへのアクセスを制御するために、他のオブジェクトにプロキシを提供するために使用されます。
これらのパターンは組み合わせて使用できます。たとえば、アダプター パターンとプロキシ パターンのどちらかを選択して、必要に応じてプロキシを動的に追加または削除できます。あるいは、デコレーター パターンとファサード パターンのどちらかを選択して、必要のない複雑さの一部を隠すこともできます。
2.アダプターモード
アダプター パターンは、インターフェイスをクライアントが期待する別のインターフェイスに変換できるようにする構造設計パターンです。これは、互換性のない 2 つのインターフェイス間の問題を解決したり、古いインターフェイスを新しいインターフェイスに適応させたりするためによく使用されます。
アダプター モードでは、アダプターは中間層として機能し、クライアントのリクエストを適応されたオブジェクトに渡し、適応されたオブジェクトの応答をクライアントに返します。アダプターは本質的に、クライアントが必要とするインターフェースを実装し、クライアントの要求をアダプター オブジェクトが理解できる形式に変換するラッパーです。
アダプター パターンには、クラス アダプターとオブジェクト アダプターという 2 つの一般的な実装があります。クラス アダプタは多重継承を使用して 2 つの異なるインターフェイスに適応しますが、オブジェクト アダプタは、適応されるオブジェクトのインスタンスを含めることによってインターフェイス変換を実装します。
アダプターのパターンは、実際のアプリケーションでは非常に広範囲にわたっています。たとえば、多くのソフトウェアには、新しいバージョンでは廃止された古い API に基づくコードが含まれています。この場合、アダプター パターンを使用して古い API を新しい API に適応させ、コードの互換性を維持できます。さらに、アダプター モードは、さまざまなデータベース API を統一インターフェイスに適合させるためによく使用され、開発者がさまざまなデータベースを簡単に切り替えることができます。
以下は、外部ライブラリのクラスがあるが、そのインターフェイスがニーズを満たしていないことを前提としたアダプター パターンのサンプル コードです。必要なインターフェイスを実装し、外部ライブラリ クラスのオブジェクトをメンバー変数として持つアダプター クラスを作成できます。アダプター クラスのメソッドは、外部ライブラリ クラスのメソッドを呼び出し、その結果を適切なインターフェイスに変換します。
// 外部库的类,其接口不符合我们的需求
public class ExternalLibraryClass {
public void doSomethingComplicated() {
// ...
}
}
// 我们需要一个能够调用doSomethingComplicated()方法的接口
public interface ComplicatedInterface {
void doSomething();
}
// 适配器类,将ExternalLibraryClass适配为ComplicatedInterface
public class Adapter implements ComplicatedInterface {
private ExternalLibraryClass externalLibrary;
public Adapter(ExternalLibraryClass externalLibrary) {
this.externalLibrary = externalLibrary;
}
@Override
public void doSomething() {
externalLibrary.doSomethingComplicated();
}
}
// 使用适配器来调用外部库的方法
public class Client {
public static void main(String[] args) {
ExternalLibraryClass externalLibrary = new ExternalLibraryClass();
Adapter adapter = new Adapter(externalLibrary);
adapter.doSomething();
}
}
3. ブリッジモード
ブリッジ パターンは、大規模なクラスまたは密接に関連した一連のクラスを、抽象化と実装の 2 つの別個の階層に分割し、2 つのレベルで独立して変更できるようにする構造設計パターンです。
ブリッジ パターンの中心的な考え方は、抽象化と実装を分離して、相互に影響を与えることなく独立して変更できるようにすることです。ブリッジパターンでは通常、抽象部で何らかの操作を定義し、実装部でこれらの操作を実現します。両方の部分は、相互に影響を与えることなく独立して拡張できます。
以下は、Shape インターフェイスと DrawAPI インターフェイスを実装するブリッジ モードの簡単なサンプル コードです。Shape インターフェイスが抽象部分で、DrawAPI インターフェイスが実装部分です。Circle や Rectangle などの具象グラフィックス クラスは Shape インターフェイスを継承し、DrawAPI インターフェイスのメソッドを呼び出して描画できます。
interface Shape {
void draw();
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Rectangle");
}
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Circle");
}
}
abstract class ShapeColor {
protected Shape shape;
public ShapeColor(Shape shape) {
this.shape = shape;
}
abstract public void drawWithColor(String color);
}
class RedShapeColor extends ShapeColor {
public RedShapeColor(Shape shape) {
super(shape);
}
public void drawWithColor(String color) {
System.out.println(color + " ");
shape.draw();
}
}
public class Main {
public static void main(String[] args) {
Shape rectangle = new Rectangle();
Shape circle = new Circle();
ShapeColor redRectangle = new RedShapeColor(rectangle);
ShapeColor redCircle = new RedShapeColor(circle);
redRectangle.drawWithColor("Red");
redCircle.drawWithColor("Red");
}
}
この例では、インターフェイスShape
と2 つの実装クラスRectangle
と がありますCircle
。次に、オブジェクトShapeColor
を保持し、抽象メソッドを定義する抽象クラスを作成します。また、メソッドを継承し、メソッドを実装クラスも定義します。このメソッドでは、色を出力し、渡されたオブジェクトのメソッドを呼び出します。Shape
drawWithColor
RedShapeColor
ShapeColor
drawWithColor
Shape
draw
Main
class では、 Rectangle
object とCircle
objectを作成し、これら 2 つのオブジェクトを使用してRedShapeColor
オブジェクトを作成します。最後に、drawWithColor
メソッドを、色のパラメータを渡します。
実際には、ブリッジ パターンを使用して抽象化をその実装から分離し、それらを独立して変更できるようにすることができます。一般的な例は、さまざまな種類のデータベース (Oracle、MySQL など) をサポートするために使用されるデータ アクセス フレームワークです。この場合、ブリッジ パターンを使用して、データベース アクセス インターフェイス (JDBC など) をさまざまなタイプのデータベースの実装から分離できます。これにより、クライアント コードを変更せずに実装を変更することで、新しいデータベース タイプをサポートできるようになります。
4. コンビネーションモード
複合パターンは、オブジェクトをツリー構造に編成して部分全体の階層を表現できる構造設計パターンです。複合パターンは、クライアントが単一のオブジェクトとオブジェクトの組み合わせを均一に処理できるようにすることで、クライアント コードを簡素化します。
複合スキーマには、複合オブジェクトとリーフ オブジェクトという 2 種類のオブジェクトが含まれます。コンポジション オブジェクトには、他のコンポジション オブジェクトやリーフ オブジェクトを含めることができます。リーフ オブジェクトは最も基本的なオブジェクトであり、他のオブジェクトを含めることはできません。これらのオブジェクトはツリー状の構造を形成できます。複合オブジェクトは非リーフ ノードであり、リーフ オブジェクトはツリーの終了ノードです。
単純な複合パターンの例を次に示します。
// 定义抽象组件类
abstract class Component {
public abstract function operation();
}
// 定义叶子节点类
class Leaf extends Component {
public function operation() {
// 叶子节点操作
}
}
// 定义组合节点类
class Composite extends Component {
private $children = array();
public function add(Component $component) {
$this->children[] = $component;
}
public function remove(Component $component) {
// 从 $children 数组中删除指定组件
}
public function operation() {
// 组合节点操作
foreach ($this->children as $child) {
$child->operation();
}
}
}
上の例では、Component
クラスは複合パターン内の抽象コンポーネント クラスであり、操作を定義しますoperation()
。Leaf
このクラスは、operation()
メソッドを。Composite
このクラスは複合ノード クラスであり、子ノードを格納するための配列と$children
、子ノードを追加および削除するためのメソッドが含まれています。このクラスは、すべての子ノードのメソッドを再帰的に呼び出すメソッドも実装します。add()
remove()
Composite
operation()
operation()
複合パターンは、次のような多くのシナリオに適用できます。
- ウィンドウ、ボタン、テキスト ボックスなどのグラフィカル ユーザー インターフェイス (GUI) コンポーネント。ネストして組み合わせて、より複雑な GUI 要素を作成できます。
- ディレクトリとファイルをツリー構造に編成できるファイル システム。
- 組織構造。会社を部門や従業員などを含むツリー構造に編成できます。
要約すると、合成パターンにより、オブジェクトの合成を処理するコードが簡素化され、変化するニーズに適応するためのより柔軟な設計が提供されます。
5. デコレータパターン
デコレータ パターンは、元のオブジェクトを変更せずにオブジェクトに動作を動的に追加できる構造設計パターンです。このパターンは、元のオブジェクトの周囲に 1 つ以上のデコレータ オブジェクトをラップするラッパー オブジェクトを作成することによって実装されます。このようにして、元のオブジェクトを変更せずに、動作を動的に追加、変更、または削除できます。
デコレータ パターンには、次の 4 つの中心的な役割があります。
-
抽象コンポーネント (コンポーネント): これらのオブジェクトに責任や動作を動的に追加できるオブジェクト インターフェイスを定義します。
-
具象コンポーネント: 抽象コンポーネント インターフェイス、つまり動作を追加する特定のオブジェクトを実装します。
-
抽象デコレータ (Decorator): 抽象コンポーネント インターフェイスを継承または実装し、抽象コンポーネントへの参照を含みます。
-
具体的なデコレータ: 抽象デコレータ インターフェイスを実装し、追加される動作を具体的に実装します。
以下は、一杯のコーヒーの例を実装するための単純なデコレータ パターンの例です。
// 抽象构件
interface Coffee {
String getDescription();
double getCost();
}
// 具体构件
class Espresso implements Coffee {
@Override
public String getDescription() {
return "Espresso";
}
@Override
public double getCost() {
return 1.99;
}
}
// 抽象装饰器
abstract class CoffeeDecorator implements Coffee {
private final Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription();
}
@Override
public double getCost() {
return coffee.getCost();
}
}
// 具体装饰器
class Mocha extends CoffeeDecorator {
public Mocha(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Mocha";
}
@Override
public double getCost() {
return super.getCost() + 0.50;
}
}
class Whip extends CoffeeDecorator {
public Whip(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Whip";
}
@Override
public double getCost() {
return super.getCost() + 0.30;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Coffee espresso = new Espresso();
System.out.println(espresso.getDescription() + " $" + espresso
アプリケーションシナリオ:
-
ログ: 元のビジネス ロジックを変更しないことを前提として、システム内の一部の操作をログに記録し、その後のトラブルシューティングとデータ統計を容易にします。
-
ダイナミックプロキシ: 元のコード構造を変更せずに、プロキシクラスを通じてアクセスを制御します。たとえば、Spring の AOP (アスペクト指向プログラミング) はダイナミックプロキシを通じて実装されます。
-
キャッシュ: システム内の特定の操作が頻繁に呼び出される場合、デコレータ モードを使用してキャッシュを実装し、システムのパフォーマンスを向上させることができます。
-
アクセス制御: アクセス制御はデコレータ モードを通じて実装され、元のコード構造を変更せずに特定の操作に対してアクセス制御を実行できます。
6. プロキシモード
プロキシ パターンは、プロキシ オブジェクトを提供することで、別のオブジェクトへのアクセスを制御できるようにする構造的な設計パターンです。プロキシ パターンを使用すると、オブジェクトへのアクセスを制限したり、オブジェクトに追加機能を提供したり、オブジェクトの実装の詳細を隠したりすることができます。
プロキシ パターンには、クライアント、プロキシ オブジェクト、実際のオブジェクトという 3 つの主要なアクターがあります。クライアントはプロキシ オブジェクトを通じて実際のオブジェクトにアクセスし、プロキシ オブジェクトはクライアントのリクエストを処理し、必要に応じてリクエストを実際のオブジェクトに渡す役割を果たします。
プロキシ パターンの簡単な例を次に示します。
interface Image {
void display();
}
class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName) {
System.out.println("Loading " + fileName);
}
}
class ImageProxy implements Image {
private RealImage realImage;
private String fileName;
public ImageProxy(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
}
public class Main {
public static void main(String[] args) {
Image image = new ImageProxy("test.jpg");
image.display();
}
}
上の例では、Image
インターフェイスとその 2 つの実装クラス (RealImage
と )を定義しましたImageProxy
。RealImage
は実際のイメージを表しImageProxy
、RealImage
のプロキシです。クライアントが 経由でImageProxy
アクセスするRealImage
、ImageProxy
最初にRealImage
画像が既に存在するか、存在しない場合は作成してから、 のメソッドRealImage
を呼び出して画像を表示します。display
プロキシ パターンは実際のアプリケーションで多くの用途に使用できます。そのうちのいくつかは次のとおりです。
- リモート プロキシ: リモート オブジェクトをプロキシ オブジェクトにカプセル化して、クライアントがローカル オブジェクトであるかのようにアクセスできるようにします。
- 仮想プロキシ: 実際に必要になるまで、オブジェクトのロードのオーバーヘッドを延期します。
- セキュリティ プロキシ: オブジェクトへのアクセスを制御して、許可されたユーザーのみがオブジェクトにアクセスできるようにします。
Java では、プロキシ パターンが広く使用されています。たとえば、Spring フレームワークでは、AOP (アスペクト指向プログラミング) はプロキシ モードに基づいた実装です。AOP はメソッド呼び出しをインターセプトし、ロギング、パフォーマンス監視、セキュリティ チェックなどの特定の操作をプロキシ経由で実行できます。