クリーンで保守しやすく効率的なコードを作成する場合、ソフトウェア開発の世界ではデザイン パターンが重要な役割を果たします。デザイン パターンは、開発者がソフトウェア システムの設計および構築中に直面する一般的な問題に対する再利用可能な解決策です。これらは、特定の課題を解決するための構造化されたアプローチを提供し、堅牢なだけでなく理解と保守も容易なコードの作成を容易にします。
オブジェクト指向プログラミング (OOP ) では、デザイン パターンは、柔軟性、再利用性、および拡張性を促進する方法でコードを構造化するためのガイドラインとして機能します。これらは、ベスト プラクティスと設計原則をカプセル化し、進化し、実証済みのソリューションに集約されています。
デザイン パターンは、次の 3 つの主要なグループに分類できます。
作成パターン:これらのパターンはオブジェクト作成メカニズムに焦点を当てており、状況に適した方法でオブジェクトを作成しようとします。これらはインスタンス化プロセスを抽象化し、プロセスをより柔軟にし、システムから独立させます。
構造パターン:構造パターンはオブジェクトの構成を扱い、オブジェクト間の関係を形成して、より大きく、より複雑な構造を作成します。これらは、オブジェクトとクラスを組み合わせて新しい構造を形成し、新しい機能を提供する方法を定義するのに役立ちます。
行動パターン:行動パターンはオブジェクト間の通信に関係し、オブジェクトがどのように相互作用し、責任を分散するかを定義します。これらのパターンは、オブジェクトがより柔軟かつ効率的な方法で連携するシステムを設計するのに役立ちます。
各カテゴリの一般的なデザイン パターンのリストを次に示します。
オブザーバー パターン:オブジェクト間の 1 対多の依存関係を定義します。そのため、1 つのオブジェクトの状態が変化すると、そのすべての依存オブジェクトが通知され、自動的に更新されます。
戦略パターン:アルゴリズムのファミリーを定義し、それぞれをカプセル化して交換可能にします。
コマンド パターン:リクエストをオブジェクトとしてカプセル化することで、キュー、リクエスト、操作によるクライアントのパラメータ化が可能になります。
状態パターン:オブジェクトの内部状態が変化したときに、その動作を別のクラスでラップして、オブジェクトの動作を変更できるようにします。
責任の連鎖パターン:ハンドラーのチェーンに沿ってリクエストを渡し、各ハンドラーがリクエストを処理するかチェーン内の次のハンドラーに渡すかを決定できるようにします。
ビジター パターン:これは、オブジェクト構造の要素に対して実行される操作を表し、要素のクラスを変更せずに新しい操作を定義できるようにします。
このブログでは、これらの各設計パターンを詳しく説明し、説明、実際の使用例、およびJavaScriptコードの例を提供して、それらを理解してプロジェクトに効果的に実装するのに役立ちます。
シングルトン パターンは、クラスにインスタンスが 1 つだけ存在することを保証し、そのインスタンスへのグローバル アクセス ポイントを提供する作成設計パターンです。このパターンは、アプリケーション内のクラスのインスタンスの数を制限し、単一の共有インスタンスへのアクセスを制御する場合に特に便利です。
JavaScript では、言語の柔軟性のおかげで、シングルトン パターンの実装は比較的簡単です。 JavaScript でシングルトンを作成する方法の簡単な例を見てみましょう。
// Singleton instance let instance = null;
class Singleton { constructor() { if (!instance) { instance = this; // Your initialization code here } else { return instance; } } // Your methods and properties here }// Usage const singletonA = new Singleton(); const singletonB = new Singleton(); console.log(singletonA === singletonB); // Output: true (both variables reference the same instance)
この例では、インスタンスが既に存在するかどうかを確認するコンストラクターを含む Singleton クラスを作成します。インスタンスが存在しない場合は、インスタンスを作成してインスタンス変数に割り当てます。コンストラクターへの後続の呼び出しでは既存のインスタンスが返され、Singleton クラスのインスタンスが 1 つだけ存在することが保証されます。
シングルトン パターンは、次のようなさまざまなシナリオで役立ちます。
シングルトン パターンは有益な場合もありますが、慎重に使用することが重要です。シングルトン パターンを過度に使用すると、コードとグローバル状態が緊密に結合される可能性があり、アプリケーションの保守とテストが困難になる可能性があります。したがって、メリットとデメリットを比較検討し、コードベースに真の価値をもたらすパターンを適用することが重要です。
ファクトリ パターンと抽象ファクトリ パターンは、オブジェクトの作成を扱う創造的なデザイン パターンですが、その方法は異なり、異なる目的を果たします。これらの各パターンを調べて、JavaScript でどのように実装できるかを見てみましょう。
ファクトリ パターンは、オブジェクトを作成するためのインターフェイスを提供する作成パターンですが、サブクラスが作成されるオブジェクトのタイプを変更できるようにします。オブジェクト作成プロセスがカプセル化され、より柔軟になり、クライアント コードから切り離されます。
// Product class class Product { constructor(name) { this.name = name; } }
// Factory for creating products class ProductFactory { createProduct(name) { return new Product(name); } }// Usage const factory = new ProductFactory(); const productA = factory.createProduct('Product A'); const productB = factory.createProduct('Product B');console.log(productA.name); // Output: 'Product A' console.log(productB.name); // Output: 'Product B'
この例では、ProductFactory は Product クラスのインスタンスの作成を担当します。作成プロセスを抽象化し、ファクトリーを拡張することでさまざまなタイプの製品を作成できるようにします。
Abstract Factory パターンは、具体的なクラスを指定せずに関連オブジェクトまたは依存オブジェクトのファミリーを作成するためのインターフェイスを提供する別の作成パターンです。これにより、調和して連携するオブジェクトのセットを作成できます。
// Abstract Product classes class Button { render() {} }
class Checkbox { render() {} }// Concrete Product classes class MacButton extends Button { render() { return 'Render Mac button'; } }class MacCheckbox extends Checkbox { render() { return 'Render Mac checkbox'; } }class WindowsButton extends Button { render() { return 'Render Windows button'; } }class WindowsCheckbox extends Checkbox { render() { return 'Render Windows checkbox'; } }// Abstract Factory interface class GUIFactory { createButton() {} createCheckbox() {} }// Concrete Factories class MacFactory extends GUIFactory { createButton() { return new MacButton(); } createCheckbox() { return new MacCheckbox(); } }class WindowsFactory extends GUIFactory { createButton() { return new WindowsButton(); } createCheckbox() { return new WindowsCheckbox(); } }// Usage function createUI(factory) { const button = factory.createButton(); const checkbox = factory.createCheckbox(); return { button, checkbox }; }const macUI = createUI(new MacFactory()); console.log(macUI.button.render()); // Output: 'Render Mac button' console.log(macUI.checkbox.render()); // Output: 'Render Mac checkbox'const windowsUI = createUI(new WindowsFactory()); console.log(windowsUI.button.render()); // Output: 'Render Windows button' console.log(windowsUI.checkbox.render()); // Output: 'Render Windows checkbox'
この例では、MacFactory と WindowsFactory という 2 つの具体的なファクトリがあり、それぞれのプラットフォームに関連する UI コンポーネント (ボタンとチェックボックス) のセットを作成できます。 createUI 関数を使用すると、適切なファクトリを使用して特定のプラットフォーム用の一貫した UI を作成できます。
いつどのパターンを使用するか:
ビルダー パターンは、複雑なオブジェクトの構築をその表現から分離する創造的なデザイン パターンであり、同じ構築プロセスで異なる表現を作成できるようにします。このパターンは、オブジェクトに多数のプロパティがあり、柔軟性を維持しながらインスタンスの作成を簡素化したい場合に特に便利です。
JavaScript では、ビルダー パターンは、複雑なオブジェクトの段階的な構築をガイドするビルダー クラスまたはオブジェクトを使用して実装されることがよくあります。それがどのように機能するかを理解するために、例を見てみましょう。
// Product class with multiple properties class Product { constructor() { this.name = ''; this.price = 0; this.color = 'white'; // ... other properties }
// Additional methods can be defined here }// Builder for creating Product instances class ProductBuilder { constructor() { this.product = new Product(); } setName(name) { this.product.name = name; return this; // Return the builder for method chaining } setPrice(price) { this.product.price = price; return this; } setColor(color) { this.product.color = color; return this; } // Other methods to set additional properties build() { return this.product; // Return the fully constructed product } }// Usage const builder = new ProductBuilder();const productA = builder .setName('Product A') .setPrice(99.99) .setColor('blue') .build();const productB = builder .setName('Product B') .setPrice(49.99) .build();console.log(productA); console.log(productB);
この例では、複数のプロパティを持つ Product クラスがあります。 ProductBuilder クラスは、各プロパティを段階的に設定するメソッドを提供することで、Product のインスタンスの作成を支援します。メソッドチェーンを使用すると、複数のプロパティを滑らかで読みやすい方法で設定できます。最後に、build メソッドは完全に構築された Product インスタンスを返します。
ビルダー パターンは、次のようなさまざまなシナリオで役立ちます。
ビルダー パターンには多くの利点がありますが、特に構築されるオブジェクトが比較的単純な場合、コードベースが複雑になることに注意することが重要です。したがって、Builder によってもたらされる複雑さが特定の使用例にとって正当であるかどうかを評価することが重要です。
プロトタイプ パターンは、プロトタイプと呼ばれる既存のオブジェクトをコピーすることで新しいオブジェクトを作成できる創造的なデザイン パターンです。作成するオブジェクトの正確なクラスを指定せずに、オブジェクトの作成を促進します。このパターンは、複雑なオブジェクトのインスタンスを効率的に作成する場合に特に便利です。
JavaScript では、プロトタイプ パターンは、組み込みのprototype
プロパティおよびObject.create()
メソッドと密接に関連しています。 JavaScript でプロトタイプ パターンを実装して使用する方法を見てみましょう。
// Prototype object const vehiclePrototype = { init(make, model) { this.make = make; this.model = model; }, getDetails() { return `${this.make} ${this.model}`; }, };
// Create new instances using the prototype const car1 = Object.create(vehiclePrototype); car1.init('Toyota', 'Camry');const car2 = Object.create(vehiclePrototype); car2.init('Honda', 'Civic');console.log(car1.getDetails()); // Output: 'Toyota Camry' console.log(car2.getDetails()); // Output: 'Honda Civic'
この例では、すべての車両に共通のメソッドとプロパティを使用してvehiclePrototype
オブジェクトを定義します。 Object.create()
を使用して、このプロトタイプに基づいて新しいインスタンス (car1 および car2) を作成します。これらのインスタンスはプロトタイプからプロパティとメソッドを継承するため、動作を共有する新しいオブジェクトを効率的に作成できます。
プロトタイプ パターンは、次のようなさまざまなシナリオで役立ちます。
プロトタイプ パターンは便利ですが、いくつかの考慮事項があります。
オブジェクト プール パターンは、再利用可能なオブジェクトのプールを管理して、オブジェクトの作成と破棄のオーバーヘッドを最小限に抑える創造的なデザイン パターンです。これは、オブジェクトの作成と破棄に費用がかかる場合やリソースを大量に消費する場合に特に便利です。オブジェクト プール パターンは、新しいオブジェクトを最初から作成するのではなく、オブジェクトをリサイクルして再利用することで、パフォーマンスとリソースの使用率を向上させるのに役立ちます。
JavaScript では、配列またはカスタム プール管理クラスを使用してオブジェクト プール パターンを実装できます。簡単な例を使用して、このパターンがどのように機能するかを見てみましょう。
class ObjectPool { constructor(maxSize) { this.maxSize = maxSize; this.pool = []; }
create() { if (this.pool.length < this.maxSize) { // Create a new object and add it to the pool const obj = { /* Your object initialization code here */ }; this.pool.push(obj); return obj; } else { // Pool is full, cannot create more objects console.log('Pool is full. Cannot create more objects.'); return null; } } reuse() { if (this.pool.length > 0) { // Reuse an object from the pool return this.pool.pop(); } else { // Pool is empty, no objects available for reuse console.log('Pool is empty. No objects available for reuse.'); return null; } } release(obj) { // Release an object back to the pool for reuse this.pool.push(obj); } }// Usage const pool = new ObjectPool(5); // Create a pool with a maximum size of 5 objectsconst obj1 = pool.create(); const obj2 = pool.create(); const obj3 = pool.create();pool.release(obj2); // Release obj2 back to the pool for reuseconst obj4 = pool.reuse(); // Reuse an object from the pool (obj2)
この例では、オブジェクトのプールを管理する ObjectPool クラスを作成します。 create メソッドはプールがいっぱいでないときに新しいオブジェクトを作成し、reuse メソッドは再利用するためにプールからオブジェクトを取得し、release メソッドは将来の使用のためにオブジェクトをプールに返します。
オブジェクト プール パターンは、次のようなさまざまなシナリオで役立ちます。
オブジェクト プール パターンはパフォーマンス上の利点をもたらしますが、次の点を考慮することが重要です。
アダプター パターンは、互換性のないインターフェイスを持つオブジェクトが連携できるようにする構造設計パターンです。これは、互換性のない 2 つのインターフェイス間のブリッジとして機能し、ソース コードを変更せずに互換性を持たせます。このパターンは、アプリケーションの要件に適合しない既存のコードを統合または使用する必要がある場合に特に便利です。
JavaScript では、互換性のないインターフェイスをラップまたは適応させるクラスまたは関数を使用してアダプター パターンを実装できます。実際の例を使用して、JavaScript でアダプター パターンを実装して使用する方法を見てみましょう。
OldSystem
という既存のクラスがあり、 legacyRequest
というメソッドがあるとします。
class OldSystem { legacyRequest() { return 'Data from the legacy system'; } }
ここで、このレガシー システムを、別のインターフェイスを必要とする最新のアプリケーションで使用したいと考えています。次のようにアダプター クラスまたは関数を作成できます。
class Adapter { constructor(oldSystem) { this.oldSystem = oldSystem; }
newRequest() { const legacyData = this.oldSystem.legacyRequest(); // Adapt the data or perform any necessary transformations return `Adapted: ${legacyData}`; } }
これで、Adapter クラスを使用して、レガシー システムを最新のアプリケーションと互換性のあるものにすることができます。
const oldSystem = new OldSystem(); const adapter = new Adapter(oldSystem);
const result = adapter.newRequest(); console.log(result); // Output: 'Adapted: Data from the legacy system'
この例では、Adapter クラスが OldSystem をラップし、最新のアプリケーションと互換性のある新しいインターフェイス newRequest を提供します。
アダプター パターンは、次のようなさまざまなシナリオで役立ちます。
アダプター パターンは柔軟性と互換性を提供しますが、次のいくつかの点を考慮することが重要です。
デコレーター パターンは、既存のコードを変更せずに、オブジェクトに新しい動作や責任を動的に追加できる構造設計パターンです。これは、オブジェクトをデコレータ オブジェクトでラップすることにより、オブジェクトの機能を拡張する強力な方法です。このパターンは、「拡張にはオープンだが、変更にはクローズ」という原則を促進し、コアの実装を変更せずにオブジェクトに新しい機能を簡単に追加できるようにします。
JavaScript では、クラスとオブジェクトの合成を使用してデコレータ パターンを実装できます。実際の例を使用して、JavaScript でデコレータ パターンを実装して使用する方法を見てみましょう。
基本クラスCoffee
あるとします。
class Coffee { cost() { return 5; // Base cost of a regular coffee } } Now, you want to add decorators to your coffee to customize it with additional options, such as milk and sugar:
javascript Copy code class MilkDecorator { constructor(coffee) { this.coffee = coffee; } cost() { return this.coffee.cost() + 2; // Adding the cost of milk } }class SugarDecorator { constructor(coffee) { this.coffee = coffee; } cost() { return this.coffee.cost() + 1; // Adding the cost of sugar } }
次に、次のように装飾されたコーヒー インスタンスを作成できます。
const regularCoffee = new Coffee(); const coffeeWithMilk = new MilkDecorator(regularCoffee); const coffeeWithMilkAndSugar = new SugarDecorator(coffeeWithMilk);
console.log(regularCoffee.cost()); // Output: 5 console.log(coffeeWithMilk.cost()); // Output: 7 console.log(coffeeWithMilkAndSugar.cost()); // Output: 8
この例では、ベースのコーヒーを表す Coffee クラスがあります。 MilkDecorator クラスと SugarDecorator クラスは、コーヒー オブジェクトをラップし、それぞれミルクと砂糖のコストを基本コストに追加するデコレーターです。
デコレータ パターンは、次のようなさまざまなシナリオで役立ちます。
デコレータ パターンは多用途ですが、いくつかの考慮事項に留意することが重要です。
プロキシ パターンは、別のオブジェクトへのアクセスを制御するためのサロゲートまたはプレースホルダを提供する構造設計パターンです。これはターゲット オブジェクトの仲介またはラッパーとして機能し、追加の動作の追加、アクセスの制御、またはオブジェクトの作成の遅延を可能にします。プロキシ パターンは、遅延読み込み、アクセス制御、ロギングの実装など、さまざまなシナリオで役立ちます。
JavaScript では、組み込みのProxy
オブジェクトを使用してプロキシを作成できます。実際の例を使用して、JavaScript でプロキシ パターンを実装して使用する方法を見てみましょう。
必要な場合にのみ遅延ロードしたい、リソースを大量に消費するオブジェクトがあるとします。プロキシを使用して遅延読み込みを実現できます。
class ExpensiveResource { constructor() { console.log('Creating an expensive resource...'); }
fetchData() { console.log('Fetching data...'); } }class LazyResourceProxy { constructor() { this.resource = null; } fetchData() { if (!this.resource) { this.resource = new ExpensiveResource(); } this.resource.fetchData(); } }// Usage const lazyResource = new LazyResourceProxy(); // The actual resource is created and data is fetched only when needed lazyResource.fetchData();
この例では、LazyResourceProxy は ExpensiveResource の代理として機能し、 fetchData メソッドが初めて呼び出されたときにのみ実際のリソースを作成します。
プロキシを使用して、オブジェクトとそのプロパティへのアクセスを制御することもできます。
const user = { username: 'john_doe', password: 'secret123', };
const userProxy = new Proxy(user, { get(target, property) { if (property === 'password') { throw new Error('Access denied to password.'); } return target[property]; }, });console.log(userProxy.username); // Output: 'john_doe' console.log(userProxy.password); // Throws an error: 'Access denied to password.'
この例では、プロキシが get 操作をインターセプトし、パスワード プロパティへのアクセスを制限します。
プロキシ パターンは、次のようなさまざまなシナリオで役立ちます。
プロキシ パターンを使用する場合は、次の考慮事項に留意してください。
複合パターンは、オブジェクトをツリー状の構造に合成して部分全体の階層を表現できる構造設計パターンです。これにより、クライアントは個々のオブジェクトとオブジェクトの構成を均一に扱うことができます。複合パターンは、一貫したインターフェイスを維持しながら、より小さな関連オブジェクトで構成される複雑な構造を操作する必要がある場合に特に便利です。
JavaScript では、共通のインターフェイスを共有するクラスまたはオブジェクトを使用して複合パターンを実装し、階層構造を構築できます。実際の例を使用して、JavaScript で複合パターンを実装して使用する方法を見てみましょう。
単純な形状と形状の複雑な構成 (グループなど) の両方を操作する必要があるグラフィック デザイン アプリケーションを構築しているとします。複合パターンを使用して、この階層を表すことができます。
// Component interface class Graphic { draw() {} }
// Leaf class (represents simple shapes) class Circle extends Graphic { constructor() { super(); // Circle-specific properties and methods } draw() { // Draw a circle } }// Composite class (represents groups of shapes) class Group extends Graphic { constructor() { super(); this.graphics = []; } add(graphic) { this.graphics.push(graphic); } draw() { // Draw each graphic in the group this.graphics.forEach((graphic) => graphic.draw()); } }// Usage const circle1 = new Circle(); const circle2 = new Circle(); const group = new Group();group.add(circle1); group.add(circle2);group.draw(); // Draws both circles in the group
この例では、Graphic クラスがコンポーネント インターフェイスとして機能します。 Circle クラスは単純な形状を表し、Group クラスは形状の構成を表します。 Circle クラスと Group クラスは両方とも描画メソッドを実装しているため、レンダリング時にそれらを均一に扱うことができます。
複合パターンは、次のようなさまざまなシナリオで役立ちます。
複合パターンを使用する場合は、次の点を考慮してください。
ブリッジ パターンは、オブジェクトの抽象化をその実装から分離する構造設計パターンです。これにより、2 つの間にブリッジを作成し、それらを独立して変更できるようになります。このパターンは、抽象化とその実装の間の永続的なバインディングを回避し、コードをより柔軟で保守しやすくする場合に特に役立ちます。
JavaScript では、抽象化のための抽象インターフェイスとさまざまなプラットフォームや機能のさまざまな具体的な実装を提供するクラスとオブジェクトを使用してブリッジ パターンを実装できます。実際の例を使用して、JavaScript でブリッジ パターンを実装して使用する方法を見てみましょう。
Web ブラウザやモバイル デバイスなど、さまざまなプラットフォームで図形をレンダリングできる描画アプリケーションを構築しているとします。ブリッジ パターンを使用すると、描画シェイプ (抽象化) をレンダリング ロジック (実装) から分離できます。
// Abstraction class Shape { constructor(renderer) { this.renderer = renderer; }
draw() { // Delegating the drawing to the specific renderer this.renderer.renderShape(this); } }// Implementor interface class Renderer { renderShape(shape) {} }// Concrete Implementors class WebRenderer extends Renderer { renderShape(shape) { console.log(`Drawing on the web: ${shape.constructor.name}`); } }class MobileRenderer extends Renderer { renderShape(shape) { console.log(`Drawing on mobile: ${shape.constructor.name}`); } }// Concrete Abstractions (Shapes) class Circle extends Shape { constructor(renderer) { super(renderer); } }class Square extends Shape { constructor(renderer) { super(renderer); } }// Usage const webRenderer = new WebRenderer(); const mobileRenderer = new MobileRenderer();const circle = new Circle(webRenderer); const square = new Square(mobileRenderer);circle.draw(); // Output: Drawing on the web: Circle square.draw(); // Output: Drawing on mobile: Square
この例では、Shape クラスは抽象化 (描画される形状) を表し、Renderer クラスは実装インターフェイス (プラットフォーム固有のレンダリング ロジック) を表します。さまざまな具体的な実装者 (WebRenderer と MobileRenderer) が、それぞれ Web プラットフォームとモバイル プラットフォームのレンダリング ロジックを提供します。 Circle クラスと Square クラスは、形状を表す具体的な抽象化です。
ブリッジ パターンは、次のようなさまざまなシナリオで役立ちます。
ブリッジ パターンを使用する場合は、次の点を考慮してください。
フライウェイト パターンは、オブジェクトの共通部分を共有することでメモリ消費量を削減し、パフォーマンスを向上させることを目的とした構造設計パターンです。これは、オブジェクトの固有状態 (共有および不変) を外部状態 (固有でコンテキスト依存) から分離することによって実現されます。このパターンは、類似したオブジェクトが多数あり、メモリ フットプリントを最小限に抑えたい場合に特に便利です。
JavaScript では、共有の固有状態と個別の外部状態を表すクラスまたはオブジェクトを使用して Flyweight パターンを実装できます。実際の例を使用して、JavaScript で Flyweight パターンを実装して使用する方法を見てみましょう。
大量のテキストを表示する必要があるテキスト エディタを開発しているとします。キャラクタごとに個別のオブジェクトを作成する代わりに、同じ固有のプロパティ (フォントやサイズなど) を持つキャラクタ オブジェクトを共有するために Flyweight パターンを使用できます。
class Character { constructor(char, font, size) { this.char = char; this.font = font; this.size = size; }
render() { console.log(`Rendering character "${this.char}" in ${this.font}, size ${this.size}`); } }class CharacterFactory { constructor() { this.characters = {}; } getCharacter(char, font, size) { const key = `${char}-${font}-${size}`; if (!this.characters[key]) { this.characters[key] = new Character(char, font, size); } return this.characters[key]; } }// Usage const factory = new CharacterFactory();const charA1 = factory.getCharacter('A', 'Arial', 12); const charA2 = factory.getCharacter('A', 'Arial', 12); const charB = factory.getCharacter('B', 'Times New Roman', 14);charA1.render(); // Output: Rendering character "A" in Arial, size 12 charA2.render(); // Output: Rendering character "A" in Arial, size 12 (shared instance) charB.render(); // Output: Rendering character "B" in Times New Roman, size 14
この例では、Character クラスは、文字自体、フォント、サイズなどの固有のプロパティを持つ個々の文字を表します。 CharacterFactory クラスは、同じ固有プロパティを持つ文字が重複するのではなく共有されることを保証します。
フライウェイト パターンは、次のようなさまざまなシナリオで役立ちます。
フライウェイト パターンを使用する場合は、次の点を考慮してください。
オブザーバー パターンは、オブジェクト間に 1 対多の依存関係を確立する動作設計パターンです。これにより、1 つのオブジェクト (サブジェクトまたはオブザーバブル) がその状態やデータの変化を複数のオブザーバー (リスナー) に通知できるようになります。このパターンは、分散イベント処理システムの実装によく使用され、1 つのオブジェクトの状態変化が他の依存オブジェクトのアクションをトリガーします。
JavaScript では、カスタム クラスや、イベント リスナーやaddEventListener
メソッドなどの組み込み機能を使用して、オブザーバー パターンを実装できます。実際の例を使用して、JavaScript で Observer パターンを実装して使用する方法を見てみましょう。
天気アプリケーションを構築していて、気象条件が変化したときに UI のさまざまな部分を更新したいとします。 Observer パターンのカスタム実装を使用できます。
class WeatherStation { constructor() { this.observers = []; }
addObserver(observer) { this.observers.push(observer); } removeObserver(observer) { const index = this.observers.indexOf(observer); if (index !== -1) { this.observers.splice(index, 1); } } notifyObservers() { this.observers.forEach((observer) => { observer.update(this); }); } setWeatherData(weatherData) { this.weatherData = weatherData; this.notifyObservers(); } }class WeatherDisplay { update(weatherStation) { console.log(`Current weather: ${weatherStation.weatherData}`); } }// Usage const weatherStation = new WeatherStation(); const display1 = new WeatherDisplay(); const display2 = new WeatherDisplay();weatherStation.addObserver(display1); weatherStation.addObserver(display2);weatherStation.setWeatherData('Sunny'); // Both displays update with the new weather data
この例では、WeatherStation は、気象データが変化したときに観測者 (表示オブジェクト) に通知するサブジェクトとして機能します。オブザーバーは、addObserver メソッドを使用してサブジェクトをサブスクライブし、変更に反応するために update メソッドを実装します。
JavaScript には、イベント リスナーを使用してオブザーバー パターンを実装する組み込みの方法も用意されています。
class NewsPublisher { constructor() { this.subscribers = []; }
subscribe(subscriber) { this.subscribers.push(subscriber); } unsubscribe(subscriber) { const index = this.subscribers.indexOf(subscriber); if (index !== -1) { this.subscribers.splice(index, 1); } } publishNews(news) { this.subscribers.forEach((subscriber) => { subscriber(news); }); } }// Usage const publisher = new NewsPublisher();const subscriber1 = (news) => { console.log(`Subscriber 1 received news: ${news}`); };const subscriber2 = (news) => { console.log(`Subscriber 2 received news: ${news}`); };publisher.subscribe(subscriber1); publisher.subscribe(subscriber2);publisher.publishNews('Breaking News: Important Announcement');
この例では、NewsPublisher がサブジェクトとして機能し、subscribe メソッドを使用して購読者 (関数) が追加されます。 pubNews メソッドは、ニュースで関数を呼び出すことによって購読者に通知します。
オブザーバー パターンは、次のようなさまざまなシナリオで役立ちます。
オブザーバー パターンを使用する場合は、次の点を考慮してください。
戦略パターンは、交換可能なアルゴリズムのファミリーを定義し、それぞれをカプセル化し、交換可能にすることを可能にする動作設計パターンです。これにより、クライアントは実行時に適切なアルゴリズムを動的に選択できるようになります。このパターンは、アルゴリズムの動作をそれを使用するコンテキストから分離することにより、柔軟性と再利用性を促進します。
JavaScript では、さまざまな戦略を表すオブジェクトまたは関数と、これらの戦略を切り替えることができるコンテキスト オブジェクトを使用して戦略パターンを実装できます。実際の例を使用して、JavaScript で戦略パターンを実装して使用する方法を見てみましょう。
電子商取引アプリケーションを開発していて、さまざまなタイプの顧客に対する割引を計算したいとします。戦略パターンを使用して、割引戦略をカプセル化できます。
// Discount Strategies const regularCustomerDiscount = (amount) => amount * 0.1; // 10% discount const premiumCustomerDiscount = (amount) => amount * 0.2; // 20% discount
// Context class ShoppingCart { constructor(discountStrategy) { this.items = []; this.discountStrategy = discountStrategy; } addItem(item) { this.items.push(item); } calculateTotal() { const subtotal = this.items.reduce((total, item) => total + item.price, 0); return subtotal - this.discountStrategy(subtotal); } }// Usage const regularCustomerCart = new ShoppingCart(regularCustomerDiscount); const premiumCustomerCart = new ShoppingCart(premiumCustomerDiscount);regularCustomerCart.addItem({ name: 'Item 1', price: 50 }); premiumCustomerCart.addItem({ name: 'Item 2', price: 100 });console.log(`Regular Customer Total: $${regularCustomerCart.calculateTotal()}`); // Output: $45 (after 10% discount) console.log(`Premium Customer Total: $${premiumCustomerCart.calculateTotal()}`); // Output: $80 (after 20% discount)
この例では、2 つの割引戦略を関数として定義します (normalCustomerDiscount と premiumCustomerDiscount)。 ShoppingCart クラスは、割引戦略をパラメーターとして受け取り、選択された戦略に基づいて合計価格を計算します。
戦略パターンは、次のようなさまざまなシナリオで役立ちます。
戦略パターンを使用する場合は、次の点を考慮してください。
コマンド パターンは、リクエストまたは単純な操作をスタンドアロン オブジェクトに変換する動作設計パターンです。これにより、さまざまなリクエストを持つオブジェクトをパラメータ化したり、リクエストの実行を遅延またはキューに入れたり、取り消し可能な操作をサポートしたりすることができます。このパターンでは、リクエストの送信者と受信者が切り離され、コードの拡張と保守が容易になります。
JavaScript では、コマンドとそれらのコマンドを実行する呼び出し元を表すオブジェクトまたはクラスを使用してコマンド パターンを実装できます。実際の例を使用して、JavaScript でコマンド パターンを実装して使用する方法を見てみましょう。
スマート ホーム用のリモート コントロール アプリケーションを開発していて、さまざまなデバイスを制御する柔軟な方法を作成したいとします。
コマンド パターンを使用できます。
// Command interface class Command { execute() {} }
// Concrete Commands class LightOnCommand extends Command { constructor(light) { super(); this.light = light; } execute() { this.light.turnOn(); } }class LightOffCommand extends Command { constructor(light) { super(); this.light = light; } execute() { this.light.turnOff(); } }// Receiver (Device) class Light { turnOn() { console.log('Light is on.'); } turnOff() { console.log('Light is off.'); } }// Invoker (Remote Control) class RemoteControl { constructor() { this.commands = []; } addCommand(command) { this.commands.push(command); } executeCommands() { this.commands.forEach((command) => { command.execute(); }); } }// Usage const livingRoomLight = new Light(); const kitchenLight = new Light();const livingRoomLightOn = new LightOnCommand(livingRoomLight); const livingRoomLightOff = new LightOffCommand(livingRoomLight); const kitchenLightOn = new LightOnCommand(kitchenLight); const kitchenLightOff = new LightOffCommand(kitchenLight);const remoteControl = new RemoteControl();remoteControl.addCommand(livingRoomLightOn); remoteControl.addCommand(kitchenLightOff);remoteControl.executeCommands(); // Output: "Light is on." (for living room) // Output: "Light is off." (for kitchen)
この例では、コマンド パターンを使用して、照明のオンとオフのアクションをカプセル化します。 RemoteControl は呼び出し側として機能し、具体的なコマンド (LightOnCommand や LightOffCommand など) が実行されるアクションをカプセル化します。
コマンド パターンは、次のようなさまざまなシナリオで役立ちます。
コマンド パターンを使用する場合は、次の点を考慮してください。
状態パターンは、内部状態が変化したときにオブジェクトの動作を変更できるようにする動作設計パターンです。状態を別のクラスとしてカプセル化し、動作を現在の状態オブジェクトに委任します。このパターンは、複雑な状態遷移の管理に役立ち、「オープン-クローズ」原則を促進するため、既存のコードを変更せずに新しい状態を簡単に追加できます。
JavaScript では、状態を表すクラスと、その動作を現在の状態に委任するコンテキスト オブジェクトを使用して状態パターンを実装できます。実際の例を使用して、JavaScript で状態パターンを実装して使用する方法を見てみましょう。
さまざまな製品を販売する自動販売機を開発しているとします。自動販売機の動作は、「準備完了」、「調剤中」、「売り切れ」などの現在の状態によって異なります。状態パターンを使用して、この動作をモデル化できます。
// State interface class VendingMachineState { insertMoney() {} ejectMoney() {} selectProduct() {} dispenseProduct() {} }
// Concrete States class ReadyState extends VendingMachineState { constructor(machine) { super(); this.machine = machine; } insertMoney() { console.log('Money inserted.'); this.machine.setState(this.machine.getDispensingState()); } selectProduct() { console.log('Please insert money first.'); } }class DispensingState extends VendingMachineState { constructor(machine) { super(); this.machine = machine; } dispenseProduct() { console.log('Product dispensed.'); this.machine.setState(this.machine.getReadyState()); } }class VendingMachine { constructor() { this.readyState = new ReadyState(this); this.dispensingState = new DispensingState(this); this.currentState = this.readyState; } setState(state) { this.currentState = state; } getReadyState() { return this.readyState; } getDispensingState() { return this.dispensingState; } insertMoney() { this.currentState.insertMoney(); } selectProduct() { this.currentState.selectProduct(); } dispenseProduct() { this.currentState.dispenseProduct(); } }// Usage const vendingMachine = new VendingMachine();vendingMachine.selectProduct(); // Output: "Please insert money first." vendingMachine.insertMoney(); // Output: "Money inserted." vendingMachine.dispenseProduct(); // Output: "Product dispensed."
この例では、自動販売機の動作を管理するために状態パターンが使用されます。 「準備完了」や「調剤中」などの状態は別のクラスとして表され、コンテキスト (自動販売機) がその動作を現在の状態に委任します。
状態パターンは、次のようなさまざまなシナリオで役立ちます。
状態パターンを使用する場合は、次の点を考慮してください。
Chain of Responsibility パターンは、リクエストを処理するオブジェクトのチェーンを構築するのに役立つ動作設計パターンです。チェーン内の各オブジェクトには、リクエストを処理するか、チェーン内の次のオブジェクトにリクエストを渡す機会があります。これにより、リクエストの送信者と受信者が切り離され、複数のハンドラーがチェーン内に存在できるようになります。このパターンでは、クライアント コードに影響を与えることなくハンドラーを追加または変更できるため、柔軟性と拡張性が向上します。
JavaScript では、ハンドラーとリクエストを開始するクライアントを表すオブジェクトまたはクラスを使用して、責任連鎖パターンを実装できます。各ハンドラーには、チェーン内の次のハンドラーへの参照があります。実際の例を使用して、JavaScript で責任連鎖パターンを実装して使用する方法を見てみましょう。
注文処理システムを開発していて、注文を合計金額に基づいて処理したいとします。責任チェーン パターンを使用して、特定の価格範囲内の注文の処理をそれぞれが担当するハンドラーのチェーンを作成できます。
// Handler interface class OrderHandler { constructor() { this.nextHandler = null; }
setNextHandler(handler) { this.nextHandler = handler; } handleOrder(order) { if (this.canHandleOrder(order)) { this.processOrder(order); } else if (this.nextHandler) { this.nextHandler.handleOrder(order); } else { console.log('No handler can process this order.'); } } canHandleOrder(order) {} processOrder(order) {} }// Concrete Handlers class SmallOrderHandler extends OrderHandler { canHandleOrder(order) { return order.amount <= 100; } processOrder(order) { console.log(`Processing small order for ${order.amount}`); } }class MediumOrderHandler extends OrderHandler { canHandleOrder(order) { return order.amount <= 500; } processOrder(order) { console.log(`Processing medium order for ${order.amount}`); } }class LargeOrderHandler extends OrderHandler { canHandleOrder(order) { return order.amount > 500; } processOrder(order) { console.log(`Processing large order for ${order.amount}`); } }// Client class Order { constructor(amount) { this.amount = amount; } }// Usage const smallOrderHandler = new SmallOrderHandler(); const mediumOrderHandler = new MediumOrderHandler(); const largeOrderHandler = new LargeOrderHandler();smallOrderHandler.setNextHandler(mediumOrderHandler); mediumOrderHandler.setNextHandler(largeOrderHandler);const order1 = new Order(80); const order2 = new Order(250); const order3 = new Order(600);smallOrderHandler.handleOrder(order1); // Output: "Processing small order for 80" smallOrderHandler.handleOrder(order2); // Output: "Processing medium order for 250" smallOrderHandler.handleOrder(order3); // Output: "Processing large order for 600"
この例では、責任連鎖パターンを使用して、さまざまな金額の注文を処理します。 SmallOrderHandler、MediumOrderHandler、LargeOrderHandler などのハンドラーはそれぞれ、注文の金額に基づいて注文を処理できるかどうかを決定します。可能であれば、処理します。それ以外の場合は、チェーン内の次のハンドラーに注文を渡します。
責任連鎖パターンは、次のようなさまざまなシナリオで役立ちます。
責任連鎖パターンを使用する場合は、次の点を考慮してください。
Visitor パターンは、アルゴリズムを、アルゴリズムが動作するオブジェクト構造から分離できるようにする動作設計パターンです。クラスを変更せずにオブジェクトに新しい操作を追加する方法を提供し、複雑なオブジェクト階層の機能を簡単に拡張できます。このパターンは、個別の要素のセットがあり、コードを変更せずにそれらに対してさまざまな操作を実行したい場合に特に便利です。
JavaScript では、オブジェクト構造内の要素を訪問する訪問者を表す関数またはクラスを使用して訪問者パターンを実装できます。実際の例を使用して、JavaScript で訪問者パターンを実装して使用する方法を見てみましょう。
記事、画像、ビデオなどのさまざまな種類のコンテンツ要素を含むコンテンツ管理システムを開発しているとします。これらの要素に対して、クラスを変更せずにレンダリングやエクスポートなどのさまざまな操作を実行したいと考えています。訪問者パターンを使用できます。
// Element interface class ContentElement { accept(visitor) {} }
// Concrete Elements class Article extends ContentElement { accept(visitor) { visitor.visitArticle(this); } }class Image extends ContentElement { accept(visitor) { visitor.visitImage(this); } }class Video extends ContentElement { accept(visitor) { visitor.visitVideo(this); } }// Visitor interface class Visitor { visitArticle(article) {} visitImage(image) {} visitVideo(video) {} }// Concrete Visitors class RendererVisitor extends Visitor { visitArticle(article) { console.log(`Rendering article: ${article.title}`); } visitImage(image) { console.log(`Rendering image: ${image.caption}`); } visitVideo(video) { console.log(`Rendering video: ${video.title}`); } }class ExportVisitor extends Visitor { visitArticle(article) { console.log(`Exporting article: ${article.title}`); } visitImage(image) { console.log(`Exporting image: ${image.caption}`); } visitVideo(video) { console.log(`Exporting video: ${video.title}`); } }// Usage const elements = [new Article('Article 1'), new Image('Image 1'), new Video('Video 1')]; const renderer = new RendererVisitor(); const exporter = new ExportVisitor();elements.forEach((element) => { element.accept(renderer); element.accept(exporter); });
この例では、Article、Image、Video などのコンテンツ要素があり、それらのクラスを変更せずにレンダリングおよびエクスポート操作を実行したいと考えています。これを実現するには、要素にアクセスして必要な操作を実行する RendererVisitor や ExportVisitor などのビジター クラスを実装します。
訪問者パターンは、次のようなさまざまなシナリオで役立ちます。
訪問者パターンを使用する場合は、次の点を考慮してください。
JavaScript のデザイン パターンの包括的な調査では、開発者が柔軟で保守しやすく効率的なコードを作成できるようにするさまざまなパターンを詳しく掘り下げました。各設計パターンは特定の問題に対処し、一般的なソフトウェア設計の課題に対する洗練されたソリューションを提供します。
私たちはデザイン パターンの基本概念を理解することから始め、デザイン パターンを作成パターン、構造パターン、動作パターンの 3 つの主要なグループに分類しました。各カテゴリ内で、人気のあるデザイン パターンを調査し、JavaScript での実際の実装を紹介しました。
ここで取り上げた主要な設計パターンを簡単に要約します。
作成パターン:これらのパターンは、クラスの単一インスタンスを保証するためのシングルトン パターン、柔軟なファクトリでオブジェクトを作成するためのファクトリ パターンおよび抽象ファクトリ パターン、複雑なオブジェクトを段階的に構築するためのビルダー パターン、クローン作成のためのプロトタイプ パターンなど、オブジェクト作成メカニズムに焦点を当てています。オブジェクト、およびオブジェクトを効率的に再利用するためのオブジェクト プール パターン。
構造パターン:これらのパターンはオブジェクトの構成を扱い、より単純なコンポーネントから複雑な構造を構築する方法を提供します。インターフェイスを適応させるためのアダプター パターン、オブジェクトに動作を動的に追加するためのデコレーター パターン、オブジェクトへのアクセスを制御するためのプロキシ パターン、オブジェクトをツリー構造に構成するための複合パターン、実装から抽象化を分離するためのブリッジ パターン、およびフライウェイトを検討しました。共通の状態を共有することでメモリ使用量を最小限に抑えるパターン。
行動パターン:これらのパターンは、オブジェクト間の相互作用とコミュニケーションに関係します。分散イベント処理システムを実装するためのオブザーバー パターン、交換可能なアルゴリズムをカプセル化するためのストラテジー パターン、リクエストをスタンドアロン オブジェクトに変換するためのコマンド パターン、内部状態に基づいてオブジェクトの動作を管理するための状態パターン、システムを構築するための責任連鎖パターンについて説明しました。リクエストを処理するためのハンドラーのチェーンと、オブジェクト構造からアルゴリズムを分離するための訪問者パターン。
デザイン パターンは、開発者ツールキットの貴重なツールであり、スケーラブルで保守可能なコードベースの作成を可能にします。これらのパターンを理解して JavaScript プロジェクトに適用すると、より効率的で適応性があり、堅牢なソフトウェアを作成できるようになります。
デザイン パターンは万能のソリューションではなく、その適用性はプロジェクトの特定の要件と課題によって異なることに注意してください。最良の結果を得るために、いつどのように適用するかを慎重に検討してください。
JavaScript 開発者として成長し続けるにつれて、これらの設計パターンをマスターすると、自信と創造性を持って複雑なソフトウェア設計の課題に取り組むことができるようになります。 Web アプリケーション、ゲーム エンジン、その他のソフトウェアを構築している場合でも、デザイン パターンは、エレガントで保守しやすいコードを作成する際の味方になります。コーディングを楽しんでください!
ここでも公開されています。