创建型模式、结构型模式和行为型模式_行为型模式之策略模式和模板方法模式...

在刚开始接触设计模式,经常弄混行为型模式中的策略模式和模板方法模式。最近工作之外的时间,重新学习了一下,总结出二者的区别,以及何时使用策略模式,何时使用模板方法模式,向大家分享出来。

首先大致讲一下二者的区别,后续具体讲解每种模式,大家再回头看下这部分,会对二者有更好的体会。

策略模式:定义一个算法的抽象,有一组算法实现这个抽象算法,它们可以相互替换,所以说策略模式指的是一组算法,它们之间可以互相替换,使用策略模式一般是为了解决代码中出现的又臭又长的if else分支,使代码满足开闭原则,有利于后续程序扩展。

模板方法模式:定义一个算法的操作框架(抽象类),框架是指算法第一步干嘛,第二步干嘛。。。(模板方法),某个步骤所有子类都一样,则将其在框架中进行实现(具体方法),某些步骤所有子类不一样,就延迟到子类各自去实现(抽象方法)。模板方法模式,也是针对一组算法,这些算法的框架都是一样的,即算法第一步做什么,第二步做什么。算法之间相同的步骤在框架中实现,不同部分由各自自己实现。使用模板方法模式是为了抽取程序中的公共代码,减少重复代码。

策略模式

1、策略模式实现原理:向上转型

2、策略模式的定义
策略模式,定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换。UML结构图如图1所示,时序图如图2所示。

0d07fb240e77779e51232eaf0abd90cf.png
图1
ba3f3750dbe713a6181786e89cf9cfaf.png
图2

Context:策略上下文,通常策略上下文对象会持有一个真正的策略实现对象,负责调用具体策略上下文对象的方法。
IStrategy:策略抽象,算法的骨架。
ConcreteStrategy:具体策略类,算法的具体实现,IStrategy抽象的实现。

3、策略模式的目的
使程序遵循开闭原则,有利于程序的扩展,以及程序扩展不会影响现有的功能。

4、策略模式案例
商场打折案例,商场场往往根据不同的客户制定不同的报价策略,比如针对新客户不打折扣,针对老客户打9折,针对VIP客户打8折...现在我们要做一个报价管理的模块,针对不同的客户,提供不同的折扣报价,应该怎么做呢?

原始的if else写法
有人肯定会觉得so easy,写几个if else判断一下就可以了嘛。

// 程序1
/**
 * 报价管理系统
 */
public class QuoteManager {
    /**
     * 报价方法
     * @param originalPrice 商品原始价格
     * @param customType 客户类型
     * @return
     */
    public BigDecimal quote(BigDecimal originalPrice, String customType){
        if ("新客户".equals(customType)) {
            System.out.println("抱歉!新客户没有折扣!");
            return originalPrice;
        }else if ("老客户".equals(customType)) {
            System.out.println("恭喜你!老客户打9折!");
            originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);// 保留两位小数,四舍五入(若舍弃部分>=0.5,就进位)
            return originalPrice;
        }else if("VIP客户".equals(customType)){
            System.out.println("恭喜你!VIP客户打8折!");
            originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
            return originalPrice;
        }
        //其他人员都是原价
        return originalPrice;
    }
}

/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        QuoteManager quoteManager = new QuoteManager();
        BigDecimal price = quoteManager.quote(new BigDecimal(100), "老客户");
        System.out.println("报价为:" +price);
    }
}

没有接触过策略模式的新手程序员基本都会想到这种写法,但是仔细观察,这种写法是存在一些问题的。当我们新增加一个客户类型的时候,需要修改源代码增加一个else if分支,一是会使QuoteManager的quote()方法变得越来越臃肿,二是违反了开闭原则,不利于程序的维护,对源代码的改动,可能不小心破坏现有运行正常的功能。

策略模式

// 程序2
/**
 * 报价策略接口
 */
public interface IQuoteStrategy {
    /**
     * 获取最终报价
     * @param originalPrice 商品原始价格
     * @return
     */
    BigDecimal getPrice(BigDecimal originalPrice);
}

/**
 * 新客户 报价策略实现类
 */
public class NewCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("抱歉!新客户没有折扣!");
        return originalPrice;
    }
}

/**
 * 老客户 报价策略实现类
 */
public class OldCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("恭喜!老客户享有9折优惠!");
        originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
}

