原创

JAVA设计模式(篇八)

温馨提示:
本文最后更新于 2022年10月27日,已超过 964 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

十三-模板方法模式

在面向对象程序设计过程中,常常会遇到这种情况︰设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。

1.模板方法模式的定义与特点

1. 模板方法模式定义:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。

2. 优点

  • 封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
  • 在父类中提取了公共的部分代码,便于代码复用。
  • 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。

3. 缺点:

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

2. 模式的结构和实现

模板方法模式由抽象类具体子类 组成

1. 模式的结构

  • 抽象类: 负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
    • 抽象方法:在抽象类中申明,由具体子类实现。
    • 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
    • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
  • 具体子类: 实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。

结构图:

image-20221011110523535

2.模式的实现

//抽象类==模拟员工到公司上班操作
public abstract class AbstractTemplate {

    //固定的执行步骤1.到公司先开门2.随即打卡3.到达工位
    public void templateMethod(){
        accessControl();
        String userName = clock();
        arriveStation(userName);
    }
    //抽象方法1==门禁
    public void accessControl(){}
    //抽象方法2==打卡
    public String clock(){
        return "";
    }
    //具体方法==到工位
    public void arriveStation(String userName){
        System.out.println(userName+"抵达工位");
    }
}

//具体子类==员工
@Data
public class ExecuteTemplateOne extends AbstractTemplate{

    private String userName;

    public ExecuteTemplateOne(String userName){
        this.userName = userName;
    }

    @Override
    public void templateMethod() {
        super.templateMethod();
    }

    @Override
    public String clock() {
        System.out.println(this.userName+"使用手机打卡");
        return this.userName;
    }

    @Override
    public void accessControl() {
        System.out.println(this.userName+"使用指纹打开门禁");
    }
}

//具体子类2==领导
@Data
public class ExecuteTemplateTwo extends AbstractTemplate{

    @Override
    public void templateMethod() {
        super.templateMethod();
    }

    @Override
    public String clock() {
        System.out.println("领导不用打卡");
        return "领导";
    }

    @Override
    public void accessControl() {
        System.out.println("领导使用手机打开门禁");
    }
}
//测试
    @Test
    public void template(){

        AbstractTemplate template = new ExecuteTemplateOne("张三");
        template.templateMethod();
        System.out.println("======================");
        AbstractTemplate template2 = new ExecuteTemplateTwo();
        template2.templateMethod();
    }
张三使用指纹打开门禁
张三使用手机打卡
张三抵达工位
======================
领导使用手机打开门禁
领导不用打卡
领导抵达工位

3.模式的应用场景

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,提供子类实现。
  • 当多个子类存在公共的行为时,可以将公共行为集中到一个父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
  • 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。

4.模板方法模式的扩展

在模板方法模式中,通常包含模板方法(固定执行步骤),具体方法(公共方法,已实现),抽象方法(提供给子类重写和扩展),钩子方法(判断逻辑等) ,合理使用钩子方法,即可改变父类的执行结果。

上述打卡列子优化:

//抽象类==模拟员工到公司上班操作
public abstract class AbstractTemplate {

    //固定的执行步骤1.到公司先开门2.随即打卡3.到达工位(领导不用到工位)  
    public void templateMethod(){
        accessControl();
        String userName = clock();
        if (!leader()){
            arriveStation(userName);
        }
    }
    //抽象方法1==门禁
    public void accessControl(){}
    //抽象方法2==打卡
    public String clock(){
        return "";
    }
    //添加是否是领导的判断逻辑
    boolean leader(){
        return false;
    }
    //具体方法==到工位
    public void arriveStation(String userName){
        System.out.println(userName+"抵达工位");
    }
}

//具体子类==员工
@Data
public class ExecuteTemplateOne extends AbstractTemplate{

    private String userName;

    public ExecuteTemplateOne(String userName){
        this.userName = userName;
    }

    @Override
    public void templateMethod() {
        super.templateMethod();
    }

    @Override
    public String clock() {
        System.out.println(this.userName+"使用手机打卡");
        return this.userName;
    }

    @Override
    public void accessControl() {
        System.out.println(this.userName+"使用指纹打开门禁");
    }
}

//具体子类2==领导
@Data
public class ExecuteTemplateTwo extends AbstractTemplate{

    @Override
    public void templateMethod() {
        super.templateMethod();
    }

    @Override
    public String clock() {
        System.out.println("领导不用打卡");
        return "领导";
    }

    @Override
    public void accessControl() {
        System.out.println("领导使用手机打开门禁");
    }

    @Override
    boolean leader() {
        return true;
    }
}

