生活中的设计模式之策略模式

定义

defines a family of functionality, encapsulate each one, and make them interchangeable

定义一个抽象的算法类,将具体的算法封装成单独的对象,使它们可以相互替换。

实列

无论是生活中还是程序中,我们都会面临许许多多的问题,而解决问题的方式或方法往往又有很多,这时就需要我们根据具体情况选择一个合适的方式解决问题。
比如说,每天上下班你都会面临如何到达目的地的问题,是步行还是开车亦或是坐公交,你都需要做出权衡取舍。同样,程序中如果你要给一串无序的数字排序,那么你有冒泡排序、快速排序、插入排序等等排序算法可以选者;
还有,如果你要给一串字符加密,那么你同样会有很多算法可选如:MD5、SHA1、DES。上面的例子都可以使用策略模式来实现,接下来我们看看如何正确的使用策略模式。

小故事

一周前,我帮老板开发了一款计算税后收入的软件,当时只能计算工资。
前天,他期望这软件也能计算年终奖,因为工资和年终奖的算法不一样,所以当天我就增加好了。
今天,又和我说要增加,利息......,天知道他要加多少种。

public class Calculator {

    public Integer getRealIncome(Integer amount,String type){
        Integer incomeTax= 0;

        if("工资".equals(type)){
            //......计算工资所得税的算法
        }else if("年终奖".equals(type)){
            //......计算年终奖所得税的算法
        }else if("利息".equals(type)){
            //......计算利息所得税的算法
        }
        //税后收入
        return amount-incomeTax;
    }

}

问题

故事中,有三种类型的收入:工资、年终奖、利息,分别对应三种不同的算法。之后,很可能还会要求增加股息、红利等算法,且不说这么多的算法耦合在一个上下文(IncomeTaxCalculator)中,会造成代码膨胀、难以维护,
而且还会让代码失去扩展性、稳定性。比如说,你的同事接手了你的代码,他想不修改你代码的前提下,给这个软件扩展一种新的收入类型,那该如何实现呢?
又或者,他开发的算法性能更好,想替换你的算法或者两个都同时保留由客户端根据具体情况进行切换,那又该怎么办?
最后,如果收入类型有很多且数量不固定,真这样不停的用条件语句进行扩展,难免会殃及池鱼。

因此,这种情况下我们应该使用更具有扩展性的方式——策略模式。

方案

策略模式,首先会将算法从上下文(Context)中提取出来并封装到单独的类中,这个类被称为策略类(Strategy);然后会维护一个指向策略对象的引用,并向客户端暴露设置该引用的操作,使客户端在运行时可以动态的切换策略。
这样,扩展或修改算法时,只需新增或修改策略类,而不是修改上下文的代码。如此这般,也就不会影响其它算法,造成代码膨胀、难以维护。
另外,客户端可以在运行时,根据具体情况动态的切换策略;因为,策略对象是由客户端创建并传递给上下文的,上下文根本不知道算法的实现细节甚至连具体是哪一个策略都不知道,关于算法它只负责将计算任务委派给策略,其它一概不知。

应用

接下来,我们使用策略模式重构一下"计算税后收入的软件"。

首先,创建抽象的所得税计算策略,它接收一个金额并返回该金额所要交纳的所得税。

/**抽象所得税计算策略*/
public interface TaxStrategy {

    public Integer calculate(Integer amount);
}

然后,创建各种收入类型对应的策略类,它是抽象策略类的子类,负责实现具体的算法。


/**工资所得税计算策略*/
public class SalaryStrategy implements TaxStrategy {
    @Override
    public Integer calculate(Integer amount) {
        System.out.println("计算薪资所得税");
    }
}

/**利息所得税计算策略*/
public class InterestStrategy implements TaxStrategy {
    @Override
    public Integer calculate(Integer amount) {
        System.out.println("计算利息所得税");
    }
}

/**年终奖所得税计算策略*/
public class YearEndBonusStrategy implements TaxStrategy {
    @Override
    public Integer calculate(Integer amount) {
        System.out.println("计算薪资所得税");
    }
}


现在,我们修改一下上下文类Calculator,让它使用策略来计算各种收入的所得税。


/**个税计算器*/
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;
    }

}

最后,我们在看看客户端如何动态切换算法。


public class Client {

    public static void main(String[] args) {
        Calculator calculator = new Calculator();

        //计算年终奖的收入
        calculator.setTaxStrategy(new YearEndBonusStrategy());
        Integer bonusIncome = calculator.getRealIncome(10000);

        /**动态切换策略*/

        //用性能更好的算法计算年终奖
        calculator.setTaxStrategy(new YearEndBonusV2Strategy());
        Integer bonus = calculator.getRealIncome(10000);

    }
}

结构

avatar

抽象策略角色(Strategy) :声明所有具体算法的共同接口,它是对上下文对象中共同行为的抽象。

具体策略角色(ConcreteStrategy):实现抽象策略接口,负责实现具体的算法,算法细节对上下文对象不可见。

上下文角色(Context) :具体策略的触发类,它持有一个指向Strategy抽象类的引用,并向客户端暴露设置策略的操作,允许客户端动态地改变策略。

客户端(Client) :上下文的使用类,它根据具体情况决定使用哪一个策略,然后传递给上下文对象。

/**抽象策略类*/
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();

        //动态切换策略
        context.setStrategy(new ConcreteStrategyB());
        context.doSomeThing();
    }
}


总结

当一个对象的某个行为存在多种实现时,那么将它们封装成一个单独的对象,并使它们同属于一个继承等级结构。
这样,可以使上下文类不关心具体的实现细节以及一致地使用这些策略,也能让客户端在运行时动态的改变策略,还能在不修改上下文类的前提下扩展策略。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容