定義
defines a family of functionality, encapsulate each one, and make them interchangeable
定義一個(gè)抽象的算法類坤次,將具體的算法封裝成單獨(dú)的對象蝴光,使它們可以相互替換睹耐。
實(shí)列
無論是生活中還是程序中,我們都會面臨許許多多的問題舌胶,而解決問題的方式或方法往往又有很多棉安,這時(shí)就需要我們根據(jù)具體情況選擇一個(gè)合適的方式解決問題气破。
比如說请垛,每天上下班你都會面臨如何到達(dá)目的地的問題,是步行還是開車亦或是坐公交见咒,你都需要做出權(quán)衡取舍偿衰。同樣,程序中如果你要給一串無序的數(shù)字排序改览,那么你有冒泡排序下翎、快速排序、插入排序等等排序算法可以選者恃疯;
還有漏设,如果你要給一串字符加密,那么你同樣會有很多算法可選如:MD5今妄、SHA1郑口、DES。上面的例子都可以使用策略模式來實(shí)現(xiàn)盾鳞,接下來我們看看如何正確的使用策略模式犬性。
小故事
一周前,我?guī)屠习彘_發(fā)了一款計(jì)算稅后收入的軟件腾仅,當(dāng)時(shí)只能計(jì)算工資乒裆。
前天,他期望這軟件也能計(jì)算年終獎推励,因?yàn)楣べY和年終獎的算法不一樣鹤耍,所以當(dāng)天我就增加好了。
今天验辞,又和我說要增加稿黄,利息......,天知道他要加多少種跌造。
public class Calculator {
public Integer getRealIncome(Integer amount,String type){
Integer incomeTax= 0;
if("工資".equals(type)){
//......計(jì)算工資所得稅的算法
}else if("年終獎".equals(type)){
//......計(jì)算年終獎所得稅的算法
}else if("利息".equals(type)){
//......計(jì)算利息所得稅的算法
}
//稅后收入
return amount-incomeTax;
}
}
問題
故事中杆怕,有三種類型的收入:工資族购、年終獎、利息陵珍,分別對應(yīng)三種不同的算法寝杖。之后,很可能還會要求增加股息互纯、紅利等算法瑟幕,且不說這么多的算法耦合在一個(gè)上下文(IncomeTaxCalculator)中,會造成代碼膨脹留潦、難以維護(hù)收苏,
而且還會讓代碼失去擴(kuò)展性、穩(wěn)定性愤兵。比如說,你的同事接手了你的代碼排吴,他想不修改你代碼的前提下秆乳,給這個(gè)軟件擴(kuò)展一種新的收入類型,那該如何實(shí)現(xiàn)呢钻哩?
又或者屹堰,他開發(fā)的算法性能更好,想替換你的算法或者兩個(gè)都同時(shí)保留由客戶端根據(jù)具體情況進(jìn)行切換街氢,那又該怎么辦扯键?
最后,如果收入類型有很多且數(shù)量不固定珊肃,真這樣不停的用條件語句進(jìn)行擴(kuò)展荣刑,難免會殃及池魚。
因此伦乔,這種情況下我們應(yīng)該使用更具有擴(kuò)展性的方式——策略模式厉亏。
方案
策略模式,首先會將算法從上下文(Context)中提取出來并封裝到單獨(dú)的類中烈和,這個(gè)類被稱為策略類(Strategy)爱只;然后會維護(hù)一個(gè)指向策略對象的引用,并向客戶端暴露設(shè)置該引用的操作招刹,使客戶端在運(yùn)行時(shí)可以動態(tài)的切換策略恬试。
這樣,擴(kuò)展或修改算法時(shí)疯暑,只需新增或修改策略類训柴,而不是修改上下文的代碼。如此這般缰儿,也就不會影響其它算法畦粮,造成代碼膨脹、難以維護(hù)。
另外宣赔,客戶端可以在運(yùn)行時(shí)预麸,根據(jù)具體情況動態(tài)的切換策略;因?yàn)槿褰呗詫ο笫怯煽蛻舳藙?chuàng)建并傳遞給上下文的吏祸,上下文根本不知道算法的實(shí)現(xiàn)細(xì)節(jié)甚至連具體是哪一個(gè)策略都不知道,關(guān)于算法它只負(fù)責(zé)將計(jì)算任務(wù)委派給策略钩蚊,其它一概不知贡翘。
應(yīng)用
接下來,我們使用策略模式重構(gòu)一下"計(jì)算稅后收入的軟件"砰逻。
首先鸣驱,創(chuàng)建抽象的所得稅計(jì)算策略,它接收一個(gè)金額并返回該金額所要交納的所得稅蝠咆。
/**抽象所得稅計(jì)算策略*/
public interface TaxStrategy {
public Integer calculate(Integer amount);
}
然后踊东,創(chuàng)建各種收入類型對應(yīng)的策略類,它是抽象策略類的子類刚操,負(fù)責(zé)實(shí)現(xiàn)具體的算法闸翅。
/**工資所得稅計(jì)算策略*/
public class SalaryStrategy implements TaxStrategy {
@Override
public Integer calculate(Integer amount) {
System.out.println("計(jì)算薪資所得稅");
}
}
/**利息所得稅計(jì)算策略*/
public class InterestStrategy implements TaxStrategy {
@Override
public Integer calculate(Integer amount) {
System.out.println("計(jì)算利息所得稅");
}
}
/**年終獎所得稅計(jì)算策略*/
public class YearEndBonusStrategy implements TaxStrategy {
@Override
public Integer calculate(Integer amount) {
System.out.println("計(jì)算薪資所得稅");
}
}
現(xiàn)在,我們修改一下上下文類Calculator菊霜,讓它使用策略來計(jì)算各種收入的所得稅坚冀。
/**個(gè)稅計(jì)算器*/
public class Calculator {
protected TaxStrategy taxStrategy;
public void setTaxStrategy(TaxStrategy taxStrategy) {
this.taxStrategy = taxStrategy;
}
public Integer getRealIncome(Integer amount){
Integer incomeTax = taxStrategy.calculate(amount);
//稅后收入
return amount-incomeTax;
}
}
最后,我們在看看客戶端如何動態(tài)切換算法鉴逞。
public class Client {
public static void main(String[] args) {
Calculator calculator = new Calculator();
//計(jì)算年終獎的收入
calculator.setTaxStrategy(new YearEndBonusStrategy());
Integer bonusIncome = calculator.getRealIncome(10000);
/**動態(tài)切換策略*/
//用性能更好的算法計(jì)算年終獎
calculator.setTaxStrategy(new YearEndBonusV2Strategy());
Integer bonus = calculator.getRealIncome(10000);
}
}
結(jié)構(gòu)
抽象策略角色(Strategy) :聲明所有具體算法的共同接口记某,它是對上下文對象中共同行為的抽象。
具體策略角色(ConcreteStrategy):實(shí)現(xiàn)抽象策略接口构捡,負(fù)責(zé)實(shí)現(xiàn)具體的算法辙纬,算法細(xì)節(jié)對上下文對象不可見。
上下文角色(Context) :具體策略的觸發(fā)類叭喜,它持有一個(gè)指向Strategy抽象類的引用贺拣,并向客戶端暴露設(shè)置策略的操作,允許客戶端動態(tài)地改變策略捂蕴。
客戶端(Client) :上下文的使用類譬涡,它根據(jù)具體情況決定使用哪一個(gè)策略,然后傳遞給上下文對象啥辨。
/**抽象策略類*/
public interface Strategy {
public void operation();
}
/**具體策略類A*/
public class ConcreteStrategyA implements Strategy{
@Override
public void operation() {
System.out.println("ConcreteStrategyA");
}
}
/**具體策略類B*/
public class ConcreteStrategyB implements Strategy{
@Override
public void operation() {
System.out.println("ConcreteStrategyB");
}
}
/**上下文類*/
public class Context {
protected Strategy strategy;
public void setStrategy(Strategy strategy){
this.strategy = strategy;
}
public void doSomeThing(){
System.out.println("before operation");
strategy.operation();
System.out.println("after operation");
}
}
public class Client {
public static void main(String[] args) {
Context context = new Context();
//使用策略A
context.setStrategy(new ConcreteStrategyA());
context.doSomeThing();
//動態(tài)切換策略
context.setStrategy(new ConcreteStrategyB());
context.doSomeThing();
}
}
總結(jié)
當(dāng)一個(gè)對象的某個(gè)行為存在多種實(shí)現(xiàn)時(shí)涡匀,那么將它們封裝成一個(gè)單獨(dú)的對象,并使它們同屬于一個(gè)繼承等級結(jié)構(gòu)溉知。
這樣陨瘩,可以使上下文類不關(guān)心具體的實(shí)現(xiàn)細(xì)節(jié)以及一致地使用這些策略腕够,也能讓客戶端在運(yùn)行時(shí)動態(tài)的改變策略,還能在不修改上下文類的前提下擴(kuò)展策略舌劳。