/**
 * VIP客户 报价策略实现类
 */
public class VIPCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("恭喜!VIP客户享有8折优惠!");
        originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
}

/**
 *
 * 报价上下文
 */
public class QuoteContext {
    private IQuoteStrategy quoteStrategy;

    //通过构造器注入具体的报价策略
    public QuoteContext(IQuoteStrategy quoteStrategy){
        this.quoteStrategy = quoteStrategy;
    }

    // 调用具体报价策略
    public BigDecimal getPrice(BigDecimal originalPrice){
        return quoteStrategy.getPrice(originalPrice);
    }
}

/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        //1.创建老客户的报价策略
        IQuoteStrategy oldQuoteStrategy = new OldCustomerQuoteStrategy();
        //2.创建报价上下文对象,并设置具体的报价策略
        QuoteContext quoteContext = new QuoteContext(oldQuoteStrategy);
        //3.调用报价上下文的方法
        BigDecimal price = quoteContext.getPrice(new BigDecimal(100));
        System.out.println("报价为:" +price);
    }
}

此时,商场营销部新推出了一个客户类型--MVP用户,可以享受折扣7折优惠,我们只要新增一种MVP客户的报价策略,然后客户端调用的时候,创建这个新增的报价策略实现,并设置到策略上下文就可以了,对原来已经实现的代码没有任何的改动。

// 程序3
/**
 * 新增MVP客户报价策略
 * MVP客户 报价策略实现类
 */
public class MVPCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("哇偶!MVP客户享受7折优惠!!!");
        originalPrice = originalPrice.multiply(new BigDecimal(0.7)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
}

/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        //创建MVP客户的报价策略
        IQuoteStrategy mvpQuoteStrategy = new MVPCustomerQuoteStrategy();
        //创建报价上下文对象,并设置具体的报价策略
        QuoteContext quoteContext = new QuoteContext(mvpQuoteStrategy);
        //调用报价上下文的方法
        BigDecimal price = quoteContext.getPrice(new BigDecimal(100));
        System.out.println("报价为:" +price);
    }
}

5、策略模式优缺点
优点:
遵循开闭原则,一是程序简洁不臃肿,二是有利于程序的扩展,程序的扩展不会影响现有正常的功能。

缺点:
1、客户端必须知道所有的策略类,并自行决定使用哪一个策略类(见程序2,Client类的第3行代码)。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。
2、由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观,会造成类爆炸的问题。

模板方法模式

1、模板方法模式实现原理:继承

2、模板方法模式的定义
定义一个算法中的操作框架,将子类之间相同的步骤在框架中实现,不同的步骤交给子类自己去实现,使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。UML结构图如图3所示,模板方法模式代码结构如程序4所示。

6c925443eaaaa428b7101a771d86e802.png
图3
// 程序4
/**
 * 抽象类(算法的框架)
 */
public abstract class TemplateClass {
    // 模板方法
    public void templateMethod(){
        // 算法第一步
        abstractMethod();
        // 算法第二步
        concreteMethod();
        // 算法第三步
        abstractMethod();
    }

    // 具体方法
    private final void concreteMethod(){
        // do something
    }

    // 抽象方法
    protected abstract void abstractMethod();

    // 钩子方法
    protected void hookMethod(){

    }
}

/**
 * 子类(具体的算法)
 */
public class ConcreteClass extends TemplateClass{
    // 实现父类的抽象方法
    @Override
    protected void abstractMethod() {
        // do something
    }

    // 实现父类的钩子方法
    @Override
    protected void hookMethod(){
        // do something
    }
}

TemplateClass:抽象类,算法的框架。
ConcreteClass:子类,具体的算法。
templateMethod():模板方法,定义在抽象类中,把基本方法组合在一起形成一个算法的框架,在抽象类中定义并实现,子类直接继承即可。
abstractMethod():抽象方法,在抽象类中声明,由具体子类实现。在Java语言里抽象方法以abstract关键字标示。
concreteMethod():具体方法,在抽象类中声明并实现。
hookMethod():钩子方法,在抽象类中声明并实现,子类会重写它。钩子方法的作用,可以让具体类灵活控制算法的执行(当我们不想让某个步骤执行的话,用钩子方法就可以达到这个目的,见下面的例子),使算法更加灵活。

3、模板方法模式的目的
减少重复代码,见定义中描述的,将子类之间相同的算法步骤在框架中实现,子类就不需要自己实现了,减少子类之间的重复代码。

