戦略とは何ですか?たとえば、どこかに旅行したい場合、実際の状況に基づいてルートを選択できます。
1. 戦略パターンの定義
時間がないけどお金は気にしないという場合は、飛行機を利用することもできます。
お金がない場合は、バスや電車を選択することもできます。
もしあなたが貧しいなら、自転車に乗るという選択もできます。
プログラミングでは、同様の状況によく遭遇します。特定の機能を実装するには、多くのオプションから選択できます。たとえば、ファイルを圧縮するプログラムは、zip アルゴリズムまたは gzip アルゴリズムのいずれかを選択できます。
定義: 戦略パターンは、相互に置き換えることができるように個別にカプセル化された一連のアルゴリズムを定義します。このパターンにより、アルゴリズムは顧客とは独立して変更されます。 Suanfan を使用している人。
戦略パターンには幅広い用途があります。ここでは、年末賞与の計算を例に説明します。
2. 年末ボーナスの例
多くの企業の年末ボーナスは、従業員の給与ベースと年末の業績に基づいて決定されます。例えば、業績Sの人の期末賞与は給与の4倍、業績Aの人の期末賞与は給与の3倍、業績Bの人の期末賞与は給与の3倍です。給料の2倍。財務部門から、従業員の年末ボーナスの計算を容易にするコードの提供を求められたとします。
1) 初期コード実装
各人のボーナス額を計算するために、calculateBonus という関数を作成できます。明らかに、calculateBonus 関数が正しく機能するには、従業員の給与額と業績評価グレードという 2 つのパラメータを受け取る必要があります。コードは次のとおりです:
var calculateBonus = function( performanceLevel, salary ){ if ( performanceLevel === 'S' ){ return salary * 4; } if ( performanceLevel === 'A' ){ return salary * 3; } if ( performanceLevel === 'B' ){ return salary * 2; } }; calculateBonus( 'B', 20000 ); // 输出:40000 calculateBonus( 'S', 6000 ); // 输出:24000
このコードは非常に単純ですが、明らかな欠点があることがわかります。
calculateBonus 関数は比較的大きく、 にはすべての論理分岐をカバーする必要がある多くの if-else ステートメントが含まれています。
calculateBonus 関数は柔軟性がありません。新しいパフォーマンス レベル C を追加する場合、またはパフォーマンス S のボーナス係数を 5 に変更したい場合は、calculateBonus の内部実装を深く掘り下げる必要があります。これはオープンクローズの原則に違反します。
アルゴリズムの再利用性が低い。ボーナスを計算するためのこれらのアルゴリズムをプログラム内の別の場所で再利用する必要がある場合はどうすればよいでしょうか。唯一のオプションはコピー&ペーストです。したがって、このコードをリファクタリングする必要があります。
2) 組み合わせた関数
を使用してコードをリファクタリングします。一般的に考えられる最も簡単な方法は、さまざまなアルゴリズムを小さな関数にカプセル化することです。これらの小さな関数には、どのアルゴリズムに対応するかが一目でわかります。プログラム内の他の場所で再利用することもできます。コードは次のとおりです:
var performanceS = function( salary ){ return salary * 4; }; var performanceA = function( salary ){ return salary * 3; }; var performanceB = function( salary ){ return salary * 2; }; var calculateBonus = function( performanceLevel, salary ){ if ( performanceLevel === 'S' ){ return performanceS( salary ); } if ( performanceLevel === 'A' ){ return performanceA( salary ); } if ( performanceLevel === 'B' ){ return performanceB( salary ); } }; calculateBonus( 'A' , 10000 ); // 输出:30000
現在、プログラムはある程度改善されていますが、この改善は非常に限定的であり、最も重要な問題はまだ解決されていません。それは、calculateBonus 関数がますます大きくなる可能性があり、システムが変更されると柔軟性に欠けることです。
3) 戦略パターンを使用してコードをリファクタリングします
考えた結果、戦略パターンを使用してコードをリファクタリングするという、より良い方法を思いつきました。戦略パターンとは、一連のアルゴリズムを定義し、それらを 1 つずつカプセル化することを指します。変化しない部分と変化する部分を分離することは、すべての設計パターンのテーマであり、戦略パターンも例外ではありません。戦略パターンの目的は、アルゴリズムの使用とアルゴリズムの実装を分離することです。
この例では、アルゴリズムの使用方法は同じであり、計算されたボーナス額は特定のアルゴリズムに基づいて取得されます。アルゴリズムの実装は異なり、変化しており、それぞれのパフォーマンスは異なる計算ルールに対応しています。
戦略パターンに基づくプログラムは少なくとも 2 つの部分から構成されます。最初の部分は一連の戦略クラスであり、戦略クラスは特定のアルゴリズムをカプセル化し、特定の計算プロセスを担当します。 2 番目の部分は環境クラス Context で、Context は顧客のリクエストを受け入れ、そのリクエストを特定の戦略クラスに委任します。これを行うには、ポリシー オブジェクトへの参照がコンテキスト内で維持される必要があることを意味します。
次に、戦略パターンを使用して上記のコードをリファクタリングします。最初のバージョンは、従来のオブジェクト指向言語での実装をモデルにして作られました。まず、各パフォーマンス計算ルールを対応する戦略クラスにカプセル化します:
var performanceS = function(){}; performanceS.prototype.calculate = function( salary ){ return salary * 4; }; var performanceA = function(){}; performanceA.prototype.calculate = function( salary ){ return salary * 3; }; var performanceB = function(){}; performanceB.prototype.calculate = function( salary ){ return salary * 2; };
次にボーナスの種類を定義します。 Bonus:
var Bonus = function(){ this.salary = null; //原始工资 this.strategy = null; //绩效等级对应的策略对象 }; Bonus.prototype.setSalary = function( salary ){ this.salary = salary; //设置员工的原始工资 }; Bonus.prototype.setStrategy = function( strategy ){ this.strategy = strategy; //设置员工绩效等级对应的策略对象 }; Bonus.prototype.getBonus = function(){ //取得奖金数额 return this.strategy.calculate( this.salary ); //把计算奖金的操作委托给对应的策略对象 };
在完成最终的代码之前,我们再来回顾一下策略模式的思想:
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
这句话如果说得更详细一点,就是:定义一系列的算法,把它们各自封装成策略类,算法被封装在策略类内部的方法里。在客户对Context发起请求的时候,Context总是把请求委托给这些策略对象中间的某一个进行计算。
“并且使它们可以相互替换”,这句话在很大程度上是相对于静态类型语言而言的。因为静态类型语言中有类型检查机制,所以各个策略类需要实现同样的接口。当它们的真正类型被隐藏在接口后面时,它们才能被相互替换。而在JavaScript这种“类型模糊”的语言中没有这种困扰,任何对象都可以被替换使用。因此,JavaScript中的“可以相互替换使用”表现为它们具有相同的目标和意图。
现在我们来完成这个例子中剩下的代码。先创建一个bonus对象,并且给bonus对象设置一些原始的数据,比如员工的原始工资数额。接下来把某个计算奖金的策略对象也传入bonus对象内部保存起来。当调用bonus.getBonus()来计算奖金的时候,bonus对象本身并没有能力进行计算,而是把请求委托给了之前保存好的策略对象:
var bonus = new Bonus(); bonus.setSalary( 10000 ); bonus.setStrategy( new performanceS() ); //设置策略对象 console.log( bonus.getBonus() ); // 输出:40000 bonus.setStrategy( new performanceA() ); //设置策略对象 console.log( bonus.getBonus() ); // 输出:30000
刚刚我们用策略模式重构了这段计算年终奖的代码,可以看到通过策略模式重构之后,代码变得更加清晰,各个类的职责更加鲜明。但这段代码是基于传统面向对象语言的模仿,下一节我们将了解用JavaScript实现的策略模式。
在5.1节中,我们让strategy对象从各个策略类中创建而来,这是模拟一些传统面向对象语言的实现。实际上在JavaScript语言中,函数也是对象,所以更简单和直接的做法是把strategy直接定义为函数:
var strategies = { "S": function( salary ){ return salary * 4; }, "A": function( salary ){ return salary * 3; }, "B": function( salary ){ return salary * 2; } };
同样,Context也没有必要必须用Bonus类来表示,我们依然用calculateBonus 函数充当Context来接受用户的请求。经过改造,代码的结构变得更加简洁:
var strategies = { "S": function( salary ){ return salary * 4; }, "A": function( salary ){ return salary * 3; }, "B": function( salary ){ return salary * 2; } }; var calculateBonus = function( level, salary ){ return strategies[ level ]( salary ); }; console.log( calculateBonus( 'S', 20000 ) ); // 输出: 80000 console.log( calculateBonus( 'A', 10000 ) ); // 输出: 30000
3、实例再讲解
一个小例子就能让我们一目了然。
回忆下jquery里的animate方法.
$( div ).animate( {"left: 200px"}, 1000, 'linear' ); //匀速运动 $( div ).animate( {"left: 200px"}, 1000, 'cubic' ); //三次方的缓动
这2句代码都是让div在1000ms内往右移动200个像素. linear(匀速)和cubic(三次方缓动)就是一种策略模式的封装.
再来一个例子. 很多页面都会有个即时验证的表单. 表单的每个成员都会有一些不同的验证规则.
比如姓名框里面, 需要验证非空,敏感词,字符过长这几种情况。 当然是可以写3个if else来解决,不过这样写代码的扩展性和维护性可想而知。如果表单里面的元素多一点,需要校验的情况多一点,加起来写上百个if else也不是没有可能。
所以更好的做法是把每种验证规则都用策略模式单独的封装起来。需要哪种验证的时候只需要提供这个策略的名字。就像这样:
nameInput.addValidata({ notNull: true, dirtyWords: true, maxLength: 30 }) 而notNull,maxLength等方法只需要统一的返回true或者false,来表示是否通过了验证。 validataList = { notNull: function( value ){ return value !== ''; }, maxLength: function( value, maxLen ){ return value.length() > maxLen; } }
可以看到,各种验证规则很容易被修改和相互替换。如果某天产品经理建议字符过长的限制改成60个字符。那只需要0.5秒完成这次工作。
大概内容就为大家介绍到这。
聊一聊题外话,马上2015年要过去了,大家的年终奖是不是很丰厚呀!!!
希望大家可以在这一年里有所收获,通过这篇文章也能有所收获,知道什么是策略模式,理解小编精心为大家准备的两个实例。