8 つの一般的なフロントエンド設計パターンのうち、どれを使用したことがありますか?

1. デザインパターンとは何ですか?

デザインパターンは、特定の状況における問題の解決策です。デザインパターンとは、コンセプトをまとめたテンプレートであり、決まったものをまとめたものです。それぞれのパターンは、私たちの周囲で繰り返される問題と、その問題に対する解決策の核心を説明します。

フロントエンド開発では、デザインパターンが広く使用されているアイデアです。デザイン パターンは、開発者が一般的な問題を解決し、再利用可能なソリューションを提供するのに役立ちます。この記事では、一般的なフロントエンド設計パターンを紹介し、コードを通じてその実装について詳しく説明します。

2. 設計原則

1. S – 単一責任原則 単一責任原則

  • プログラムは 1 つのことをうまく実行します
  • 関数が複雑すぎる場合は、関数を分割して各部分を独立させます。

2. O – OpenClosed 原則 オープン/クローズ原則

  • 拡張の場合はオープン、変更の場合はクローズ
  • 要件の増加に応じて既存のコードを変更するのではなく、新しいコードを拡張する

3. L – リスコフ置換原理

  • サブクラスは親クラスをオーバーライドできます
  • 親クラスが出現できる場所には、サブクラスも出現できます

4. I – インターフェース分離の原則 インターフェース分離の原則

  • インターフェイスを単一かつ独立したものに保つ
  • 単一責任の原則と同様に、ここではインターフェースにさらに注意が払われています。

5. D – 依存関係逆転の原理 依存関係逆転の原理

  • 具象ではなく抽象化に依存したインターフェイス指向のプログラミング
  • ユーザーはインターフェースのみに注意を払い、具体的なクラスの実装には注意を払いません。

3. デザインパターンの種類

1. 構造パターン: 

        システム内のコンポーネント間の単純な関係を識別することで、システム設計を簡素化します。

2. 創作パターン: 

        オブジェクトの作成を処理し、実際の状況に応じて適切な方法でオブジェクトを作成します。従来のオブジェクト作成方法では、設計上の問題が発生したり、設計が複雑になったりする可能性があります。作成パターンは、オブジェクトの作成を何らかの方法で制御することで問題を解決します。

3. 行動パターン: 

        オブジェクト間の共通の対話パターンを特定して実装するために使用され、これによりこれらの対話の柔軟性が向上します。

4. フロントエンドに共通する8つのデザインパターン

1. シングルトンモード

シングルトン パターンは、クラスが 1 回だけインスタンス化され、グローバル アクセス ポイントを提供することを意味します。このモードは、リソースを共有する必要があるシナリオに非常に適しています。たとえば、フロントエンド開発では、特定のリソースが毎回再ロードされるのではなく、一度だけロードされるようにする必要があることがよくあります。シングルトン パターンを使用した例を次に示します。

class Singleton {
  constructor() {
    if (typeof Singleton.instance === 'object') {
      return Singleton.instance;
    }
    this.name = 'Singleton';
    Singleton.instance = this;
    return this;
  }
}

const instance1 = new Singleton();
const instance2 = new Singleton();

console.log(instance1 === instance2); // true

上記のコードでは、一度だけインスタンス化できるシングルトン クラスを作成しました。クラスのインスタンス化を複数回試行すると、同じインスタンスが返されます。これにより、特定のリソースが 1 回だけロードされるようになり、パフォーマンスが向上します。

2. オブザーバーモード

オブザーバー モードとは、オブジェクトの状態が変化すると、そのすべての依存オブジェクトに通知が送信され、自動的に更新されることを意味します。このモードは、ユーザー インターフェイスをリアルタイムで更新する必要があるシナリオに非常に適しています。次に、Observer パターンを使用した例を示します。

class ObserverList {
  constructor() {
    this.observerList = [];
  }

  add(observer) {
    return this.observerList.push(observer);
  }

  remove(observer) {
    this.observerList = this.observerList.filter((obs) => obs !== observer);
  }

  count() {
    return this.observerList.length;
  }

  get(i) {
    return this.observerList[i];
  }
}

class Subject {
  constructor() {
    this.observers = new ObserverList();
  }

  addObserver(observer) {
    this.observers.add(observer);
  }