4、模板方法模式案例
业务场景:银行计算存款利息场景,银行交易系统需要支持两种存款账号,活期存款(Demand Deposite)账号和定期存款(Time Deposite)账号,这两种账号的存款利息计算方式是不同的。

不使用模板方法策略模式的写法

程序设计UML图如图4所示,代码见程序5。

1c70a49f41a7af55720ed23ea2527ade.png
图4
// 程序5
/**
 * 活期存款
 */
public class DemandDepositeAccount {

    // 计算活期存款的利息总额
    public double calculateDemandDepositeInterest(){
        double demandDepositeAccount = getDemandDepositeAccount();
        double demandDepositeInterestRate = getDemandDepositeInterestRate();
        return demandDepositeAccount * demandDepositeInterestRate;
    }

    public double getDemandDepositeAccount() {
        return 4000;// 活期存款总额是4000元。
    }

    public double getDemandDepositeInterestRate() {
        return 0.04;// 活期存款利息率是0.04。
    }

}

/**
 * 定期存款
 */
public class TimeDepositeAccount {

    // 计算定期存款的利息总额
    public double calculateTimeDepositeInterest(){
        double timeDepositeAccount = getTimeDepositeAccount();
        double timeDepositeInterestRate = getTimeDepositeInterestRate();
        return timeDepositeAccount * timeDepositeInterestRate;
    }

    public double getTimeDepositeAccount() {
        return 6000;// 定期存款总额是6000元。
    }

    public double getTimeDepositeInterestRate() {
        return 0.06;// 定期存款利息率是0.06。
    }
    
}

/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        // 计算活期存款的存款总额
        DemandDepositeAccount demandDepositeAccount = new DemandDepositeAccount();
        double demandDepositeInterest = demandDepositeAccount.calculateDemandDepositeInterest();
        System.out.println("活期存款的利息总额:" + demandDepositeInterest + "元");

        // 计算定期存款的存款总额
        TimeDepositeAccount timeDepositeAccount = new TimeDepositeAccount();
        double timeDepositeInterest = timeDepositeAccount.calculateTimeDepositeInterest();
        System.out.println("定期存款的利息总额:" + timeDepositeInterest + "元");
    }
}

使用模板方法策略模式的写法

我发现原始写法,DemandDepositeAccount的calculateDemandDepositeInterest()方法和TimeDepositeAccount的calculateTimeDepositeInterest()大致逻辑是一样的,都是先获取存款总额,然后获取利息率,最后存款总额乘以利息率得到利息总额,有没有什么办法来解决这个代码重复的问题呢?

使用模板方法模式进行优化,将利息计算这个算法的框架提取出来作为一个模板,两种存款模式直接继承这个模板,无需自己再写一遍了。程序设计UML图如图5所示,代码见程序6。

ab88fc484e00c23e6e2dcd8abff53875.png
图5
// 程序6
/**
 * 抽象类
 */
public abstract class Account {
    // 计算利息总额,算法框架
    public double calculateInterest(){
        double depositAccount = getDepositAccount();
        double interestRate = getInterestRate();
        return depositAccount * interestRate;
    }

    // 获取存款总额
    public abstract double getDepositAccount();

    // 获取利息率
    public abstract double getInterestRate();
}

/**
 * 子类1,表示活期存款
 */
public class DemandDepositeAccount extends Account {
    @Override
    public double getDepositAccount() {
        return 4000;// 活期存款总额是4000元。
    }

    @Override
    public double getInterestRate() {
        return 0.04;// 活期存款利息率是0.04。
    }
}

/**
 * 子类2,表示定期存款
 */
public class TimeDepositeAccount extends Account {
    @Override
    public double getDepositAccount() {
        return 6000;// 定期存款总额是6000元。
    }

    @Override
    public double getInterestRate() {
        return 0.06;// 定期存款利息率是0.06。
    }
}

/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        // 计算活期存款的存款总额
        Account demandDepositeAccount = new DemandDepositeAccount();
        double demandDepositeInterest = demandDepositeAccount.calculateInterest();
        System.out.println("活期存款的利息总额:" + demandDepositeInterest + "元");

        // 计算定期存款的存款总额
        Account timeDepositeAccount = new TimeDepositeAccount();
        double timeDepositeInterest = timeDepositeAccount.calculateInterest();
        System.out.println("定期存款的利息总额:" + timeDepositeInterest + "元");
    }
}

