クラシックおよびモダンなデザインパターンの JavaScript 実装について探ります。
最良のものはなく、最も適したものだけが存在する
デザインパターンは合計で 3 つの大カテゴリと 23 種類に分かれています。
パターンタイプ | デザインパターン |
---|---|
創造的パターン | シングルトンパターン、ファクトリーパターン、ビルダーパターン |
構造的パターン | アダプターパターン、デコレーターパターン、プロキシパターン |
行動的パターン | ストラテジーパターン、オブザーバーパターン、パブリッシュ・サブスクライブパターン、チェーンオブレスポンシビリティパターン、メディエーターパターン |
シングルトンパターン#
クラスは 1 つのインスタンスのみを持ち、そのインスタンスへのグローバルアクセス点を提供します。
-
Singleton
:特定のクラスで、私たちがアクセスする必要があるクラスで、訪問者が取得するのはそのインスタンスです; -
instance
:シングルトンで、特定のクラスのインスタンスで、特定のクラスは一般的にgetInstance
メソッドを提供してそのシングルトンを取得します; -
getInstance
:シングルトンを取得するためのメソッド;
class Singleton {
let _instance = null;
static getInstance() {
if (!Singleton._instance) {
Singleton.instance = new Singleton()
}
// この唯一のインスタンスが既に存在する場合は、直接返します
return Singleton._instance
}
}
const s1 = Singleton.getInstance()
const s2 = Singleton.getInstance()
console.log(s1 === s2) // true
インスタンス#
Vuex はアプリケーションのすべての状態を保存するためのグローバルストアを実装しています。このストアの実装はシングルトンパターンの典型的な応用です。
使用シーン#
-
もしクラスのインスタンス化プロセスがリソースを多く消費する場合、シングルトンを使用してパフォーマンスの無駄を避けることができます。
-
公共の状態が必要な場合、シングルトンを使用してアクセスの一貫性を保証できます。
ファクトリーパターン#
ファクトリーパターン:異なるパラメータに基づいて異なるクラスのインスタンスを返します。オブジェクトの作成とオブジェクトの実装を分離します。実装は複雑ですが、使用は簡単です。ファクトリーが提供するメソッドを直接使用するだけです
利点:
-
良好なカプセル化、訪問者は作成プロセスを理解する必要がなく、コード構造が明確です。
-
拡張性が良好で、ファクトリーメソッドによってユーザーと作成プロセスが隔離され、オープン・クローズド原則に従います。
欠点:
システムに抽象性を追加し、余分なシステムの複雑さをもたらし、乱用してはいけません。
インスタンス#
document.createElement
は DOM
要素を作成します。このメソッドはファクトリーパターンを採用しており、メソッド内部は非常に複雑ですが、外部での使用は非常に簡単です。
使用シーン#
-
オブジェクトの作成が非常に複雑で、訪問者は作成プロセスを理解する必要がありません。
-
同じ / 類似の属性を持つ小さなオブジェクトが多数ある場合。
アダプターパターン#
互換性の問題を解決するために使用され、インターフェース / メソッド / データが互換性がない場合、それを訪問者が期待する形式に変換して使用します。
シーンの特徴:
- サードパーティ SDK の統合
- 古いインターフェースのカプセル化
デコレーターパターン#
- 動的に特定のオブジェクトに追加の責任を付与するもので、継承の代替手段です。
- 元のオブジェクトを変更せずに、そのラッピングを拡張することで、元のオブジェクトがユーザーのより複雑な要求を満たすことができるようにし、このクラスから派生した他のオブジェクトに影響を与えません。
プロトタイプチェーンのような感じがあります。
プロキシパターン#
オブジェクトへのアクセスを制御するために、オブジェクトの代用品またはプレースホルダーを提供します。
使用シーン#
- ES6 のプロキシ
- jQuery.proxy () メソッド
デコレーターとプロキシパターンの違い#
- デコレーターパターン:機能を拡張し、元の機能は変わらず直接使用可能
- プロキシパターン:元の機能を表示しますが、制限された後のものです。
ストラテジーパターン#
一連のアルゴリズムを定義し、入力パラメータに基づいてどのアルゴリズムを使用するかを決定します。
インスタンス#
シーン:ダブルイレブンの割引イベント。200 円以上で 20 円引き、300 円以上で 50 円引き、500 円以上で 100 円引き。この要求はどう書くべきか?
// if-else:冗長で、変更が難しい
function priceCalculate(discountType,price){
if(discountType === 'discount200-20'){
return price - Math.floor(price/200) * 20;
}else if(discountType === 'discount300-50'){
return price - Math.floor(price/300) * 50;
}else if(userType === 'discount500-100'){
return price - Math.floor(price/500) * 100;
}
}
//ストラテジーパターンに書き換え、アルゴリズムを隠し、戦略を追加するためのエントリを予約し、拡張しやすくします。
const priceCalculate = (function(){
const discountMap = {
'discount200-20': function(price) {
return price - Math.floor(price / 200) * 20;
},
'discount300-50': function(price) {
return price - Math.floor(price/300) * 50;
},
'discount500-100': function(price) {
return price - Math.floor(price/500) * 100;
},
};
return {
addStategy(stategyName,fn){
if(discountMap[stategyName]) return;
discountMap[stategyName] = fn;
},
priceCal(discountType,price){
return discountMap[discountType] && discountMap[discountType](price);
}
}
})()
利点:
- ストラテジーは相互に独立しており、切り替えが可能です。柔軟性と再利用性が向上します。
if-else
を使用してストラテジーを選択する必要がなく、メンテナンス性が向上します。- 拡張性が良好で、オープン・クローズド原則を満たします。
欠点:
- ストラテジーは相互に独立しており、いくつかの複雑なアルゴリズムロジックを共有できず、リソースの無駄を引き起こします。
- ユーザーはストラテジーを使用する際、具体的なストラテジーの実装を理解する必要があります。最小知識原則を満たさず、使用コストが増加します。
使用シーン#
- アルゴリズムを自由に切り替える必要があるシーン。
- 複数のアルゴリズムが行動上わずかに異なる場合、ストラテジーパターンを考慮して動的にアルゴリズムを選択できます。
- 複数の判断が必要な場合、ストラテジーパターンを考慮して多重条件判断を回避できます。
オブザーバーパターン#
1 つのオブジェクト(サブジェクトと呼ばれる)は、それに依存する一連のオブジェクト(オブザーバーと呼ばれる)を維持し、状態の変更を自動的にそれらに通知します(オブザーバー)。
利点と欠点#
利点:ターゲットが変化するとオブザーバーに通知されることが、オブザーバーパターンの最大の利点です。
欠点:ターゲットとオブザーバーは結合されており、オブザーバーパターンを実現するには、被観察者とオブザーバーの両方を同時に導入する必要があります。
使用シーン#
Bilibili のユーザーがオブザーバーで、Bilibili のアップ主が被観察者です。多くの Bilibili ユーザーが青春湖北というアップ主をフォローしており、このアップ主が動画を更新すると、これらのフォロワーに通知されます。
パブリッシュ・サブスクライブパターン#
特定のテーマに基づいて、通知を受け取りたいオブジェクト(サブスクライバーと呼ばれる)はカスタムイベントを介してテーマを購読し、イベントを発行するオブジェクト(パブリッシャーと呼ばれる)はテーマイベントを発行することで通知されます。
使用シーン#
WeChat では多くの公式アカウントをフォローしており、公式アカウントが新しい記事を公開すると、私たちに記事が更新されたことを通知するメッセージが届きます。
この場合、公式アカウントが発行者で、ユーザーがサブスクライバーです。ユーザーは公式アカウントのイベントをイベントスケジューリングセンターに登録し、発行者が新しい記事を公開すると、イベントスケジューリングセンターにイベントを発行し、スケジューリングセンターがサブスクライバーにメッセージを送信します。
Vue の双方向バインディングにおけるパブリッシュ・サブスクライブパターン#
Vue
の双方向バインディングは、データのハイジャックとパブリッシュ・サブスクライブパターンを通じて実現されています。
-
DefineProperty
を使用して各データのsetter
とgetter
をハイジャックし、各データにサブスクライバーリストを追加します。このリストは、そのデータに依存するすべてのコンポーネントを記録します。リアクティブデータはメッセージの発行者に相当します。
-
各コンポーネントは 1 つの
Watcher
サブスクライバーに対応し、コンポーネントのレンダリング関数が実行されると、そのコンポーネントのWatcher
が依存するリアクティブデータのサブスクライバーリストに追加されます。このプロセスを「依存関係の収集」と呼びます。
-
リアクティブデータが変更されると、
setter
が発生し、setter
はデータのサブスクライバーリスト内のWatcher
に通知し、Watcher
はコンポーネントを再レンダリングしてビューを更新します。ビュー層はメッセージのサブスクライバーに相当します。
オブザーバーパターンとパブリッシュ・サブスクライブの違い#
オブザーバーはクラシックなソフトウェアデザインパターンの 1 つですが、パブリッシュ・サブスクライブはソフトウェアアーキテクチャにおけるメッセージパラダイムの 1 つです。
オブザーバーパターン | パブリッシュ・サブスクライブ |
---|---|
2 つの役割 | 3 つの役割 |
重点は被観察者 | 重点はパブリッシュ・サブスクライブセンター |
観察者と被観察者の関係は、被観察者が積極的に
構築し、被観察者
は少なくとも 3 つのメソッド —— オブザーバーの追加、オブザーバーの削除、オブザーバーへの通知を持つ必要があります。
パブリッシュ・サブスクライブは、中心に基づいて全体のシステムを構築し、発行者
とサブスクライバー
は直接通信せず、発行者は発行するメッセージを中心に管理し、サブスクライバーは自分の状況に応じて中心のメッセージを必要に応じて購読します。
パブリッシュ・サブスクライブの実装は内部でオブザーバーパターン
を利用していますが、パブリッシュ・サブスクライブセンター
という中間層の出現により、製造者と消費者の通信管理がより管理可能で拡張可能になりました。