  removeObserver(observer) {
    this.observers.remove(observer);
  }

  notify(context) {
    const observerCount = this.observers.count();
    for (let i = 0; i < observerCount; i++) {
      this.observers.get(i).update(context);
    }
  }
}

class Observer {
  constructor() {
    this.update = () => {};
  }
}

const subject = new Subject();

const observer1 = new Observer();
observer1.update = function (context) {
  console.log(`Observer 1: ${context}`);
};

const observer2 = new Observer();
observer2.update = function (context) {
  console.log(`Observer 2: ${context}`);
};

subject.addObserver(observer1);
subject.addObserver(observer2);

subject.notify('Hello World');

上記のコードでは、Subject クラスと Observer クラスを作成しました。Subject クラスにはオブザーバーのリストが含まれており、オブザーバーを追加、削除、通知するためのメソッドが提供されます。Observer クラスには、Observed からの通知を処理するための update メソッドが含まれています。メイン プログラムでは、2 つのオブザーバーを作成し、オブザーバーのオブザーバーのリストに追加します。次に、オブザーバブルに通知します。これにより、すべてのオブザーバーに自動的に通知され、更新メソッドが実行されます。

3. ファクトリーモード

ファクトリ パターンは、ファクトリ クラスを通じて他のクラスのインスタンスを作成することを指します。このパターンは、さまざまな条件に応じてさまざまなインスタンスを作成する必要があるシナリオに非常に適しています。ファクトリ パターンを使用した例を次に示します。

class ProductA {
	constructor(name) {
		this.name = name;
	}
	operation() {
		console.log(`Product A (${this.name}) is working.`);
	}
}
class ProductB {
	constructor(name) {
		this.name = name;
	}
	operation() {
		console.log(`Product B (${this.name}) is working.`);
	}
}
class Factory {
	createProduct(type, name) {
		switch (type) {
			case 'A':
				return new ProductA(name);
			case 'B':
				return new ProductB(name);
			default:
				throw new Error('Invalid product type.');
		}
	}
}
const factory = new Factory();
const productA1 = factory.createProduct('A', 'productA1');
const productA2 = factory.createProduct('A', 'productA2');
const productB1 = factory.createProduct('B', 'productB1');
const productB2 = factory.createProduct('B', 'productB2');
productA1.operation(); // Product A (productA1) is working.
productA2.operation(); // Product A (productA2) is working.
productB1.operation(); // Product B (productB1) is working.
productB2.operation(); // Product B (productB2) is working.

上記のコードでは、2 つの製品クラス `ProductA` と `ProductB` と、ファクトリ クラス `Factory` を作成しました。ファクトリ クラスは、製品インスタンスを作成するためのメソッド `createProduct` を提供します。このメソッドは、渡されたパラメータに基づいてどの製品インスタンスを作成するかを決定します。メインプログラムでは、ファクトリクラスを通じて 4 つの異なる製品インスタンスを作成し、それぞれの操作メソッドを実行します。

4. デコレーターモード

デコレータ パターンは、オブジェクトに追加の機能を動的に追加することを指します。このパターンは、実行時にオブジェクトの動作を動的に変更する必要があるシナリオに非常に適しています。以下はデコレーター パターンを使用した例です。

class Shape {
  draw() {
  }
}

class Circle extends Shape {
  draw() {
    console.log('Drawing a circle.');
  }
}

class Rectangle extends Shape {
  draw() {
    console.log('Drawing a rectangle.');
  }
}

class Decorator {
  constructor(shape) {
    this.shape = shape;
  }

  draw() {
    this.shape.draw();
  }
}

class RedShapeDecorator extends Decorator {
  draw() {
    this.shape.draw();
    this.setRedBorder();
  }

  setRedBorder() {
    console.log('Setting red border.');
  }
}

const circle = new Circle();
const rectangle = new Rectangle();

circle.draw(); // Drawing a circle.
rectangle.draw(); // Drawing a rectangle.

const redCircle = new RedShapeDecorator(new Circle());
const redRectangle = new RedShapeDecorator(new Rectangle());

redCircle.draw(); // Drawing a circle. Setting red border.
redRectangle.draw(); // Drawing a rectangle. Setting red border.