//测试
    @Test
    public void template(){

        AbstractTemplate template = new ExecuteTemplateOne("张三");
        template.templateMethod();
        System.out.println("======================");
        AbstractTemplate template2 = new ExecuteTemplateTwo();
        template2.templateMethod();
    }
张三使用指纹打开门禁
张三使用手机打卡
张三抵达工位
======================
领导使用手机打开门禁
领导不用打卡

十四-策略模式

当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序有冒泡排序、选择排序、插入排序、二叉树排序等。
如果使用多重条件转移语句实现(if else),不但使条件语句变得很复杂,而且增加、删除或更换算法要修改原代码,不易维护,违背开闭原则。如果采用策略模式就能很好解决该问题。

1.模式的定义与特点

策略模式: 定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

优点:

  • 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
  • 提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
  • 可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
  • 提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
  • 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

缺点:

  • 所有的策略类都需要暴露给客户端,以便于客户端根据业务选择不同的策略。
  • 如果算法很多,策略类会变得很多,增加系统的复杂度

2.模式的结构和实现

策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性。

1.策略模式的结构

  • 抽象策略: 定义一个公共方法,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般为接口或抽象类。
  • 具体策略: 实现抽象策略,重写公共方法,实现具体的业务算法逻辑。
  • 环境类: 包含一个具体策略,并调用策略中的公共方法,最终交由客户端使用。

image-20221011141343261

2.策略模式的实现

以服装店购物打折为例,500以下打9折,500及以上打8折

//抽象算法接口==模拟服装店打折
public interface AbstractStrategy {
    //计算服装店打折后的价格
    BigDecimal discount(BigDecimal oldPrice);
}
//具体策略==八折
public class EightDiscount implements AbstractStrategy{

    @Override
    public BigDecimal discount(BigDecimal oldPrice) {
        return oldPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
    }
}

//具体策略==九折
public class NineDiscount implements AbstractStrategy{

    @Override
    public BigDecimal discount(BigDecimal oldPrice) {

        return oldPrice.multiply(new BigDecimal(0.9).setScale(2,BigDecimal.ROUND_HALF_UP));
    }
}

//环境类
@Data
public class ContextStrategy {

    private AbstractStrategy strategy;

    public void showPrice(BigDecimal oldPrice){
        System.out.println(this.strategy.discount(oldPrice));
    }

}
//测试 客户端知道所有的具体策略,并且可以根据业务使用不同的打折策略
    @Test
    public void strategy(){
        ContextStrategy contextStrategy = new ContextStrategy();
        contextStrategy.setStrategy(new EightDiscount());
        contextStrategy.showPrice(new BigDecimal(998));
    }

3. 策略模式的应用场景

  • 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
  • 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
  • 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构
  • 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

4.策略模式的扩展

在一个使用策略模式的系统中,当存在的策略很多时,客户端管理所有策略算法将变得很复杂,如果在环境类中使用策略工厂模式类似享元模式中的享元工厂角色)来管理这些策略类将大大减少客户端的工作复杂度。

image-20221011142725292

//抽象算法接口==模拟服装店打折
public interface AbstractStrategy {
    //计算服装店打折后的价格
    BigDecimal discount(BigDecimal oldPrice);
}
//具体策略==八折
public class EightDiscount implements AbstractStrategy{

    @Override
    public BigDecimal discount(BigDecimal oldPrice) {
        return oldPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
    }
}

//具体策略==九折
public class NineDiscount implements AbstractStrategy{

    @Override
    public BigDecimal discount(BigDecimal oldPrice) {

        return oldPrice.multiply(new BigDecimal(0.9).setScale(2,BigDecimal.ROUND_HALF_UP));
    }
}

//环境类==策略工厂模式
public class StrategyFactory {

    private Map<String, AbstractStrategy> strategyMap = new HashMap<>();

    public AbstractStrategy getStrategy(String key) {
        return strategyMap.get(key);
    }

    public void setStrategy(String key, AbstractStrategy strategy) {
        strategyMap.put(key, strategy);
    }

    public BigDecimal discount(String key,BigDecimal oldPrice){
        AbstractStrategy strategy = getStrategy(key);
        if (null!=strategy){
            BigDecimal discount = strategy.discount(oldPrice);
            System.out.println("原价:"+oldPrice+"  折后价:"+discount);
           return discount;
        }
        return null;
    }
}
//测试 客户端知道所有的具体策略,并且可以根据业务使用不同的打折策略
    @Test
    public void strategy(){
        StrategyFactory factory = new StrategyFactory();
        factory.setStrategy("eight",new EightDiscount());
        factory.setStrategy("nine",new NineDiscount());
        factory.discount("eight",new BigDecimal(888));
    }
正文到此结束