5、钩子方法

钩子就是给子类一个授权,让子类来决定模板方法的逻辑执行,有一些子类不想执行模板方法中的某一步,就可以使用钩子进行控制。拿做西红柿炒蛋这个例子来讲解一下钩子方法,比如在炒西红柿鸡蛋的时候,由子类去决定是否要加调料。见程序7所示。

// 程序7
/**
 * 抽象类
 */
public abstract class Cook {
    
    // 钩子方法,让子类决定是否放油,默认放油
    public boolean isAddOil(){
        return true;
    }

    // 做西红柿炒蛋的步骤封装成一个模板
    public final void cook(){
        if (isAddOil()){
            this.addOil();
        }
        this.addEgg();
        this.addTomato();
    }

    // 放油
    public abstract void addOil();

    // 放鸡蛋
    public abstract void addEgg();

    // 放西红柿
    public abstract void addTomato();

}

/**
 * 菜鸟厨师做西红柿炒鸡蛋
 */
public class NoviceCook extends Cook {

    private boolean addOilFlag = true; // 默认放油

    /**
     * 由子类决定是否放油
     * @param addOilFlag
     */
    public void setAddOilFlag(boolean addOilFlag){
        this.addOilFlag = addOilFlag;
    }

    @Override
    public boolean isAddOil(){
        return this.addOilFlag;
    }

    @Override
    public void addOil() {
        System.out.println("菜鸟:放十斤油");
    }

    @Override
    public void addEgg() {
        System.out.println("菜鸟:鸡蛋壳掉到锅里了");
    }

    @Override
    public void addTomato() {
        System.out.println("菜鸟:西红柿没有切块");
    }

}

/**
 * 大厨做西红柿炒蛋
 */
public class ChefCook extends Cook {

    private boolean addOilFlag = true;

    public void setAddOilFlag(boolean addOilFlag){
        this.addOilFlag = addOilFlag;
    }

    @Override
    public boolean isAddOil(){
        return this.addOilFlag;
    }

    @Override
    public void addOil() {
        System.out.println("大厨:放适量油");
    }

    @Override
    public void addEgg() {
        System.out.println("大厨:放适量鸡蛋");
    }

    @Override
    public void addTomato() {
        System.out.println("大厨:放适量西红柿");
    }

}

/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        System.out.println("-----菜鸟厨师做西红柿炒蛋-----");
        NoviceCook noviceCook = new NoviceCook();
        // 菜鸟厨师第一次做西红柿炒蛋,没放油
        noviceCook.setAddOilFlag(false);
        noviceCook.cook();

        System.out.println("-----大厨做西红柿炒蛋-----");
        ChefCook chefCook = new ChefCook();
        chefCook.cook();
    }
}

打印结果:
-----菜鸟厨师做西红柿炒蛋-----
菜鸟:鸡蛋壳掉到锅里了
菜鸟:西红柿没有切块
-----大厨做西红柿炒蛋-----
大厨:放适量油
大厨:放适量鸡蛋
大厨:放适量西红柿

6、模板方法模式优缺点
优点:
1、将多个子类中,重复的代码抽取成模板方法放在抽象类中,子类只需要实现彼此不同的方法。以此来实现代码复用,减少重复代码
2、封装不变部分,扩展可变部分。把认为不变部分的算法封装到抽象类中实现,而可变部分则通过继承来让子类扩展。
缺点:
1、算法骨架需要改变时需要修改抽象类。
2、按照设计习惯,抽象类负责声明最抽象、最一般的事物属性和方法,实现类负责完成具体的事物属性和方法,但是模板方式正好相反,子类执行的结果影响了父类的结果,会增加代码阅读的难度。

总结

何时使用策略模式?何时使用模板方法模式?

程序在最开始设计阶段,基本很少用到设计模式,设计模式一般会在程序出现一些问题时,比如重复代码太多、增加新的代码很容易影响线运行正常的功能,才会考虑使用设计模式对程序做重构。策略模式和模板方法模式一般在程序重构过程中应用,那么二者的应用场景分别是什么呢?

当我们为了程序有利于以后的扩展,扩展不会影响现有的功能,此时考虑使用策略模式

当我们是为了抽离程序的公共代码,减少代码重复,此时考虑使用模板方法模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值