JavaScriptデザインパターン

私たちが公開している翻訳の資料の著者は、プロジェクトを開始してもすぐにはコードを書き始めないと述べています。 まず、プロジェクトの目的と境界を決定し、次にプロジェクトが持つべき機会を特定します。 その後、すぐにコードを記述するか、それがかなり複雑なプロジェクトの場合は、その基礎を形成する適切な設計パターンを選択します。 この資料はJavaScriptのデザインパターンに関するものです。 主に初心者の開発者向けに設計されています。







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



ソフトウェア開発の分野では、設計パターンは、頻繁に発生するコンテキスト内の設計問題の解決策である、反復可能なアーキテクチャ設計です。 設計パターンは、プロのソフトウェア開発者の経験の要約です。 設計パターンは、プログラムの作成に応じた一種のパターンと考えることができます。



設計パターンが必要な理由



多くのプログラマーは、デザインパターンは時間の無駄だと考えているか、単にそれらを正しく適用する方法を知らないかのいずれかです。 ただし、適切なパターンを使用すると、よりわかりやすく理解しやすいコードを作成するのに役立ちます。このコードは、その理解しやすさにより、保守が容易になります。



ここで最も重要なことは、おそらく、パターンを使用すると、ソフトウェア開発者が、他の人のコードを解析する場合など、非常に役立つ有名な用語の辞書のようなものになることです。 パターンは、プロジェクトのデバイスを把握しようとしている人にとって、プログラムの特定の断片の目的を明らかにします。



たとえば、「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();
      
      





「モジュール」パターンよりも「モジュールを開く」パターンの利点を考慮してください。





ES6のモジュール



ES6標準がリリースされるまで、JavaScriptにはモジュールを操作するための標準ツールがありませんでした。そのため、開発者はサードパーティのライブラリまたは「モジュール」パターンを使用して適切なメカニズムを実装する必要がありました。 しかし、ES6の登場により、標準モジュールシステムがJavaScriptに登場しました。



ES6モジュールはファイルに保存されます。 1つのファイルに含めることができるモジュールは1つだけです。 モジュール内のすべてはデフォルトでプライベートです。 関数、変数、およびクラスは、 export



キーワードを使用して公開できます。 モジュール内のコードは常に厳格モードで実行されます。



▍エクスポートモジュール



モジュールで宣言された関数または変数をエクスポートするには、2つの方法があります。





▍インポートモジュール



エクスポートする方法が2つあるように、モジュールをインポートする方法も2つあります。 これは、 import



キーワードを使用して行われます。





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で使用されるいくつかの設計パターンを調べましたが、実際には、さまざまな問題を解決するために使用できるパターンがまだたくさんあります。



プログラマーにとってさまざまなデザインパターンの知識は重要ですが、それらの適切な使用も同様に重要です。 パターンとアプリケーションの範囲を知っているプログラマーは、自分の前でタスクを分析し、どのようなパターンがそれを解決するのに役立つかを理解できます。



親愛なる読者! 最も頻繁に使用するデザインパターンは何ですか?






All Articles