記事ディレクトリ
導入
デザイン パターンの起源は、オブジェクト指向プログラミングが普及した 1980 年代に遡ります。この期間中に、一部のソフトウェア開発者は、別のプロジェクトで同じ問題が発生していることに気づき始め、再利用可能な解決策を探し始めました。これらのソリューションはデザイン パターンと呼ばれます。デザイン パターンを最初に提案したのは、Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides で、1994 年に『デザイン パターン: 再利用可能なオブジェクト指向ソフトウェアの基礎』という本を出版しました。この本は、デザイン パターンの分野では古典となっています。それ以来、デザイン パターンはソフトウェア開発において非常に重要な概念となり、さまざまなプログラミング言語や開発フレームワークで広く使用されています。
4人の著者を総称してGOF(Gang of Four)と呼びます。彼らが提案した設計パターンは、主に以下のオブジェクト指向設計原則に基づいています。
- 実装ではなくインターフェイスに対するプログラム。
- 継承よりもオブジェクトの合成を優先します。
デザイン パターンは、一般的なソフトウェア デザインの問題に対する再利用可能なソリューションです。これらは、開発者がソフトウェア アプリケーションを設計するときによく遭遇する問題を解決する方法を提供します。デザイン パターンには、創造パターン、構造パターン、動作パターンなど、いくつかの種類があります。
- 作成パターンは、柔軟かつ効率的な方法でオブジェクトを作成するために使用されます。シングルトン モード、ファクトリ モード、アブストラクト ファクトリ モードが含まれます。
- 構造パターンは、クラスとオブジェクトを組み合わせてより大きな構造を形成するために使用されます。アダプターモード、ブリッジモード、デコレーターモードなどを含みます。
- 動作パターンは、クラスまたはオブジェクト間の通信と制御フローを処理するために使用されます。オブザーバー モード、ストラテジー モード、テンプレート メソッド モードを含みます。
デザイン パターンはソフトウェア開発において非常に便利なツールであり、コードの再利用性と保守性を向上させることができると同時に、開発者の生産性も向上させることができます。
デザインパターンの6つの原則
- 単一責任原則 (SRP): クラスが変更する理由は 1 つだけである必要があります。
- オープン クローズド原則 (OCP): ソフトウェア エンティティ (クラス、モジュール、関数など) は拡張に対してオープンであり、変更に対してクローズされる必要があります。
- リスコフ置換原則 (LSP): サブタイプはスーパータイプを置換できなければなりません。
- 依存性反転原則 (DIP): 高レベルのモジュールは低レベルのモジュールに依存すべきではなく、両方とも抽象インターフェイスに依存する必要があります。抽象インターフェイスは具体的な実装に依存すべきではなく、具体的な実装は抽象インターフェイスに依存する必要があります。
- インターフェイス分離原則 (ISP): クラスは、必要のないインターフェイスの実装を強制されるべきではありません。クライアントが関心のある部分だけを知る必要があるように、インターフェイスはより小さく、より具体的な部分に分割される必要があります。
- デメテルの法則 (LOD): オブジェクトは他のオブジェクトについてできる限り知っていなければなりません。これは、「最小知識の原則」と呼ばれることがよくあります。
1.クリエーションモード
1. シングルトンパターン
シングルトン パターン (Singleton Pattern) は、Java の最も単純な設計パターンの 1 つであり、次の特徴があります。
- シングルトン クラスはインスタンスを 1 つだけ持つことができます。
- シングルトン クラスは、独自の一意のインスタンスを作成する必要があります。
- シングルトン クラスは、このインスタンスを他のすべてのオブジェクトに提供する必要があります。
1) お腹を空かせた中華風
この方法の方が一般的に使用されますが、ゴミオブジェクトが生成されやすい
利点: ロックがないため、実行効率が向上します。
欠点: クラスのロード時に初期化され、メモリが無駄に消費されます。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
2) 怠惰なスタイル、ダブルチェックロック
スレッドの安全性とパフォーマンスを確保するために、怠け者に基づいてダブルチェック ロックを追加します。
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
3) 静的内部クラス
静的内部クラスを使用して遅延シングルトン パターンを実装し、スレッドの安全性とパフォーマンスを確保します。この方法はダブルチェックロック方法と同じ効果を達成できますが、実装はより簡単です。
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
4) 列挙
列挙を使用してシングルトン モードを実装し、スレッドの安全性を確保し、リフレクション攻撃を防ぎます。
1) 既存のクラスを列挙型シングルトンに変換する
PersonEnum.INSTANCE.getInstance()
@Data
public class Person {
private String name;
private int age;
}
public enum PersonEnum {
INSTANCE;
private Person instance;
private PersonEnum(){
instance = new Person();
}
public Person getInstance() {
return instance;
}
}
2) 新しい列挙型クラスを直接作成する
PersonSingleton.INSTANCE
public enum PersonSingleton {
INSTANCE;
private String name;
public String getName() {
return name;
}
}
2. 試作パターン
プロトタイプ デザイン パターンでは、クラスをインスタンス化する代わりに、既存のオブジェクトをコピーすることで新しいオブジェクトを作成できます。
類似したオブジェクトを多数作成する必要がある場合に非常に便利です。オブジェクトの繰り返し作成を回避できるため、パフォーマンスが向上し、必要に応じて浅いコピーまたは深いコピーを実装できます。
Java では、プロトタイプ パターンの実装には通常、Cloneable インターフェイスの実装と clone() メソッドのオーバーライドが含まれます。
public abstract class Shape implements Cloneable {
private String id;
protected String type;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public abstract void draw();
@Override
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
public class Circle extends Shape {
public Circle() {
type = "Circle";
}
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
public class Square extends Shape {
public Square() {
type = "Square";
}
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public class ShapeCache {
private static Map<String, Shape> shapeMap = new HashMap<>();
public static Shape getShape(String shapeId) {
Shape cachedShape = shapeMap.get(shapeId);
return (Shape) cachedShape.clone();
}
// For each shape run database query and create shape
// shapeMap.put(shapeKey, shape);
// for example, we are adding three shapes
public static void loadCache() {
Circle circle = new Circle();
circle.setId("1");
shapeMap.put(circle.getId(), circle);
Square square = new Square();
square.setId("2");
shapeMap.put(square.getId(), square);
}
}
public class PrototypePatternDemo {
public static void main(String[] args) {
ShapeCache.loadCache();
Shape clonedShape = ShapeCache.getShape("1");
System.out.println("Shape : " + clonedShape.getType());
Shape clonedShape2 = ShapeCache.getShape("2");
System.out.println("Shape : " + clonedShape2.getType());
}
}
上記のコードでは、Shape は Cloneable インターフェイスを実装し、 clone() メソッドをオーバーライドする抽象クラスです。Circle と Square は、draw() メソッドを実装する Shape の具象サブクラスです。ShapeCache クラスは、Shape オブジェクトのコピーを保存するキャッシュです。PrototypePatternDemo クラスは、ShapeCache を使用して Shape オブジェクトのコピーを取得するデモ クラスです。
loadCache() メソッドでは、Shape オブジェクトのコピーを 2 つ作成し、shapeMap に保存します。main() メソッドでは、getShape() メソッドを使用して Shape オブジェクトのコピーを取得し、その型を出力します。プロトタイプ パターンを使用しているため、クラスをインスタンス化せずに、既存のオブジェクトをコピーすることで新しいオブジェクトを作成できます。
3. ファクトリーパターン
オブジェクト作成ロジックをクライアントに直接公開せずに、ファクトリ クラスを通じてオブジェクトを作成します。
シンプルなファクトリ モデルの利点は、クライアントが特定の製品クラスの作成の詳細を知る必要がなく、ファクトリ クラスを通じてオブジェクトを作成するだけでよく、ファクトリ クラスがクライアントのニーズに応じてさまざまなタイプのオブジェクトを動的に作成できることです。ただし、デメリットも明らかで、作成するプロダクトクラスが多いと、ファクトリクラスのコードが肥大化し、メンテナンスが困難になります。
abstract class Animal {
public abstract void sound();
}
class Cat extends Animal {
@Override
public void sound() {
System.out.println("喵喵喵");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("汪汪汪");
}
}
// 创建一个工厂类
class AnimalFactory {
// 定义一个静态方法,根据传入的参数创建具体的产品类对象
public static Animal createAnimal(String type) {
if (type.equalsIgnoreCase("dog")) {
return new Dog();
} else if (type.equalsIgnoreCase("cat")) {
return new Cat();
} else {
throw new IllegalArgumentException("Invalid animal type: " + type);
}
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
// 使用工厂类创建不同的 Animal 对象
Animal dog = AnimalFactory.createAnimal("dog");
dog.sound();
Animal cat = AnimalFactory.createAnimal("cat");
cat.sound();
}
}
4. 抽象的なファクトリーパターン
オブジェクトは、それを作成するためのインターフェイスを定義することによって作成されますが、実装の決定はサブクラスに委ねられます。
Abstract Factory パターンでは、インターフェイスは、クラスを明示的に指定せずに関連オブジェクトを作成する役割を担うファクトリです。生成された各ファクトリは、ファクトリ パターンに従ってオブジェクトを提供できます。
// 创建一个抽象产品类
abstract class Animal {
public abstract void sound();
}
class Cat extends Animal {
@Override
public void sound() {
System.out.println("喵喵喵");
}
}
// 创建具体产品类,继承自 Animal 类
class Dog extends Animal {
@Override
public void sound() {
System.out.println("汪汪汪");
}
}
abstract class AnimalFactory {
// 定义一个抽象方法,用于创建 Animal 对象
public abstract Animal createAnimal();
}
class CatFactory extends AnimalFactory {
@Override
public Animal createAnimal() {
return new Cat();
}
}
// 创建具体工厂类,实现创建 Animal 对象的接口
class DogFactory extends AnimalFactory {
@Override
public Animal createAnimal() {
return new Dog();
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
// 创建一个 Dog 对象
AnimalFactory dogFactory = new DogFactory();
Animal dog = dogFactory.createAnimal();
dog.sound();
// 创建一个 Cat 对象
AnimalFactory catFactory = new CatFactory();
Animal cat = catFactory.createAnimal();
cat.sound();
}
}
5. ビルダーパターン
ビルダー パターンは、複雑なオブジェクトを段階的に構築することで、さまざまなタイプのオブジェクトを作成できる創造的なデザイン パターンです。ビルダー クラスを使用してオブジェクト作成プロセスをカプセル化し、それを単純なステップに分割します。これにより、これらの手順を変更することで、さまざまなタイプのオブジェクトを作成できます。
この例では、構築する複雑なオブジェクトとして Car クラスを作成します。次に、Car オブジェクトを段階的に構築し、最後にそれを返す CarBuilder クラスを作成しました。最後に、CarBuilder を使用して Car オブジェクトを構築します。
public class Car {
private String make;
private String model;
private int year;
private String engine;
private int seats;
public Car(String make, String model, int year, String engine, int seats) {
this.make = make;
this.model = model;
this.year = year;
this.engine = engine;
this.seats = seats;
}
// ... getter setter ... //
}
public class CarBuilder {
private String make;
private String model;
private int year;
private String engine;
private int seats;
public CarBuilder setMake(String make) {
this.make = make;
return this;
}
public CarBuilder setModel(String model) {
this.model = model;
return this;
}
public CarBuilder setYear(int year) {
this.year = year;
return this;
}
public CarBuilder setEngine(String engine) {
this.engine = engine;
return this;
}
public CarBuilder setSeats(int seats) {
this.seats = seats;
return this;
}
public Car build() {
return new Car(make, model, year, engine, seats);
}
}
2. 構造モデル
1. アダプターのパターン
Java アダプター パターンは、互換性のないインターフェイス間の通信を可能にする構造設計パターンです。アダプター パターンは、あるクラスのインターフェイスをクライアントが期待する別のインターフェイスに変換することによってこれを行います。このパターンにより、既存のコードを変更せずに既存のクラスを再利用できます。
アダプター パターンは、既存のコードを変更せずに既存のクラスを再利用するのに役立ち、互換性のないインターフェイス間の通信を可能にします。
主な機能: アダプター モードは、クラスのインターフェイスを顧客が望む別のインターフェイスに変換し、インターフェイスの互換性がないために連携できなかったクラスが連携できるようにします。
次の例では、ターゲット インターフェイス Target、ソース インターフェイス Adapte、アダプター クラス Adaptor があります。アダプター クラスはターゲット インターフェイスを実装し、リクエストをソース インターフェイスの特定のリクエスト メソッドに転送します。クライアント コードはアダプター オブジェクトを作成し、それを使用してターゲット インターフェイスのメソッドを呼び出します。
// 源接口
public class Adaptee {
public void specificRequest() {
System.out.println("Adaptee's specific request");
}
}
public interface Target {
public void request();
}
// 适配器类
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target target = new Adapter(adaptee);
target.request();
}
}
2. 複合パターン
複合パターンは、オブジェクトをツリー構造に合成して「部分全体」の階層を表現できるようにする構造設計パターンです。このパターンにより、クライアントは個々のオブジェクトとオブジェクトの組み合わせを均一に扱うことができます。
複合モードには、リーフ ノードと複合ノードという 2 つの基本的なタイプのオブジェクトがあります。リーフ ノードはツリー構造内の単一のオブジェクトを表し、複合ノードはツリー構造内のオブジェクトのグループを表します。構成ノードには、他の構成ノードやリーフ ノードを含めることができ、再帰的なツリー構造を形成します。
以下は、Java を使用して結合モードを実装するサンプル コードです。このコードでは、抽象クラス Component を使用して、リーフ ノードや結合ノードなどのツリー構造内のノードを表します。複合ノードには子ノードが含まれており、子ノードは add() メソッドおよび delete() メソッドを通じて追加および削除できます。リーフ ノードには子ノードはありませんが、共通の操作メソッドを実装できます。特定結合ノードは、Componentクラスを継承し、独自の操作メソッドを実現します。
public interface IComponent {
void display();
}
// Component.java
public abstract class Component implements IComponent {
protected String name;
public Component(String name) {
this.name = name;
}
public abstract void add(IComponent component);
public abstract void remove(IComponent component);
}
// Composite.java
public class Composite extends Component {
private List<IComponent> children = new ArrayList<>();
public Composite(String name) {
super(name);
}
@Override
public void add(IComponent component) {
children.add(component);
}
@Override
public void remove(IComponent component) {
children.remove(component);
}
@Override
public void display() {
System.out.println("Composite: " + name);
for (IComponent component : children) {
component.display();
}
}
}
// Leaf.java
public class Leaf implements IComponent {
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void display() {
System.out.println("Leaf: " + name);
}
}
// Client.java
public class Client {
public static void main(String[] args) {
Component root = new Composite("root");
Component branch1 = new Composite("branch1");
Component branch2 = new Composite("branch2");
Component leaf1 = new Leaf("leaf1");
Component leaf2 = new Leaf("leaf2");
Component leaf3 = new Leaf("leaf3");
root.add(branch1);
root.add(branch2);
branch1.add(leaf1);
branch2.add(leaf2);
branch2.add(leaf3);
root.display();
}
}
3. デコレータパターン
デコレータ パターンは、同じクラスの他のオブジェクトの動作に影響を与えることなく、単一のオブジェクトに静的または動的に動作を追加できる構造設計パターンです。このパターンは、実行時にオブジェクトを追加または削除する機能が必要な場合、またはさまざまな動作の組み合わせを作成するために必要なサブクラスの数を減らしたい場合に便利です。
Java では、デコレータ パターンは継承と合成の組み合わせを使用して実装されます。具体的には、基本クラスまたはインターフェイスを作成してオブジェクトの核となる動作を定義し、次に 1 つ以上のデコレータ クラスを作成してオブジェクトに追加の動作を追加します。各デコレーター クラスには、装飾するオブジェクトへの参照があり、オブジェクトの元の動作に委任する前または後に、オブジェクトの動作を変更できます。
デコレータ パターンは、次のシナリオに適しています。
- 既存のコードを変更せずに、既存のクラスに新しい機能を追加します。
- 実行時にオブジェクトに新しい動作を動的に追加します。
- オブジェクトをさまざまな方法で組み合わせて、さまざまな動作を実現します。
デコレーター パターンを使用する場合は、次の点に注意してください。
- デコレータ クラスは、装飾されたオブジェクトをラップできるように、装飾されたオブジェクトと同じインターフェイスを実装する必要があります。
- デコレーター クラスは、装飾されたオブジェクトのメソッドを呼び出す前または後に、新しい動作を追加する必要があります。
- デコレータ オブジェクトを作成しすぎないでください。作成しないと、コードが複雑になり、保守が困難になります。
public interface Pizza {
public String getDescription();
public double getCost();
}
// 具体组件
public class PlainPizza implements Pizza {
public String getDescription() {
return "薄饼";
}
public double getCost() {
return 4.00;
}
}
// 装饰器
public abstract class ToppingDecorator implements Pizza {
protected Pizza pizza;
public ToppingDecorator(Pizza pizza) {
this.pizza = pizza;
}
public String getDescription() {
return pizza.getDescription();
}
public double getCost() {
return pizza.getCost();
}
}
// 具体装饰器
public class Cheese extends ToppingDecorator {
public Cheese(Pizza pizza) {
super(pizza);
}
public String getDescription() {
return pizza.getDescription() + ",马苏里拉奶酪";
}
public double getCost() {
return pizza.getCost() + 0.50;
}
}
// 具体装饰器
public class Pepperoni extends ToppingDecorator {
public Pepperoni(Pizza pizza) {
super(pizza);
}
public String getDescription() {
return pizza.getDescription() + ",意大利辣香肠";
}
public double getCost() {
return pizza.getCost() + 1.00;
}
}
// 客户端代码
public class PizzaShop {
public static void main(String[] args) {
Pizza pizza = new PlainPizza();
pizza = new Cheese(pizza);
pizza = new Pepperoni(pizza);
System.out.println(pizza.getDescription());
System.out.println("成本:$" + pizza.getCost());
}
}
この例では、説明やコストなど、Pizza の核となる動作を定義する Pizza インターフェイスがあります。次に、Pizza インターフェイスを実装する PlainPizza クラスを作成します。
次に、Pizza インターフェイスを実装し、装飾する Pizza オブジェクトへの参照を持つ抽象 ToppingDecorator クラスを作成します。これにより、元のオブジェクトを変更せずに、Pizza オブジェクトに追加の動作を追加できます。
最後に、Pizza オブジェクトに追加の動作を追加する 2 つの具象デコレータ クラス Cheese と Pepperoni を作成しました。各デコレータ クラスは getDescription() メソッドと getCost() メソッドを変更して、元のオブジェクトに委任する前または後に独自の動作を追加します。
クライアント コードでは、PlainPizza オブジェクトを作成し、それを Cheese オブジェクトと Pepperoni オブジェクトで装飾します。次に、Pizza の説明とコストを出力して、デコレーターが元のオブジェクトの動作を変更することを確認します。
4. ファサードパターン
ファサード パターンは、複雑なシステム内のサブシステムにアクセスするためのシンプルなインターフェイスを提供する構造設計パターンであり、サブシステムの複雑さを隠します。ファサード モードはオブジェクトベースのモードで、ファサード クラスを作成することでクライアントをサブシステムから切り離すため、クライアントは操作を完了するためにファサード クラスと対話するだけで済みます。
class CPU {
public void processData() {
System.out.println("正在处理数据...");
}
}
class Memory {
public void load() {
System.out.println("正在加载内存...");
}
}
class HardDrive {
public void readData() {
System.out.println("正在读取硬盘数据...");
}
}
// 外观类
class ComputerFacade {
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
public ComputerFacade() {
cpu = new CPU();
memory = new Memory();
hardDrive = new HardDrive();
}
public void start() {
System.out.println("启动计算机...");
cpu.processData();
memory.load();
hardDrive.readData();
System.out.println("计算机启动完毕!");
}
}
// 客户端代码
public class FacadePatternDemo {
public static void main(String[] args) {
ComputerFacade computer = new ComputerFacade();
computer.start();
}
}
この例では、 CPU 、 Memory 、および HardDrive という 3 つのサブシステム クラスがあります。各クラスは、データの処理、メモリのロード、ハードディスクからのデータの読み取りなど、システムの異なる機能を実行します。
次に、3 つのサブシステム クラスをカプセル化し、クライアントがコンピューターを起動するために使用できる単純な start() メソッドを提供する ComputerFacade クラスを作成しました。start() メソッドでは、各サブシステムのメソッドを正しい順序で呼び出して、コンピューターが正しく起動することを確認します。
最後に、クライアント コードで ComputerFacade オブジェクトを作成し、start() メソッドを使用してコンピューターを起動します。システムの複雑さはファサード クラスによって隠蔽されているため、クライアント コードは非常に単純です。
5. フライウェイトパターン
フライウェイト パターンは、オブジェクトを共有することでメモリ使用量とオブジェクト作成のオーバーヘッドを削減する構造設計パターンです。Flyweight パターンはオブジェクト タイプ パターンに属し、Flyweight ファクトリを作成して共有オブジェクトを管理し、必要に応じて既存のオブジェクトを返すことで、オブジェクトの作成と破棄の数を減らします。
interface Shape {
void draw();
}
// 具体享元类
class Circle implements Shape {
private String color;
private int x;
private int y;
private int radius;
public Circle(String color) {
this.color = color;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setRadius(int radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.println("画了一个" + color + "的圆,半径为" + radius + ",位置为(" + x + "," + y + ")");
}
}
// 享元工厂类
class ShapeFactory {
private static final Map<String, Shape> circleMap = new HashMap<>();
public static Shape getCircle(String color) {
Circle circle = (Circle) circleMap.get(color);
if (circle == null) {
circle = new Circle(color);
circleMap.put(color, circle);
System.out.println("创建了一个" + color + "的圆");
}
return circle;
}
}
// 客户端代码
public class FlyweightPatternDemo {
private static final String[] colors = {
"红色", "绿色", "蓝色", "黄色", "黑色" };
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
Circle circle = (Circle) ShapeFactory.getCircle(getRandomColor());
circle.setX(getRandomX());
circle.setY(getRandomY());
circle.setRadius(100);
circle.draw();
}
}
private static String getRandomColor() {
return colors[(int) (Math.random() * colors.length)];
}
private static int getRandomX() {
return (int) (Math.random() * 100);
}
private static int getRandomY() {
return (int) (Math.random() * 100);
}
}
この例では、図形を描画するためのdraw() メソッドを定義する Shape インターフェイスがあります。
次に、Shape インターフェイスを実装し、円の色、位置、半径などの情報を保存する特定のフライウェイト クラス Circle を作成しました。
次に、作成した円オブジェクトを保存するための Map を維持するフライウェイト ファクトリ クラス ShapeFactory を作成しました。クライアントが円を描画する必要がある場合、ShapeFactory を通じて既存の円オブジェクトを取得できます。存在しない場合は、新しい円オブジェクトを作成してマップに保存します。
最後に、クライアント コードで、それぞれランダムな色、位置、半径を持つ 20 個の円をランダムに生成します。ShapeFactory を通じて円オブジェクトを取得し、そのdraw() メソッドを呼び出して形状を描画します。同じ色の円オブジェクトが共有されるため、メモリ使用量とオブジェクト作成のオーバーヘッドが削減されます。
6. プロキシパターン
プロキシ パターンは、プロジェクトでよく使用されるデザイン パターンです。ターゲット オブジェクトに間接的にアクセスする方法、つまり、プロキシ オブジェクトを介してターゲット オブジェクトにアクセスする方法を提供します。
この利点は、元のターゲット オブジェクトを変更せずに、追加の拡張関数をターゲット オブジェクトに追加できることです。
プロキシ モードは、静的プロキシ、jdk ダイナミック プロキシ、および cglib ダイナミック プロキシの 3 つの実装方法に分かれています。
3 つの実装方法には、それぞれ独自の利点と適用可能なシナリオがあります。
- 静的プロキシ: プロキシ クラスは非常に明確である必要があるため、汎用的であることはできませんが、最も効率的でもあります。
- jdk ダイナミック プロキシ: インターフェイス プロキシに基づく必要がありますが、これには特定の制限があります。一般的なビジネス (パフォーマンス ログなど) に使用できるバイトコード ファイルを動的に生成します。
- cglig ダイナミック プロキシ: バイトコード ファイルも動的に生成され、生成されたプロキシ クラスはターゲット オブジェクトを継承します。
- Spring AOP のデフォルトのプロキシ戦略は次のとおりです。ターゲット オブジェクトがインターフェイスを実装している場合は、jdk 動的プロキシを使用し、それ以外の場合は cglib プロキシを使用します。
- jdk8 以降、jdk 動的プロキシの効率は cglib プロキシの効率よりも高くなります
1) 静的プロキシ
プロキシ オブジェクトとプロキシ オブジェクトは同じインターフェイスを実装するか、同じ親クラスを継承する必要があるため、インターフェイスまたは抽象クラスを定義する必要があります。
/**代理接口*/
public interface IHello {
String hi(String key);
}
/**代理接口实现类*/
public class HelloImpl implements IHello {
@Override
public String hi(String key) {
String str = "hello:" + key;
System.out.println("HelloImpl! " + str);
return str;
}
}
/**静态代理类*/
public class HelloStaticProxy implements IHello {
private IHello hello;
public HelloStaticProxy(IHello hello) {
this.hello = hello;
}
@Override
public String hi(String key) {
System.out.println(">>> static proxy start");
String result = hello.hi(key);
System.out.println(">>> static proxy end");
return result;
}
}
/**测试*/
public class DemoTest {
public static void main(String[] args) {
IHello helloProxy = new HelloStaticProxy(new HelloImpl());
helloProxy.hi("world");
}
}
2) jdk動的プロキシ
jdk ダイナミック プロキシはインターフェイス ベースのプロキシ メソッドであり、ターゲット オブジェクトはインターフェイスを実装する必要があります。
原則として、リフレクション メカニズムを使用して、Proxy クラスを継承し、プロキシされるインターフェイスを実装する匿名クラスが動的に生成されます。Java は多重継承をサポートしていないため、JDK 動的プロキシはクラスをプロキシできません。
/**代理接口*/
public interface IHello {
String hi(String key);
}
/**代理接口实现类*/
public class HelloImpl implements IHello {
@Override
public String hi(String key) {
String str = "hello:" + key;
System.out.println("HelloImpl! " + str);
return str;
}
}
/**jdk动态代理类*/
public class JdkProxy implements InvocationHandler {
private Object target;
public JdkProxy(Object target) {
this.target = target;
}
/**
* 获取被代理接口实例对象
*
* @param <T>
* @return
*/
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">>> JdkProxy start");
Object result = method.invoke(target, args);
System.out.println(">>> JdkProxy end");
return result;
}
}
/**测试*/
public class Demo2Test {
public static void main(String[] args) {
JdkProxy proxy = new JdkProxy(new HelloImpl());
IHello helloProxy = proxy.getProxy();
helloProxy.hi(" jdk proxy !");
}
}
3) cglib ダイナミックプロキシ
ターゲット オブジェクトはインターフェイスを実装する必要がなく、最終クラスのプロキシとして機能することはできません。
原則として、動的に生成されたクラスはターゲット オブジェクトを継承します。cglib を使用するには、対応する jar パッケージを導入する必要があります
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.7</version>
</dependency>
/**目标类*/
public class HelloImpl {
public String hi(String key) {
String str = "hello:" + key;
System.out.println("HelloImpl! " + str);
return str;
}
}
/**cglib代理类*/
public class CglibProxy implements InvocationHandler {
private Object target;
/**
* 获取被代理接口实例对象
*/
public <T> T getProxy() {
//1创建增强器对象
Enhancer e = new Enhancer();
//2设置增强器的类加载器
e.setClassLoader(target.getClass().getClassLoader());
//3设置代理对象父类类型
e.setSuperclass(target.getClass());
//4设置回调函数
e.setCallback(this);
//5创建代理对象
return (T) e.create();
}
public CglibProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">>> cglib start");
Object obj = method.invoke(target, args);
System.out.println(">>> cglib end");
return obj;
}
}
/**测试*/
public class Demo3Test {
public static void main(String[] args) {
HelloImpl hello = new HelloImpl();
CglibProxy cglibProxy = new CglibProxy(hello);
HelloImpl proxy = cglibProxy.getProxy();
proxy.hi(" cglib ");
}
}
7. ブリッジパターン
ブリッジ パターンは、抽象化と実装を分離する設計パターンです。カプセル化、集約、継承などの基本テクノロジーを使用して、独立して変化する 2 つの次元を抽象的な方法で橋渡しし、それによってそれらの間の結合度を減らし、システムをより柔軟にします。
以下は、Java でのブリッジ パターン実装のサンプル コードです。
// 首先,我们定义一个 Color 接口,它表示颜色:
public interface Color {
void applyColor();
}
// 然后,我们定义一个 Shape 抽象类,它包含了一个 Color 对象:
public abstract class Shape {
protected Color color;
public Shape(Color color) {
this.color = color;
}
public abstract void applyColor();
}
// 接下来,我们定义两个实现了 Color 接口的具体类:
public class Red implements Color {
@Override
public void applyColor() {
System.out.println("Applying red color");
}
}
public class Blue implements Color {
@Override
public void applyColor() {
System.out.println("Applying blue color");
}
}
// 最后,我们定义两个实现了 Shape 抽象类的具体类:
public class Circle extends Shape {
public Circle(Color color) {
super(color);
}
@Override
public void applyColor() {
System.out.print("Circle applying color: ");
color.applyColor();
}
}
public class Square extends Shape {
public Square(Color color) {
super(color);
}
@Override
public void applyColor() {
System.out.print("Square applying color: ");
color.applyColor();
}
}
// 现在,我们可以使用这些类来创建出对应的对象并调用它们的方法:
public class Test {
public static void main(String[] args) {
Color blue = new Blue();
Shape square = new Square(new Red());
Shape circle = new Circle(blue);
square.applyColor();
circle.applyColor();
}
}
出力は次のとおりです。
Square applying color: Applying red color
Circle applying color: Applying blue color
これは、Shape サブクラスに影響を与えることなく実行時に Shape クラスの色を動的に変更できるようにする単純なブリッジ パターンの実装です。また、他の既存のクラスを変更せずに新しい色と形状のクラスを追加することもできます。
3. 行動モデル
1. 責任連鎖のパターン
Chain of Responsibility パターンは、ハンドラーがリクエストを処理するまで、リクエストを処理チェーンに沿って渡すことを可能にする動作設計パターンです。Java で責任連鎖パターンを実装するには、通常、次の手順が必要です。
- リクエストを処理するメソッドを含むハンドラー インターフェイスを定義します。
- ハンドラー インターフェイスを実装し、次のハンドラーへの参照を含む抽象ハンドラー クラスを作成します。
- 抽象ハンドラー クラスを継承する具象ハンドラー クラスを作成し、リクエストを処理するためのメソッドを実装します。
- クライアント コードで、ハンドラーのチェーンを作成し、チェーン内の最初のハンドラーにリクエストを送信します。
以下は、責任連鎖パターンの単純な Java 実装です。
public interface Handler {
Handler setNextHandler(Handler nextHandler);
void handleRequest(Request request);
}
// 创建抽象处理器类
public abstract class AbstractHandler implements Handler {
private Handler nextHandler;
public Handler setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
return this.nextHandler;
}
public Handler getNextHandler() {
return nextHandler;
}
}
// 创建具体的处理器类
public class ConcreteHandler1 extends AbstractHandler {
public void handleRequest(Request request) {
if (request.getType().equals("Type1")) {
System.out.println("ConcreteHandler1 handles request " + request);
} else {
getNextHandler().handleRequest(request);
}
}
}
public class ConcreteHandler2 extends AbstractHandler {
public void handleRequest(Request request) {
if (request.getType().equals("Type2")) {
System.out.println("ConcreteHandler2 handles request " + request);
} else {
getNextHandler().handleRequest(request);
}
}
}
public class ConcreteHandler3 extends AbstractHandler {
public void handleRequest(Request request) {
if (request.getType().equals("Type3")) {
System.out.println("ConcreteHandler3 handles request " + request);
} else {
getNextHandler().handleRequest(request);
}
}
}
// 创建请求类
public class Request {
private String type;
public Request(String type) {
this.type = type;
}
public String getType() {
return type;
}
public String toString() {
return "Request [type=" + type + "]";
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
Handler handler3 = new ConcreteHandler3();
handler1.setNextHandler(handler2)
.setNextHandler(handler3);
handler1.handleRequest(new Request("Type1"));
handler1.handleRequest(new Request("Type2"));
handler1.handleRequest(new Request("Type3"));
}
}
結果を入力してください:
ConcreteHandler1 handles request Request [type=Type1]
ConcreteHandler2 handles request Request [type=Type2]
ConcreteHandler3 handles request Request [type=Type3]
上記のコードは、ハンドラーのチェーンを作成し、ハンドラーが処理するまでそのチェーンに沿ってリクエストを渡す方法を示しています。この例では、ConcreteHandler1、ConcreteHandler2、および ConcreteHandler3 は、AbstractHandler クラスを継承し、handleRequest メソッドを実装する具象ハンドラー クラスです。クライアント コードはハンドラーのチェーンを作成し、チェーン内の最初のハンドラーにリクエストを送信します。リクエストがプロセッサに到着すると、リクエストのタイプがプロセッサが処理できるタイプと一致するかどうかがチェックされます。「はい」の場合、ハンドラーはリクエストを処理します。それ以外の場合、ハンドラーが要求を処理するまで、チェーン内の次のハンドラーに要求を渡します。
2. コマンドパターン
コマンド パターンは、リクエストをオブジェクトとしてカプセル化できる動作設計パターンであり、さまざまなリクエストをさまざまなパラメータ、キューまたはログ リクエストでパラメータ化し、取り消し可能な操作をサポートできます。Java では、コマンド パターンの実装には通常、コマンド インターフェイスと 1 つ以上の具体的なコマンド クラスが含まれます。これらのクラスは、コマンド インターフェイスを実装し、execute() メソッドで実際のコマンド ロジックを定義します。さらに、コマンド パターンには、1 つまたは複数のコマンド オブジェクトを受信側オブジェクトに関連付け、必要に応じてそれらのexecute() メソッドを呼び出す起動側クラスが含まれる場合があります。コマンド パターンは、さまざまなアプリケーション シナリオで使用できる、非常に柔軟で拡張可能なパターンです。
interface Command {
void execute(String[] args);
}
// 定义具体命令
class CreateFileCommand implements Command {
public void execute(String[] args) {
// 根据给定的名称和内容创建文件的代码
System.out.println("创建文件 : " + String.join(", ", args));
}
}
class DeleteFileCommand implements Command {
public void execute(String[] args) {
// 根据给定的名称删除文件的代码
System.out.println("删除文件 : "+String.join(",",args) );
}
}
// 定义命令执行者
class CommandExecutor {
private Map<String, Command> commands = new HashMap<>();
public CommandExecutor() {
// 将具体命令与命令名称关联起来
commands.put("create", new CreateFileCommand());
commands.put("delete", new DeleteFileCommand());
}
public void executeCommand(String commandName, String[] args) {
// 查找对应的命令并执行
Command command = commands.get(commandName);
if (command != null) {
command.execute(args);
} else {
System.out.println("Unknown command: " + commandName);
}
}
}
// 使用命令执行者执行命令
public class Main {
public static void main(String[] args) {
CommandExecutor executor = new CommandExecutor();
executor.executeCommand("create", new String[]{
"file.txt", "Hello World!"});
executor.executeCommand("delete", new String[]{
"file.txt"});
executor.executeCommand("unknown", new String[]{
});
}
}
実行出力:
创建文件 : file.txt, Hello World!
删除文件 : file.txt
Unknown command: unknown
3. インタプリタパターン
Java インタプリタ パターンは、言語とその言語のインタプリタを定義する動作設計パターンであり、その言語を使用して特定の操作を表現できるようになります。このモードは、コンパイラ、式計算機など、特定の言語を解釈する必要があるシナリオに適しています。
Java では、言語式は抽象構文ツリー (AST) を使用して表現でき、インタプリタを使用してそれらの式を実行します。通常、インタープリター パターンには次のコンポーネントが含まれます。
- 抽象式: インタプリタが実装する必要があるメソッドを含む抽象インタプリタ インターフェイスを定義します。
- 端末式: 抽象式インターフェイスを実装し、言語の基本的な操作や値を表すために使用される端末。
- 非終端式 (非終端式): 抽象式インターフェイスを実装し、言語での複雑な操作を表すために使用される非終端記号。
- コンテキスト: 変数、関数など、インタープリターに必要ないくつかのグローバル情報が含まれます。
- インタプリタ: 上記のコンポーネントを使用して言語表現を解釈し、対応する操作を実行します。
以下は、単純な Java インタープリター モードのサンプル コードです。
interface Expression {
int interpret(Context context);
}
// 终结符表达式
class NumberExpression implements Expression {
private int value;
public NumberExpression(int value) {
this.value = value;
}
public int interpret(Context context) {
return value;
}
}
// 非终结符表达式
class AddExpression implements Expression {
private Expression left;
private Expression right;
public AddExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
public int interpret(Context context) {
return left.interpret(context) + right.interpret(context);
}
}
// 上下文
class Context {
private Map<String, Integer> variables = new HashMap<>();
public void setVariable(String name, int value) {
variables.put(name, value);
}
public int getVariable(String name) {
return variables.get(name);
}
}
// 解释器
class Interpreter {
private Expression expression;
public Interpreter(Expression expression) {
this.expression = expression;
}
public int interpret(Context context) {
return expression.interpret(context);
}
}
// 使用解释器执行表达式
public class Main {
public static void main(String[] args) {
// 创建上下文
Context context = new Context();
context.setVariable("a", 10);
context.setVariable("b", 20);
// 创建表达式
Expression expression = new AddExpression(
new NumberExpression(context.getVariable("a")),
new NumberExpression(context.getVariable("b"))
);
// 创建解释器并执行表达式
Interpreter interpreter = new Interpreter(expression);
int result = interpreter.interpret(context);
System.out.println("Result: " + result);
}
}
上記のサンプル コードでは、加算演算を表す 2 つの終端式 (NumberExpression) と 1 つの非終端式 (AddExpression) を定義しました。また、変数と関数を格納するために使用されるコンテキスト (Context) と、式を実行するために使用されるインタープリター (Interpreter) も定義します。最後に、インタプリタを使用して単純な加算式を実行し、結果を出力しました。
4. イテレータパターン
Java Iterator パターンは、コレクション オブジェクトの内部表現を公開せずにコレクション オブジェクトの要素にアクセスする方法を提供する動作設計パターンです。このモードは、配列、リスト、ツリーなどのコレクション オブジェクトを走査する必要があるシナリオに適しています。
以下は、配列ストレージを使用して配列反復子を実装する Java 反復子パターンの簡単なサンプル コードです。
interface Iterator<T> {
boolean hasNext();
T next();
}
// 具体迭代器实现类
class ArrayIterator<T> implements Iterator<T> {
private T[] array;
private int currentIndex;
public ArrayIterator(T[] array) {
this.array = array;
this.currentIndex = 0;
}
public boolean hasNext() {
return currentIndex < array.length;
}
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
T element = array[currentIndex];
currentIndex++;
return element;
}
}
// 使用迭代器遍历数组
public class Main {
public static void main(String[] args) {
Integer[] array = {
1, 2, 3, 4, 5};
Iterator<Integer> iterator = new ArrayIterator<>(array);
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
上記のサンプル コードでは、反復子オブジェクトを表す抽象反復子インターフェイス (Iterator) を定義しました。また、配列反復子を実装するための特定の反復子実装クラス (ArrayIterator) も定義します。最後に、整数の配列を反復処理し、各要素の値を出力します。
5. オブザーバーパターン
Java オブザーバー パターンは、1 対多の依存関係を定義する動作設計パターンであり、オブジェクトの状態が変化すると、それに依存するすべてのオブジェクトが通知され、自動的に更新されます。このモードは、イベント処理など、オブジェクト間の動的で疎結合な関係を確立する必要があるシナリオに適しています。
Observer パターンは、次のシナリオに適しています。
- オブジェクトの状態が変化すると、他のオブジェクトに通知し、その状態を更新する必要があります。
- オブジェクトが他のオブジェクトにその状態の変化を通知する必要があるが、これらのオブジェクトと密結合関係を持ちたくない場合。
- オブジェクトの変更により他のオブジェクトの状態も同時に変更する必要があり、変更する必要があるオブジェクトの数が不明な場合。
- オブジェクトの変更には他のオブジェクトの支援が必要だが、これらのオブジェクトをそれ自体と密接に結合したくない場合。
- オブジェクトが変化すると、一連の連鎖反応が引き起こされますが、これらの連鎖反応の具体的な実現はオブジェクトには知られたくないのです。
- オブジェクトが他のオブジェクトにデータ更新メソッドを提供する必要があり、この更新メソッドが異なる時点で異なるオブジェクトに通知する必要がある場合。
つまり、オブザーバー パターンは、イベント処理、GUI プログラミング、メッセージ キューなど、オブジェクト間の動的な疎結合関係を確立する必要があるシナリオに適しています。
以下は、気象データが変化したときにすべてのオブザーバー オブジェクトに通知する気象ステーションを実装する、単純な Java オブザーバー パターンのサンプル コードです。
// 抽象主题接口
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// 具体主题实现类
class WeatherStation implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherStation() {
this.observers = new ArrayList<>();
}
public void registerObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
System.out.println(">> 通知所有观察者 <<");
for (Observer observer : observers) {
System.out.println("------观察者:" + observer.name() + "-----------");
observer.update(temperature, humidity, pressure);
}
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
notifyObservers();
}
}
// 抽象观察者接口
interface Observer {
void update(float temperature, float humidity, float pressure);
String name();
}
// 具体观察者实现类
class Display implements Observer {
private float temperature;
private float humidity;
private float pressure;
private String name;
@Override
public String name() {
return this.name;
}
public Display(String name){
this.name = name;
}
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
}
public void display() {
System.out.println("Temperature: " + temperature);
System.out.println("Humidity: " + humidity);
System.out.println("Pressure: " + pressure);
}
}
// 使用观察者模式实现气象站
public class Main {
public static void main(String[] args) {
WeatherStation weatherStation = new WeatherStation();
Display display1 = new Display("01");
Display display2 = new Display("02");
weatherStation.registerObserver(display1);
weatherStation.registerObserver(display2);
weatherStation.setMeasurements(25.0f, 60.0f, 1013.0f);
weatherStation.removeObserver(display2);
weatherStation.setMeasurements(26.0f, 65.0f, 1012.0f);
}
}
入力結果を実行します。
>> 通知所有观察者 <<
------观察者:01-----------
Temperature: 25.0
Humidity: 60.0
Pressure: 1013.0
------观察者:02-----------
Temperature: 25.0
Humidity: 60.0
Pressure: 1013.0
>> 通知所有观察者 <<
------观察者:01-----------
Temperature: 26.0
Humidity: 65.0
Pressure: 1012.0
上記のサンプル コードでは、サブジェクト オブジェクトとオブザーバー オブジェクトを表すために使用される、抽象サブジェクト インターフェイス (Subject) と抽象オブザーバー インターフェイス (Observer) を定義します。また、気象観測所および表示オブジェクトを実装するための具体的なサブジェクト実装クラス (WeatherStation) と具体的なオブザーバー実装クラス (Display) も定義します。最後に、Observer パターンを使用して気象観測所を実装しました。気象データが変化すると、すべての観測オブジェクトに通知され、表示上のデータが更新されます。
Java 独自のオブザーバー モードの概要
Java によって提供される組み込みのオブザーバー パターン実装。Java の Observable クラスと Observer インターフェイスを使用して、オブザーバー パターンを実装します。
Observable クラスは、オブザーバーを追加、削除、通知するためのメソッドを持つ監視可能なオブジェクトを表す抽象クラスです。Observable オブジェクトの状態が変化すると、notifyObservers() メソッドが呼び出され、すべてのオブザーバー オブジェクトに通知され、その状態が更新されます。Observable クラスは、Observable オブジェクトの状態が変更されたかどうかをマークするための setChanged() メソッドと clearChanged() メソッドも提供します。
Observer インターフェイスは、状態を更新するメソッド update() を持つオブザーバー オブジェクトを表します。Observable オブジェクトの状態が変化すると、オブザーバー オブジェクトの update() メソッドが呼び出され、更新されたデータが渡されます。
import java.util.Observable;
import java.util.Observer;
// 具体主题类
class WeatherStation extends Observable {
private float temperature;
private float humidity;
private float pressure;
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
setChanged();
notifyObservers();
}
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
// 具体观察者类
class Display implements Observer {
private float temperature;
private float humidity;
private float pressure;
public void update(Observable o, Object arg) {
if (o instanceof WeatherStation) {
WeatherStation weatherStation = (WeatherStation) o;
this.temperature = weatherStation.getTemperature();
this.humidity = weatherStation.getHumidity();
this.pressure = weatherStation.getPressure();
display();
}
}
public void display() {
System.out.println("Temperature: " + temperature);
System.out.println("Humidity: " + humidity);
System.out.println("Pressure: " + pressure);
}
}
// 使用JDK自带观察者模式实现气象站
public class Main {
public static void main(String[] args) {
WeatherStation weatherStation = new WeatherStation();
Display display1 = new Display();
Display display2 = new Display();
weatherStation.addObserver(display1);
weatherStation.addObserver(display2);
weatherStation.setMeasurements(25.0f, 60.0f, 1013.0f);
weatherStation.deleteObserver(display2);
weatherStation.setMeasurements(26.0f, 65.0f, 1012.0f);
}
}
上記のサンプル コードでは、Observable クラスと Observer インターフェイスを使用して気象観測所と表示オブジェクトを実装しました。気象データが変化すると、Observable オブジェクトは、notifyObservers() メソッドを呼び出して、すべてのオブザーバー オブジェクトに通知し、そのステータスを更新します。オブザーバー オブジェクトは、オブザーバー インターフェイスの update() メソッドを実装して、自身の状態を更新します。
6. 状態パターン
Java 状態パターンは、内部状態が変化したときにオブジェクトの動作を変更できるようにする動作設計パターンです。状態パターンは、状態を独立したクラスにカプセル化し、現在の状態オブジェクトにリクエストを委任することで、状態の切り替えと状態の動作の変更を実現します。
状態パターンを使用するシナリオは次のとおりです。
- オブジェクトの動作がその状態に依存しており、実行時に状態に基づいて動作を変更する必要がある場合。
- オブジェクトが状態に基づいてデータとメソッドを変更する必要がある場合。
- オブジェクトが、それぞれ異なる動作を持つ複数の状態の間で切り替える必要がある場合。
予防:
- 状態パターンではクラスの数が増える可能性があるため、クラスの数と複雑さを念頭に置いて設計する必要があります。
- 状態パターンは状態をカプセル化する必要があるため、設計では状態の拡張性と保守性を考慮する必要があります。
Java 状態パターンを使用して実装されたサンプル コードを次に示します。
interface State {
void handle();
}
// 具体状态类1
class ConcreteState1 implements State {
public void handle() {
System.out.println("ConcreteState1 is handling.");
}
}
// 具体状态类2
class ConcreteState2 implements State {
public void handle() {
System.out.println("ConcreteState2 is handling.");
}
}
// 环境类
class Context {
private State state;
public void setState(State state) {
this.state = state;
}
public void request() {
state.handle();
}
}
// 使用状态模式实现的客户端代码
public class Main {
public static void main(String[] args) {
Context context = new Context();
State state1 = new ConcreteState1();
State state2 = new ConcreteState2();
context.setState(state1);
context.request();
context.setState(state2);
context.request();
}
}
上記のサンプル コードでは、状態インターフェイス State と 2 つの具象状態クラス ConcreteState1 および ConcreteState2 を定義します。また、状態オブジェクトを含む環境クラス Context も定義し、現在の状態オブジェクトの handle() メソッドを呼び出すために使用されるリクエスト メソッド request() を定義します。クライアント コードでは、Context オブジェクトを作成し、その状態を ConcreteState1 に設定し、request() メソッドを呼び出して「ConcreteState1 が処理中です。」を出力します。次に、Context の状態を ConcreteState2 に設定し、再度 request() メソッドを呼び出して、「ConcreteState2 が処理中です。」と出力します。
状態パターンを使用すると、状態と動作を分離できるため、状態の変化に応じてオブジェクトの動作も変化し、より柔軟な設計が可能になります。
7. テンプレートパターン
Java テンプレート パターンは、操作内のプログラム スケルトンを定義する動作設計パターンであり、一部の手順をサブクラスの実装に延期します。これにより、サブクラスは、プログラムの構造を変更せずに、プログラム内の特定のステップを再定義できます。
Java テンプレート パターンには、抽象メソッドと具象メソッドの 2 種類のメソッドがあります。抽象メソッドはサブクラスによって実装され、具象メソッドはスーパークラスによって実装されます。テンプレート メソッドは、プログラムの骨格を定義する具象メソッドと抽象メソッドで構成され、具象メソッドはアルゴリズムの一部のステップを実装します。
Java テンプレート パターンは、次のシナリオに適しています。
- 構造は似ているが実装の詳細が異なる一連のアルゴリズムを定義する必要がある場合は、テンプレート パターンを使用します。
- テンプレート パターンは、アルゴリズムのフローを制御する必要があり、アルゴリズムのさまざまな段階で異なる動作が必要な場合に使用できます。
- アルゴリズムの全体的な構造に影響を与えずにアルゴリズムの一部のステップをカスタマイズする必要がある場合は、テンプレート モードを使用できます。
- 複数のクラスで同じアルゴリズムを使用する必要がある場合、テンプレート パターンを使用してコードの重複を避けることができます。
つまり、Java テンプレート パターンは、プログラムでスケルトンを定義し、サブクラスで特定のステップを実装する必要があるユーザーに適しています。コードの再利用性と保守性が向上すると同時に、コードの柔軟性と拡張性も高まります。
以下は、単純な Java テンプレート パターンのサンプル コードです。
abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
// 模板方法
public final void play() {
// 初始化游戏
initialize();
// 开始游戏
startPlay();
// 结束游戏
endPlay();
}
}
class Cricket extends Game {
@Override
void endPlay() {
System.out.println("Cricket Game Finished!");
}
@Override
void initialize() {
System.out.println("Cricket Game Initialized! Start playing.");
}
@Override
void startPlay() {
System.out.println("Cricket Game Started. Enjoy the game!");
}
}
class Football extends Game {
@Override
void endPlay() {
System.out.println("Football Game Finished!");
}
@Override
void initialize() {
System.out.println("Football Game Initialized! Start playing.");
}
@Override
void startPlay() {
System.out.println("Football Game Started. Enjoy the game!");
}
}
public class TemplatePatternDemo {
public static void main(String[] args) {
Game game = new Cricket();
game.play();
System.out.println();
game = new Football();
game.play();
}
}
上記のコードでは、Game クラスは、テンプレート メソッドとして play() メソッドを定義する抽象クラスです。Cricket クラスと Football クラスは、抽象クラスで定義された抽象メソッドを実装する具象実装クラスです。main() メソッドでは、Cricket オブジェクトと Football オブジェクトを作成し、それらの play() メソッドを呼び出します。このようにして、アルゴリズムの構造を変更せずに、アルゴリズムの一部のステップを再定義できます。
8. メディエーターパターン
Java Mediator パターンは、オブジェクトが Mediator オブジェクトを介して通信できるようにする動作設計パターンであり、これによりオブジェクト間の直接結合を回避します。メディエーター パターンは、オブジェクト間の複雑な関係をメディエーターとオブジェクト間の単純な関係に変換するため、システムの柔軟性と保守性が向上します。
Java 中間パターンでは、中間オブジェクトはオブジェクト間の通信を調整する役割を果たし、通常、オブジェクト間の対話を処理するためのいくつかのパブリック メソッドが含まれています。オブジェクト間の通信はメディエーター オブジェクトを通じて行われるため、オブジェクト間の直接結合が回避されます。
以下は、単純な Java Mediator パターンのサンプル コードです。
// Mediator接口定义了send()方法,用于处理对象之间的交互。
interface Mediator {
void send(String message, Colleague colleague);
}
// Colleague抽象类表示对象,它包含一个Mediator对象,用于处理对象之间的通信。
abstract class Colleague {
protected Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
public abstract void receive(String message);
public abstract void send(String message);
}
// ConcreteColleague1和ConcreteColleague2是具体的对象实现类,它们实现了Colleague抽象类中的方法。
class ConcreteColleague1 extends Colleague {
public ConcreteColleague1(Mediator mediator) {
super(mediator);
}
@Override
public void receive(String message) {
System.out.println("Colleague1 received message: " + message);
}
@Override
public void send(String message) {
System.out.println("Colleague1 sends message: " + message);
mediator.send(message, this);
}
}
class ConcreteColleague2 extends Colleague {
public ConcreteColleague2(Mediator mediator) {
super(mediator);
}
@Override
public void receive(String message) {
System.out.println("Colleague2 received message: " + message);
}
@Override
public void send(String message) {
System.out.println("Colleague2 sends message: " + message);
mediator.send(message, this);
}
}
// ConcreteMediator是具体的中介者实现类,它负责协调对象之间的通信。
class ConcreteMediator implements Mediator {
private ConcreteColleague1 colleague1;
private ConcreteColleague2 colleague2;
public void setColleague1(ConcreteColleague1 colleague1) {
this.colleague1 = colleague1;
}
public void setColleague2(ConcreteColleague2 colleague2) {
this.colleague2 = colleague2;
}
@Override
public void send(String message, Colleague colleague) {
if (colleague == colleague1) {
colleague2.receive(message);
} else {
colleague1.receive(message);
}
}
}
public class MediatorPatternDemo {
public static void main(String[] args) {
ConcreteMediator mediator = new ConcreteMediator();
ConcreteColleague1 colleague1 = new ConcreteColleague1(mediator);
ConcreteColleague2 colleague2 = new ConcreteColleague2(mediator);
mediator.setColleague1(colleague1);
mediator.setColleague2(colleague2);
colleague1.send("Hello, Colleague2.");
colleague2.send("Hello, Colleague1.");
}
}
上記のコードでは、Mediator インターフェイスはオブジェクト間の対話を処理するために使用される send() メソッドを定義します。Colleague 抽象クラスはオブジェクトを表し、オブジェクト間の通信を処理する Mediator オブジェクトが含まれています。ConcreteColleague1 および ConcreteColleague2 は、Colleague 抽象クラスのメソッドを実装する具象オブジェクト実装クラスです。ConcreteMediator は、オブジェクト間の通信を調整する役割を担う特定の中間実装クラスです。
main() メソッドでは、ConcreteMediator オブジェクトと 2 つの ConcreteColleague オブジェクトを作成し、それらの send() メソッドを呼び出して通信します。通信は中間オブジェクトを介して実行され、オブジェクト間の直接結合が回避されます。
9. メメントパターン
Java Memento パターンは、カプセル化を壊さずにオブジェクトの内部状態をキャプチャおよび復元できるようにする動作設計パターンです。memento パターンは、アクションを元に戻したり、前の状態に戻したりする必要がある状況でよく使用されます。このパターンは、プリミティブ オブジェクト、memento オブジェクト、memento オブジェクトの管理を担当するオブジェクトという 3 つの主要なコンポーネントで構成されます。
以下は、単純な Java memento パターンのサンプル コードです。
// Originator类表示原始对象,它包含需要保存的状态。
class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
// createMemento()方法创建备忘录对象,并将当前状态保存到备忘录对象中。
public Memento createMemento() {
return new Memento(state);
}
// restore()方法用于从备忘录对象中恢复先前的状态。
public void restore(Memento memento) {
state = memento.getState();
}
}
// Memento类表示备忘录对象,它包含需要保存的状态。
class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
// Caretaker类负责管理备忘录对象,它包含一个Memento对象。
class Caretaker {
private Memento memento;
public void setMemento(Memento memento) {
this.memento = memento;
}
public Memento getMemento() {
return memento;
}
}
public class MementoPatternDemo {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
// 保存原始对象的状态到备忘录对象中
originator.setState("State 1");
caretaker.setMemento(originator.createMemento());
// 修改原始对象的状态
originator.setState("State 2");
// 从备忘录对象中恢复先前的状态
originator.restore(caretaker.getMemento());
System.out.println("Current state: " + originator.getState());
}
}
上記のコードでは、Originator クラスは、保存する必要がある状態を含む元のオブジェクトを表します。createMemento() メソッドは、memento オブジェクトを作成し、現在の状態を memento オブジェクトに保存します。restore() メソッドは、memento オブジェクトから以前の状態を復元するために使用されます。
Memento クラスは、保存する必要がある状態を含む Memento オブジェクトを表します。
Caretaker クラスは、Memento オブジェクトを含む Memento オブジェクトの管理を担当します。
main() メソッドでは、Originator オブジェクトと Caretaker オブジェクトを作成し、状態の保存と復元のためにそれらのメソッドを呼び出します。Memento パターンを使用すると、カプセル化を壊さずにオブジェクトの内部状態をキャプチャして復元できます。
10. 訪問者のパターン
Java ビジター パターンは、オブジェクト構造を変更せずに新しい操作を定義できる動作設計パターンです。ビジター パターンはオブジェクト構造と操作を分離し、操作をオブジェクト構造内の要素に独立して適用できるようにします。Visitor パターンには、訪問者と要素という 2 つの主要なアクターが存在します。訪問者は要素を操作するメソッドを定義し、要素は訪問者を受け入れるメソッドを提供します。
以下は訪問者パターンのサンプル コードです。このコードでは、式を評価できる単純な計算機を実装します。ビジター パターンを使用して式ツリーをたどり、各ノードで対応する操作を実行します。
訪問者のパターンは、次のシナリオに適しています。
- オブジェクト構造は比較的安定していますが、多くの場合、このオブジェクト構造に対して新しい操作を定義する必要があります。
- オブジェクト構造内のオブジェクトに対して無関係なさまざまな操作を実行する必要がありますが、これらの操作によってオブジェクトのクラスが「汚染」されることは望ましくありません。
- オブジェクト構造内のオブジェクトのクラスが変更されることはほとんどありませんが、多くの場合、これらのオブジェクトに対して新しい操作を定義する必要があります。
- オブジェクトのタイプに応じて、実行時にさまざまな操作を実行する必要があります。
- オブジェクト構造内のすべてのオブジェクトは何らかの方法で処理する必要がありますが、オブジェクトの処理方法はオブジェクトのタイプによって異なります。
訪問者パターンを使用する一般的なシナリオには、コンパイラ構文分析、XML ドキュメント解析、静的アナライザー、モデル検証ツール、モデル コンバーターなどが含まれます。
interface Expression {
void accept(Visitor visitor);
}
class NumberExpression implements Expression {
private int value;
public NumberExpression(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// 定义具体的元素类:加法表达式
class AdditionExpression implements Expression {
private Expression left;
private Expression right;
public AdditionExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
public Expression getLeft() {
return left;
}
public Expression getRight() {
return right;
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
interface Visitor {
void visit(NumberExpression expression);
void visit(AdditionExpression expression);
}
// 定义具体的访问者类:打印访问者
class PrinterVisitor implements Visitor {
public void visit(NumberExpression expression) {
System.out.print(expression.getValue());
}
public void visit(AdditionExpression expression) {
System.out.print("(");
expression.getLeft().accept(this);
System.out.print("+");
expression.getRight().accept(this);
System.out.print(")");
}
}
class CalculatorVisitor implements Visitor {
private int result;
public void visit(NumberExpression expression) {
result = expression.getValue();
}
public void visit(AdditionExpression expression) {
expression.getLeft().accept(this);
int leftValue = result;
expression.getRight().accept(this);
int rightValue = result;
result = leftValue + rightValue;
}
public int getResult() {
return result;
}
}
public class Client {
public static void main(String[] args) {
// 构建表达式树:1 + (2 + 3)
Expression expression = new AdditionExpression(
new NumberExpression(1),
new AdditionExpression(
new NumberExpression(2),
new NumberExpression(3)
)
);
// 计算表达式的值
CalculatorVisitor calculator = new CalculatorVisitor();
expression.accept(calculator);
System.out.println("Result: " + calculator.getResult());
// 打印表达式的字符串表示
PrinterVisitor printer = new PrinterVisitor();
expression.accept(printer);
}
}
出力結果:
Result: 6
(1+(2+3))
上の例では、式インターフェイス Expression を定義し、2 つの式実装、NumberExpression と AdditionExpression を提供し、訪問者インターフェイス Visitor と 2 つの特定の訪問者、CalculatorVisitor および PrinterVisitor を定義します。2 人の訪問者は、式オブジェクトを受け取り、訪問者の式に対する特定の操作 (式操作と式印刷) を実装します。上記の例では、具象式クラスを変更せず、新しい操作を定義します。
11. 戦略パターン
Strategy パターンは、アルゴリズムのファミリーを定義し、各アルゴリズムをカプセル化し、それらを交換可能にすることを可能にする動作設計パターンです。このパターンにより、アルゴリズムを使用するクライアントとは独立してアルゴリズムを変更できます。
Java では、ストラテジ パターンは通常、インターフェイスとそのインターフェイスを実装する複数のクラスで構成されます。クライアントは、実装クラスを直接呼び出すのではなく、このインターフェイスを使用してアルゴリズムを呼び出します。このようにして、クライアントはコードを変更せずに、実行時に別のアルゴリズム実装を選択できます。
次の例は、戦略パターンを使用して単純な支払いシステムを実装する方法を示しています。PaymentStrategy インターフェイスを定義し、このインターフェイスを実装する複数のクラスを作成します。各クラスは異なる支払い方法を表します。顧客はニーズに応じて使用する支払い方法を選択できます。
interface PaymentStrategy {
void pay(double amount);
}
class CreditCardPayment implements PaymentStrategy {
private String cardNumber;
private String cvv;
private String expiryDate;
public CreditCardPayment(String cardNumber, String cvv, String expiryDate) {
this.cardNumber = cardNumber;
this.cvv = cvv;
this.expiryDate = expiryDate;
}
public void pay(double amount) {
System.out.println("Paying " + amount + " using credit card.");
}
}
class PayPalPayment implements PaymentStrategy {
private String email;
private String password;
public PayPalPayment(String email, String password) {
this.email = email;
this.password = password;
}
public void pay(double amount) {
System.out.println("Paying " + amount + " using PayPal.");
}
}
class CashPayment implements PaymentStrategy {
public void pay(double amount) {
System.out.println("Paying " + amount + " using cash.");
}
}
class PaymentProcessor {
private PaymentStrategy strategy;
public PaymentProcessor(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void processPayment(double amount) {
strategy.pay(amount);
}
}
public class PaymentSystem {
public static void main(String[] args) {
PaymentProcessor processor = new PaymentProcessor(new CreditCardPayment("1234 5678 9012 3456", "123", "12/23"));
processor.processPayment(100.0);
processor.setStrategy(new PayPalPayment("[email protected]", "password"));
processor.processPayment(50.0);
processor.setStrategy(new CashPayment());
processor.processPayment(25.0);
}
}
実行出力:
Paying 100.0 using credit card.
Paying 50.0 using PayPal.
Paying 25.0 using cash.
上記の例では、PaymentStrategy インターフェイスは支払い方法を定義し、金額パラメータを受け入れる支払い方法が含まれています。このインターフェイスを実装する 3 つのクラスを作成し、それぞれクレジット カード支払い、PayPal 支払い、現金支払いを表しました。PaymentProcessor クラスは、PaymentStrategy インスタンスをパラメータとして受け取り、それを使用して支払い操作を実行します。main メソッドでは、PaymentProcessor インスタンスを作成し、さまざまな支払い方法を使用して支払いを行います。
他の
よく使われる次の 2 つのデザイン パターンを紹介します。
1. フィルターパターン
Java フィルター設計パターンは、ターゲット オブジェクトに到達する前または後に要求を処理またはフィルターするために一般的に使用される設計パターンです。このモードを使用すると、認証、認可、ロギング、圧縮などのさまざまな機能を実装し、さまざまな操作をフィルタ チェーン内のフィルタとして扱うことができます。
単純な Java フィルター設計パターンのサンプル コードを次に示します。
- まず、リクエストを処理するメソッド doFilter を含むインターフェース Filter を定義します。
- 次に、リクエストの認証用とログ記録用の 2 つのフィルター クラスを実装します。
- 最後に、複数のフィルターをチェーンするための FilterChain クラスを定義します。
public interface Filter {
public void doFilter(String request);
}
// 定义授权过滤器
public class AuthenticationFilter implements Filter {
public void doFilter(String request) {
System.out.println("Authenticating request: " + request);
}
}
// 定义日志过滤器
public class LoggingFilter implements Filter {
public void doFilter(String request) {
System.out.println("Logging request: " + request);
}
}
// 定义过滤器链
public class FilterChain {
private List<Filter> filters = new ArrayList<Filter>();
private int index = 0;
public void addFilter(Filter filter) {
filters.add(filter);
}
public void doFilter(String request) {
if (index == filters.size()) {
return;
}
Filter filter = filters.get(index);
index++;
filter.doFilter(request);
doFilter(request);
}
}
public class Main {
public static void main(String[] args) {
FilterChain chain = new FilterChain();
chain.addFilter(new AuthenticationFilter());
chain.addFilter(new LoggingFilter());
chain.doFilter("request");
}
}
上記のサンプル コードは、Java フィルター設計パターンの基本的な実装を示しています。さまざまなフィルター クラスを定義してそれらを連鎖させることで、さまざまな機能を実現できます。
2. ヌルオブジェクトパターン
Java Null オブジェクト パターンは、null を返さずにデフォルトの動作を提供できるようにする動作設計パターンです。このパターンは通常、NullPointerException 例外を回避するために null オブジェクトを処理する必要がある状況で使用されます。このパターンは、抽象クラスと具象クラスという 2 つの主要なコンポーネントで構成されます。
以下は、Java Null Object パターンの簡単なサンプル コードです。
interface User {
String getName();
boolean hasAccess();
}
// 定义一个具体类,表示一个真实的用户
class RealUser implements User {
private String name;
private boolean hasAccess;
public RealUser(String name, boolean hasAccess) {
this.name = name;
this.hasAccess = hasAccess;
}
public String getName() {
return name;
}
public boolean hasAccess() {
return hasAccess;
}
}
// 定义一个空对象,表示一个空的用户
class NullUser implements User {
public String getName() {
return "Guest";
}
public boolean hasAccess() {
return false;
}
}
// 定义一个工厂类,用于创建用户
class UserFactory {
// 根据名称和权限创建一个用户
public static User getUser(String name, boolean hasAccess) {
if (name == null) {
return new NullUser();
}
return new RealUser(name, hasAccess);
}
}
public class NullObjectPatternDemo {
public static void main(String[] args) {
User user1 = UserFactory.getUser("Alice", true);
User user2 = UserFactory.getUser(null, false);
System.out.println("User 1: " + user1.getName() + ", has access: " + user1.hasAccess());
System.out.println("User 2: " + user2.getName() + ", has access: " + user2.hasAccess());
}
}
上記のコードでは、ユーザーを表す User インターフェイスを定義し、getName() と hasAccess() という 2 つのメソッドを含みます。次に、実際のユーザーを表す具象クラス RealUser を定義し、User インターフェイスの 2 つのメソッドを実装します。また、空のユーザーを表す空のオブジェクト NullUser も定義し、User インターフェイスの 2 つのメソッドを実装します。最後に、ユーザーを作成するためのファクトリ クラス UserFactory を定義します。受信した名前が null の場合は空のオブジェクト NullUser を返し、それ以外の場合は実際のユーザー RealUser を返します。
main() メソッドでは、UserFactory を使用して 2 人のユーザー user1 と user2 を作成し、それらの名前と権限を出力します。user2 の名前は null であるため、空のオブジェクト NullUser が返され、「Guest」文字列とブール値 false が出力されます。空のオブジェクト パターンを使用すると、null を返さずにデフォルトの動作を提供し、NullPointerException を回避できます。