上記のコードでは、Circle と Rectangle という 2 つのシェイプ クラスと、デコレータ クラス Decorator を作成しました。デコレータ クラスには、装飾に使用されるシェイプ オブジェクトが含まれています。次に、赤いシェイプ デコレータ クラス RedShapeDecorator を作成して、シェイプの周囲に赤い境界線を追加しました。メインプログラムでは、まず元の形状の描画メソッドを実行し、それを赤いデコレータで装飾します。

5. プロキシモード

プロキシ パターンは、プロキシ オブジェクトを使用して別のオブジェクトへのアクセスを制御することを指します。このパターンは、一部の機密リソースへのアクセスを制御する必要があるシナリオに非常に適しています。プロキシ パターンを使用した例を次に示します。

class Image {
  constructor(url) {
    this.url = url;
    this.loadImage();
  }

  loadImage() {
    console.log(`Loading image from ${this.url}`);
  }
}

class ProxyImage {
  constructor(url) {
    this.url = url;
  }

  loadImage() {
    if (!this.image) {
      this.image = new Image(this.url);
    }
    console.log(`Displaying cached image from ${this.url}`);
  }
}

const image1 = new Image('https://example.com/image1.jpg');
const proxyImage1 = new ProxyImage('https://example.com/image1.jpg');

proxyImage1.loadImage(); // Loading image from https://example.com/image1.jpg
proxyImage1.loadImage(); // Displaying cached image from https://example.com/image1.jpg

const image2 = new Image('https://example.com/image2.jpg');
const proxyImage2 = new ProxyImage('https://example.com/image2.jpg');

proxyImage2.loadImage(); // Loading image from https://example.com/image2.jpg
proxyImage2.loadImage(); // Displaying cached image from https://example.com/image2.jpg

上記のコードでは、イメージ クラス `Image` とプロキシ イメージ クラス `ProxyImage` を作成しました。プロキシ画像クラスには、その読み込みと表示へのアクセスを制御する画像オブジェクトが含まれています。メイン プログラムでは、最初に実画像オブジェクトを作成し、プロキシ画像オブジェクトを使用してそれにアクセスします。最初のアクセスでは、プロキシ画像オブジェクトは実際の画像をロードして表示します。2 回目のアクセスでは、プロキシ画像オブジェクトはキャッシュから画像を直接取得して表示します。

6. アダプターモード

アダプタパターンとは、互換性のないインターフェースのオブジェクトを互換性のあるインターフェースのオブジェクトに変換することを指します。このモードは、元のコードに影響を与えずにインターフェイスを変更する必要があるシナリオに非常に適しています。以下は、アダプター パターンを使用した例です。

class OldCalculator {
  operations(a, b, operation) {
    switch (operation) {
      case 'add':
        return a + b;
      case 'sub':
        return a - b;
      default:
        return NaN;
    }
  }
}

class NewCalculator {
  add(a, b) {
    return a + b;
  }

  sub(a, b) {
    return a - b;
  }
}

class CalculatorAdapter {
  constructor() {
    this.newCalculator = new NewCalculator();
  }

  operations(a, b, operation) {
    switch (operation) {
      case 'add':
        return this.newCalculator.add(a, b);
      case 'sub':
        return this.newCalculator.sub(a, b);
      default:
        return NaN;
    }
  }
}

const oldCalculator = new OldCalculator();
console.log(oldCalculator.operations(10, 5, 'add')); // 15

const newCalculator = new NewCalculator();
console.log(newCalculator.add(10, 5)); // 15

const calculatorAdapter = new CalculatorAdapter();
console.log(calculatorAdapter.operations(10, 5, 'add')); // 15

上記のコードでは、古い電卓クラス OldCalculator と新しい電卓クラス NewCalculator を作成しました。次に、アダプタ クラス CalculatorAdapter を作成しました。このクラスには、古い電卓の動作を新しい電卓の動作に変換するための新しい電卓オブジェクトが含まれています。メイン プログラムでは、古い計算機、新しい計算機、およびアダプターを使用して加算を実行し、同じ結果を取得します。

7. コマンドモード

コマンド モードは、リクエストをオブジェクトにカプセル化し、リクエストの実行に関連するすべての情報を提供することを指します。このパターンは、複数の異なる操作を実行する必要があるシナリオに最適です。コマンド パターンを使用した例を次に示します。

