デザインパターンとは何ですか?
ソフトウェア開発の分野では、設計パターンは、頻繁に発生するコンテキスト内の設計問題の解決策である、反復可能なアーキテクチャ設計です。 設計パターンは、プロのソフトウェア開発者の経験の要約です。 設計パターンは、プログラムの作成に応じた一種のパターンと考えることができます。
設計パターンが必要な理由
多くのプログラマーは、デザインパターンは時間の無駄だと考えているか、単にそれらを正しく適用する方法を知らないかのいずれかです。 ただし、適切なパターンを使用すると、よりわかりやすく理解しやすいコードを作成するのに役立ちます。このコードは、その理解しやすさにより、保守が容易になります。
ここで最も重要なことは、おそらく、パターンを使用すると、ソフトウェア開発者が、他の人のコードを解析する場合など、非常に役立つ有名な用語の辞書のようなものになることです。 パターンは、プロジェクトのデバイスを把握しようとしている人にとって、プログラムの特定の断片の目的を明らかにします。
たとえば、「Decorator」パターンを使用すると、特定のコードがどのタスクを解決するのか、なぜそれが必要なのかをプロジェクトに来た新しいプログラマーにすぐに知らせます。 これにより、そのようなプログラマーは、内部構造を理解しようとするのではなく、プログラムが解決する実際のタスクにより多くの時間を割くことができます。
デザインパターンとその用途を理解したので、次にパターン自体とJavaScriptを使用した実装の説明に進みます。
パターン「モジュール」
モジュールは、他のプロジェクトコードに影響を与えることなく変更できる独立したコードです。 さらに、モジュールは、モジュールで宣言された変数に対して個別の可視領域を作成するため、可視領域の汚染などの現象を回避できます。 あるプロジェクト用に作成されたモジュールは、そのメカニズムが普遍的で、特定のプロジェクトの機能に結び付けられていない場合、他のプロジェクトで再利用できます。
モジュールは、最新のJavaScriptアプリケーションの不可欠な部分です。 これらは、コードのクリーンさを維持し、コードを意味のあるフラグメントに分離し、整理するのに役立ちます。 JavaScriptにはモジュールを作成する多くの方法があり、そのうちの1つは「モジュール」パターンです。
他のプログラミング言語とは異なり、JavaScriptにはアクセス修飾子がありません。 つまり、変数をプライベートまたはパブリックとして宣言することはできません。 その結果、「モジュール」パターンは、カプセル化の概念をエミュレートするためにも使用されます。
このパターンでは、IIFE(Immediately-Invoked Functional Expression)、クロージャー、および関数スコープを使用してこの概念を模倣します。 例:
const myModule = (function() { const privateVariable = 'Hello World'; function privateMethod() { console.log(privateVariable); } return { publicMethod: function() { privateMethod(); } } })(); myModule.publicMethod();
IIFEがあるので、コードはすぐに実行され、式によって返されるオブジェクトは定数
myModule
割り当てられます。 クロージャがあるという事実により、返されたオブジェクトは、IIFEが完了した後でも、IIFE内で宣言された関数と変数にアクセスできます。
その結果、IIFE内で宣言された変数と関数は、それらに関して外部スコープにあるメカニズムから隠されます。 これらは、
myModule
定数のプライベートエンティティであることが
myModule
。
このコードが実行されると、
myModule
は次のようになります。
const myModule = { publicMethod: function() { privateMethod(); }};
つまり、この定数を参照して、
publicMethod()
オブジェクトメソッドを呼び出すことができます。このメソッドは、
privateMethod()
プライベートメソッドを呼び出します。 例:
// 'Hello World' module.publicMethod();
オープンモジュールパターン
Revealing Moduleパターンは、Christian Heilmannが提案したModuleパターンのわずかに改善されたバージョンです。 「モジュール」パターンの問題は、プライベート関数と変数にアクセスするためだけにパブリック関数を作成する必要があることです。
このパターンでは、公開したい返されたオブジェクトのプロパティにプライベート関数を割り当てます。 そのため、このパターンは「オープンモジュール」と呼ばれます。 例を考えてみましょう:
const myRevealingModule = (function() { let privateVar = 'Peter'; const publicVar = 'Hello World'; function privateFunction() { console.log('Name: '+ privateVar); } function publicSetName(name) { privateVar = name; } function publicGetName() { privateFunction(); } /** , */ return { setName: publicSetName, greeting: publicVar, getName: publicGetName }; })(); myRevealingModule.setName('Mark'); // Name: Mark myRevealingModule.getName();
このパターンを適用すると、モジュールのどの関数と変数が公開されているかがわかりやすくなり、コードの可読性が向上します。
IIFEの実行後、
myRevealingModule
は次のようになります。
const myRevealingModule = { setName: publicSetName, greeting: publicVar, getName: publicGetName };
たとえば、
myRevealingModule.setName('Mark')
メソッドを呼び出すことができます。これは、
publicSetName
内部関数への参照です。
myRevealingModule.getName()
メソッドは、内部関数
publicGetName
ます。 例:
myRevealingModule.setName('Mark'); // Name: Mark myRevealingModule.getName();
「モジュール」パターンよりも「モジュールを開く」パターンの利点を考慮してください。
- 「オープンモジュール」を使用すると、モジュールのパブリックな非表示エンティティを作成し(必要に応じて再び非表示にする)、IIFEの後に返されるオブジェクトの各行をそれぞれ変更できます。
- 返されたオブジェクトには関数定義が含まれていません。 プロパティ名の右側はすべてIIFEで定義されています。 これにより、コードをクリーンで読みやすく保つことができます。
ES6のモジュール
ES6標準がリリースされるまで、JavaScriptにはモジュールを操作するための標準ツールがありませんでした。そのため、開発者はサードパーティのライブラリまたは「モジュール」パターンを使用して適切なメカニズムを実装する必要がありました。 しかし、ES6の登場により、標準モジュールシステムがJavaScriptに登場しました。
ES6モジュールはファイルに保存されます。 1つのファイルに含めることができるモジュールは1つだけです。 モジュール内のすべてはデフォルトでプライベートです。 関数、変数、およびクラスは、
export
キーワードを使用して公開できます。 モジュール内のコードは常に厳格モードで実行されます。
▍エクスポートモジュール
モジュールで宣言された関数または変数をエクスポートするには、2つの方法があります。
- エクスポートは、関数または変数を宣言する前に
export
キーワードを追加することにより行われます。 例:
// utils.js export const greeting = 'Hello World'; export function sum(num1, num2) { console.log('Sum:', num1, num2); return num1 + num2; } export function subtract(num1, num2) { console.log('Subtract:', num1, num2); return num1 - num2; } // - function privateLog() { console.log('Private Function'); }
- エクスポートは、
export
される関数と変数の名前をリストするコードの最後にexport
キーワードを追加することにより行われます。 例:
// utils.js function multiply(num1, num2) { console.log('Multiply:', num1, num2); return num1 * num2; } function divide(num1, num2) { console.log('Divide:', num1, num2); return num1 / num2; } // function privateLog() { console.log('Private Function'); } export {multiply, divide};
▍インポートモジュール
エクスポートする方法が2つあるように、モジュールをインポートする方法も2つあります。 これは、
import
キーワードを使用して行われます。
- 選択した複数のアイテムをインポートします。 例:
// main.js // import { sum, multiply } from './utils.js'; console.log(sum(3, 7)); console.log(multiply(3, 7));
- モジュールがエクスポートするすべてをインポートします。 例:
// main.js // , import * as utils from './utils.js'; console.log(utils.sum(3, 7)); console.log(utils.multiply(3, 7));
exportedエクスポートおよびインポートされたエンティティのエイリアス
コードにエクスポートされた関数または変数の名前が衝突を引き起こす可能性がある場合、それらはエクスポート中またはインポート中に変更できます。
エクスポート中にエンティティの名前を変更するには、次を実行できます。
// utils.js function sum(num1, num2) { console.log('Sum:', num1, num2); return num1 + num2; } function multiply(num1, num2) { console.log('Multiply:', num1, num2); return num1 * num2; } export {sum as add, multiply};
インポート中にエンティティの名前を変更するには、次の構成が使用されます。
// main.js import { add, multiply as mult } from './utils.js'; console.log(add(3, 7)); console.log(mult(3, 7));
シングルトンパターン
「シングルトン」または「シングルトン」パターンは、単一のコピーにのみ存在できるオブジェクトです。 このパターンのアプリケーションの一部として、特定のクラスの新しいインスタンスがまだ作成されていない場合は作成されます。 クラスインスタンスが既に存在する場合、コンストラクターにアクセスしようとすると、対応するオブジェクトへの参照が返されます。 コンストラクターへの後続の呼び出しは、常に同じオブジェクトを返します。
実際、「シングルトン」パターンと呼ばれるものは常にJavaScriptにありますが、「シングルトン」ではなく「オブジェクトリテラル」と呼ばれます。 例を考えてみましょう:
const user = { name: 'Peter', age: 25, job: 'Teacher', greet: function() { console.log('Hello!'); } };
JavaScriptの各オブジェクトは独自のメモリ領域を占有し、他のオブジェクトと共有しないため、
user
変数にアクセスするたびに、同じオブジェクトへのリンクを取得します。
シングルトンパターンは、コンストラクター関数を使用して実装できます。 次のようになります。
let instance = null; function User(name, age) { if(instance) { return instance; } instance = this; this.name = name; this.age = age; return instance; } const user1 = new User('Peter', 25); const user2 = new User('Mark', 24); // true console.log(user1 === user2);
コンストラクター関数が呼び出されると、最初に
instance
オブジェクトが存在するかどうかが確認されます。 対応する変数が初期化されていない場合、
this
instance
書き込まれ
instance
。 変数がすでにオブジェクトへの参照を持っている場合、コンストラクターは単に
instance
、つまり既存のオブジェクトへの参照を返します。
シングルトンパターンは、モジュールパターンを使用して実装できます。 例:
const singleton = (function() { let instance; function User(name, age) { this.name = name; this.age = age; } return { getInstance: function(name, age) { if(!instance) { instance = new User(name, age); } return instance; } } })(); const user1 = singleton.getInstance('Peter', 24); const user2 = singleton.getInstance('Mark', 26); // prints true console.log(user1 === user2);
ここでは、
singleton.getInstance()
メソッドを呼び出して、
user
新しいインスタンスを作成します。 オブジェクトのインスタンスが既に存在する場合、このメソッドは単純にそれを返します。 そのようなオブジェクトがまだない場合、メソッドは
User
コンストラクター関数を呼び出すことにより、オブジェクトの新しいインスタンスを作成します。
工場パターン
Factoryパターンは、ファクトリメソッドと呼ばれるものを使用してオブジェクトを作成します。 オブジェクトの作成に使用されるクラスまたはコンストラクター関数を指定する必要はありません。
このパターンは、作成のロジックを公開する必要がない場合にオブジェクトを作成するために使用されます。 特定の条件に応じて異なるオブジェクトを作成する必要がある場合は、Factoryパターンを使用できます。 例:
class Car{ constructor(options) { this.doors = options.doors || 4; this.state = options.state || 'brand new'; this.color = options.color || 'white'; } } class Truck { constructor(options) { this.doors = options.doors || 4; this.state = options.state || 'used'; this.color = options.color || 'black'; } } class VehicleFactory { createVehicle(options) { if(options.vehicleType === 'car') { return new Car(options); } else if(options.vehicleType === 'truck') { return new Truck(options); } } }
ここでは、特定の標準値の使用を提供する
Car
クラスと
Truck
クラスが作成されます。 これらは、
car
と
truck
オブジェクトを作成するために使用されます。
VehicleFactory
クラスもここで宣言されます。これは、
vehicleType
プロパティの分析に基づいて新しいオブジェクトを作成するために使用され、
options
を
options
してオブジェクトで返すオブジェクトの対応するメソッドに渡され
options
。 これをすべて使用する方法は次のとおりです。
const factory = new VehicleFactory(); const car = factory.createVehicle({ vehicleType: 'car', doors: 4, color: 'silver', state: 'Brand New' }); const truck= factory.createVehicle({ vehicleType: 'truck', doors: 2, color: 'white', state: 'used' }); // Car {doors: 4, state: "Brand New", color: "silver"} console.log(car); // Truck {doors: 2, state: "used", color: "white"} console.log(truck);
VehicleFactory
クラスの
factory
オブジェクトは、
VehicleFactory
で
VehicleFactory
。 その後、
factory.createVehicle()
メソッドを呼び出して、
vehicleType
プロパティが
car
または
truck
設定された
options
オブジェクトを渡すことにより、
Car
または
Truck
クラスのオブジェクトを作成できます。
デコレータパターン
Decoratorパターンは、既存のクラスまたはコンストラクター関数を変更せずにオブジェクトの機能を拡張するために使用されます。 このパターンを使用すると、オブジェクトの作成を担当するコードを変更せずに、特定の機能をオブジェクトに追加できます。
このパターンを使用する簡単な例を次に示します。
function Car(name) { this.name = name; // this.color = 'White'; } // , const tesla= new Car('Tesla Model 3'); // - tesla.setColor = function(color) { this.color = color; } tesla.setPrice = function(price) { this.price = price; } tesla.setColor('black'); tesla.setPrice(49000); // black console.log(tesla.color);
次に、このパターンの適用の実用的な例を考えてみましょう。 車のコストは、その機能、利用可能な追加機能に依存すると仮定します。 Decoratorパターンを使用せずにこれらの車を説明するには、これらの追加機能のさまざまな組み合わせに対して異なるクラスを作成する必要があり、それぞれに車のコストを見つける方法があります。 たとえば、次のようになります。
class Car() { } class CarWithAC() { } class CarWithAutoTransmission { } class CarWithPowerLocks { } class CarWithACandPowerLocks { }
問題のパターンのおかげで、基本クラスの
Car
を記述する基本クラス
Car
作成できます。この車の値は、一定の量で表されます。 その後、このクラスに基づいて作成された標準オブジェクトは、デコレータ関数を使用して展開できます。 この機能によって処理される標準の「車」は新しい機会を獲得し、さらにその価格に影響を与えます。 たとえば、このスキームは次のように実装できます。
class Car { constructor() { // this.cost = function() { return 20000; } } } // - function carWithAC(car) { car.hasAC = true; const prevCost = car.cost(); car.cost = function() { return prevCost + 500; } } // - function carWithAutoTransmission(car) { car.hasAutoTransmission = true; const prevCost = car.cost(); car.cost = function() { return prevCost + 2000; } } // - function carWithPowerLocks(car) { car.hasPowerLocks = true; const prevCost = car.cost(); car.cost = function() { return prevCost + 500; } }
ここでは、最初に基本クラス
Car
を作成します。これは、標準として車を表すオブジェクトを作成するために使用されます。 次に、基本プロパティ
Car
クラスのオブジェクトを追加のプロパティで拡張できるようにするいくつかのデコレータ関数を作成します。 これらの関数は、対応するオブジェクトをパラメーターとして受け取ります。 その後、オブジェクトに新しいプロパティを追加して、車にどの新しい機能を装備するかを示し、オブジェクトの
cost
関数を再定義します。これにより、車の新しいコストが返されます。 その結果、標準構成の車に新しいものを「装備」するために、次の設計を使用できます。
const car = new Car(); console.log(car.cost()); carWithAC(car); carWithAutoTransmission(car); carWithPowerLocks(car);
その後、改善された構成で自動車のコストを調べることができます。
// console.log(car.cost());
まとめ
この記事では、JavaScriptで使用されるいくつかの設計パターンを調べましたが、実際には、さまざまな問題を解決するために使用できるパターンがまだたくさんあります。
プログラマーにとってさまざまなデザインパターンの知識は重要ですが、それらの適切な使用も同様に重要です。 パターンとアプリケーションの範囲を知っているプログラマーは、自分の前でタスクを分析し、どのようなパターンがそれを解決するのに役立つかを理解できます。
親愛なる読者! 最も頻繁に使用するデザインパターンは何ですか?