• 设计模式第九篇-模板方法模式


    一、引言

    生活中有很多模板,如:简历模板、论文模板,PPT模板,所谓模板就是有一个特定的格式,但是可以根据自身的需求进行改动,然后实现自己的功能。这样的好处就是可以减少自身的工作量,想想网上那么多好的PPT模板,改吧改吧就成自己的,这是一件多酸爽的事情!

    二、例子入手

    现在有两个类,分别是泡茶和泡咖啡

    //泡咖啡类
    public class Coffee {
        public void prepareRecipe(){
             boilWater();
             brewCoffeeGrinds();
             pourInCup();
             addSugarAndMilk();
        }
        //烧沸水
        public void boilWater(){
            System.out.println("烧沸水");
        }
        //冲泡咖啡
        public void brewCoffeeGrinds(){
            System.out.println("冲泡咖啡");
        }
        //倒入杯中
        public void pourInCup(){
            System.out.println("倒入杯中");
        }
        //加入糖和牛奶
        public void addSugarAndMilk(){
            System.out.println("加入糖和牛奶");
        }
    }
    
    //泡茶类
    public class Tea {
        public void prepareRecipe(){
            boilWater();
            steepTeaBag();
            pourInCup();
            addLemon();
        }
        //烧沸水
        public void boilWater(){
            System.out.println("烧沸水");
        }
        //冲泡茶叶
        public void steepTeaBag(){
            System.out.println("冲泡茶叶");
        }
        //倒入杯中
        public void pourInCup(){
            System.out.println("倒入杯中");
        }
        //加入柠檬
        public void addLemon(){
            System.out.println("加入柠檬");
        }
    }

    观察这个代码我们可以发现这个里面有重复,我们进行抽取。

    首先抽取相同的方法:boilWater()和pourInCup(),抽取完相同方法后,我们发现冲泡茶叶和冲泡咖啡动作是差不多的,那我们泛化成冲泡方法brew(),加入糖和牛奶和加入柠檬动作泛化成addCondiments(),这两个方法子类实现不一,所以要写成抽象方法,基类也就出来了。

    实现如下:

    //抽象基类
    public abstract class CaffeineBeverage {
        //我们不希望子类覆盖这个方法,用final关键字修饰
        final public void  prepareRecipe(){
            boilWater();
            brew();
            pourInCup();
            addCondiments();
        }
        //冲泡方法
        abstract void brew();
        //加入作料
        abstract void addCondiments();
        //烧沸水
        public void boilWater(){
            System.out.println("烧沸水");
        }
        //倒入杯中
        public void pourInCup(){
            System.out.println("倒入杯中");
        }
    }

    再来改造子类:由于父类已经把相同的方法实现,子类只要关注自身不同的方法,代码就非常简单了。

    //泡咖啡类
    public class Coffee extends CaffeineBeverage {
        //冲泡咖啡
        public void brew(){
            System.out.println("冲泡咖啡");
        }
        //加入糖和牛奶
        public void addCondiments(){
            System.out.println("加入糖和牛奶");
        }
    }
    
    //泡茶类
    public class Tea extends CaffeineBeverage {
        //冲泡茶叶
        public void brew(){
            System.out.println("冲泡茶叶");
        }
        //加入柠檬
        public void addCondiments(){
            System.out.println("加入柠檬");
        }
    }

    来,犒劳下自己

    private static void template() {
            Tea tea=new Tea();
            tea.prepareRecipe();
            System.out.println("-----------");
            Coffee coffee=new Coffee();
            coffee.prepareRecipe();
        }

    输出结果:

    至此,模板方法模式已经基本实现

    定义:在一个抽象类中定义一个操作中的算法骨架(对应于生活中的大家下载的模板),而将一些步骤延迟到子类中去实现(对应于我们根据自己的情况向模板填充内容)。模板方法使得子类可以不改变一个算法的结构前提下,重新定义算法的某些特定步骤

    意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

    主要解决:一些方法通用,却在每一个子类都重新写了这一方法。

    何时使用:有一些通用的方法。

    类图实现:

    模板方法还有一种钩子,钩子是一种被声明在抽象类中的方法,钩子的存在,可以让子类有能力对算法的不同点进行挂钩,要不要挂钩,由子类自行决定。

    比如:有些人喜欢不加料的咖啡,如果按照之前的基类,那么这个是无法实现的,我们试着用钩子来实现不加料的咖啡。

    先改动抽象基类:

    //带钩子的模板方法基类
    public abstract class CaffeineBerverageWithHook {
        //我们不希望子类覆盖这个方法,用final关键字修饰
        final public void prepareRecipe(){
            boilWater() ;
            brew();
            pourInCup();
            if(customerWantCondiments()) {
                addCondiments();
            }
        }
        //冲泡方法
        abstract void brew();
        //加入作料
        abstract void addCondiments();
        //烧沸水
        public void boilWater(){
            System.out.println("烧沸水");
        }
        //倒入杯中
        public void pourInCup(){
            System.out.println("倒入杯中");
        }
        //钩子
        boolean customerWantCondiments(){
            return true;
        }
    }

    再改造子类:

    //实现钩子的子类
    public class CoffeeWithHook extends CaffeineBerverageWithHook {
        public void brew() {
            System.out.println("冲泡咖啡");
        }
    
        public void addCondiments() {
            System.out.println("加入糖和牛奶");
        }
        //重写父类类方法
        public boolean customerWantCondiments(){
            String answer=getInput();
            if(answer.toLowerCase().startsWith("y")){
                return  true;
            }else{
                return false;
            }
    
        }
        private String getInput(){
            String answer=null;
            System.out.println("您想要配料么?");
            BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
            try{
                answer=in.readLine();
            }catch (IOException ex){}
            if(answer==null){
                return "no";
            }else{
                return answer;
            }
        }
    }

    看看

    private static void templateHook() {
            CoffeeWithHook coffeeWithHook=new CoffeeWithHook();
            coffeeWithHook.prepareRecipe();
        }

    结果:

    三、总结

    应用实例: 1、在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。 2、西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。 3、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。

    优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。

    缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

    使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。

    注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。

  • 相关阅读:
    ASP.NET 下载文件方式
    分享各大平台接口(空间,微博)
    BitmapToASCii
    C#操作进程(Process)
    Config ConnectionStrings
    Import Excel void (NPOI)
    C# Serialize
    C# 属性
    调用存储过程的方法
    Redis配置与安装
  • 原文地址:https://www.cnblogs.com/yuanqinnan/p/10272813.html
Copyright © 2020-2023  润新知