class Receiver {
  run() {
    console.log('Receiver is running.');
  }
}

class Command {
  constructor(receiver) {
    this.receiver = receiver;
  }

  execute() {}
}

class StartCommand extends Command {
  execute() {
    this.receiver.run();
  }
}

class Invoker {
  setCommand(command) {
    this.command = command;
  }

  executeCommand() {
    this.command.execute();
  }
}

const receiver = new Receiver();
const startCommand = new StartCommand(receiver);
const invoker = new Invoker();
invoker.setCommand(startCommand);
invoker.executeCommand(); // Receiver is running.

上記のコードでは、レシーバー クラス Receiver とコマンド ベース クラス Command を作成しました。次に、特定のコマンド クラス StartCommand を作成しました。これは、コマンドの基本クラスから継承し、レシーバーを開始するための実行メソッドを実装します。最後に、呼び出し元クラス Invoker を作成しました。これにはコマンド オブジェクトが含まれており、コマンドを実行するメソッドが提供されます。メインプログラムでは、受信者オブジェクト、具象コマンドオブジェクト、呼び出し元オブジェクトを作成し、具体コマンドオブジェクトを呼び出し元オブジェクトのコマンドとして設定します。次に、呼び出し側オブジェクトの実行メソッドを実行します。これにより、具体的なコマンド オブジェクトの実行メソッドが呼び出され、受信側が開始されます。この例は比較的単純ですが、コマンド パターンは、元に戻す/やり直し操作、トランザクション管理など、多くの複雑なシナリオに適用できます。

より魅力的なコンテンツを入手するには、公式アカウント[Programmer Style]に注目してください!

8. オブザーバーモード

オブザーバー モードとは、オブジェクト間の 1 対多の依存関係を定義することを指します。これにより、オブジェクトの状態が変化するたびに、それに依存するすべてのオブジェクトが通知され、自動的に更新されます。このパターンは、アプリケーションにイベント処理を実装する必要があるシナリオに非常に適しています。次に、Observer パターンを使用した例を示します。

class Subject {
  constructor() {
    this.observers = [];
  }

  addObserver(observer) {
    this.observers.push(observer);
  }

  removeObserver(observer) {
    const index = this.observers.indexOf(observer);
    if (index >= 0) {
      this.observers.splice(index, 1);
    }
  }

  notifyObservers() {
    for (const observer of this.observers) {
      observer.update(this);
    }
  }
}

class ConcreteSubject extends Subject {
  constructor(state) {
    super();
    this.state = state;
  }

  getState() {
    return this.state;
  }

  setState(state) {
    this.state = state;
    this.notifyObservers();
  }
}

class Observer {
  update() {}
}

class ConcreteObserver extends Observer {
  update(subject) {
    console.log(`The subject has changed to ${subject.getState()}.`);
  }
}

const subject = new ConcreteSubject('state1');
const observer1 = new ConcreteObserver();
const observer2 = new ConcreteObserver();

subject.addObserver(observer1);
subject.addObserver(observer2);

subject.setState('state2'); // The subject has changed to state2.

上記のコードでは、サブジェクトの基本クラス Subject と具体的なサブジェクト クラス ConcreteSubject を作成しました。サブジェクト クラスには、状態プロパティとオブザーバー オブジェクトのセットが含まれており、オブザーバーを追加、削除、通知するメソッドを提供します。次に、オブザーバー基本クラス Observer と、オブザーバー基本クラスを継承して update メソッドを実装する特定のオブザーバー クラス ConcreteObserver を作成しました。メイン プログラムでは、具体的なサブジェクト オブジェクトと 2 つの具体的なオブザーバー オブジェクトを作成し、オブザーバー オブジェクトをサブジェクト オブジェクトに追加します。次に、サブジェクト オブジェクトの状態を変更し、サブジェクト オブジェクトを通じて更新するようにオブザーバー オブジェクトに通知します。

上記は 8 つの一般的なデザイン パターンとその適用シナリオとサンプル コードです。もちろん、これは氷山の一角にすぎず、さまざまなシナリオに適用できる他にも多くのデザイン パターンがあります。さまざまな設計パターンに精通し、それらを柔軟に使用できるようになれば、より優れた開発者になれるでしょう。

おすすめ

転載: blog.csdn.net/dreaming317/article/details/129837200