zoukankan      html  css  js  c++  java
  • 【设计模式】学习笔记11:模板方法(Template Method)



    本文出自   http://blog.csdn.net/shuangde800



     

    —— 工厂方法是模板方法的一种特殊版本。

    ——如果你只想掌握一种设计模式,那么它就是模板方法!


    走进模板方法


    我们知道泡咖啡和泡茶的步骤是很类似的:


    咖啡泡法

    1. 把水煮沸

    2. 用沸水冲泡咖啡

    3.把咖啡倒进杯子

    4.加糖和咖啡


    泡茶方法

    1.把水煮沸

    2.用沸水侵泡茶叶

    3.把茶倒进杯子

    4.加柠檬


    如何设计这两种饮料的类?

    一个简单的方法是直接分别针对茶和咖啡设计一个类

    咖啡Coffe类里面实现方法:
    boilWater();
    brewCoffeeGrinds();
    pourInCup();
    addSugarAndMilk();
    
    茶Tea类里面实现方法:
    boilWater();
    steepTeaBad();
    pourInCup();
    addLemon();
    



    但是这样的话,将会有重复代码,不符合设计原则。

    1. Coffee和Tea将主导一切,它们控制了算法

    2. Coffee和Tea之间存在着重复的代码

    3. 对于算法所做的代码改变,需要打开子类修改许多地方

    4. 由于类的组织方式不具有弹性,所以如果新加入种类的咖啡因因类需要做很多工作

    5. 算法的只是和它的实现会分散在许多类中



    我们可以发现方法1和3是一样的,而2,4步虽然稍有区别,但是差异也不大。因为侵泡和冲泡差别不大,所以我们大可以给它一个新的方法名称:brew(),然后不管是茶还是咖啡都用这个方法。类似地,加糖和牛奶以及加柠檬也是类似的:都是加入调料。所以变成新方法:addCondiments()



    这样,我们可以给茶和咖啡定义一个共同的抽象类,然后再分别继承实现茶和咖啡的类



    抽象类CaffeineBaverage(咖啡因饮料)中, prepareRecipe()方法是把泡茶(咖啡)的动作组合起来

    boliWater(泡水)和pourlnCup(倒杯)方法因为是相同的,所以定义在超类中,并且可以直接实现。

    而brew(冲泡)和addCondiments(加调料)因为不同东西方法不太一样,所以应定义为抽象方法,在具体子类中实现。


    定义抽象类的代码:

    // 都是咖啡因饮料,所以把抽象类命名为CaffeineBeverage
    public abstract class CaffeineBeverage {
      
        // 用这个方法来处理泡茶和咖啡的步骤
        final void prepareRecipe() {
            boilWater();
            brew();
            pourInCup();
            addCondiments();
        }
     
        // 因为咖啡和茶的下面两种方法有些不同,
        // 所以定义成抽象的,具体做法留给子类定义
        abstract void brew();
      
        abstract void addCondiments();
     
        // 下面两种方法咖啡和茶是一样的,所以直接实现
        void boilWater() {
            System.out.println("Boiling water");
        }
      
        void pourInCup() {
            System.out.println("Pouring into cup");
        }
    }
    

    然后我们就可以分别实现子类咖啡和茶的具体做法了:

    // 茶
    public class Tea extends CaffeineBeverage {
        // 实现父类的抽象方法
        // 是自己特有的动作
        public void brew() {
            System.out.println("Steeping the tea");
        }
        public void addCondiments() {
            System.out.println("Adding Lemon");
        }
    }
    


    咖啡:

    // 咖啡
    public class Coffee extends CaffeineBeverage {
        public void brew() {
            System.out.println("Dripping Coffee through filter");
        }
        public void addCondiments() {
            System.out.println("Adding Sugar and Milk");
        }
    }
    


    这样做的好处:

    1. 由抽象类CaffeineBeverage主导一切,它拥有算法,而且保护这个算法

    2. 对子类来说,CaffeineBeverage类的存在,可以将代码的复用最大化

    3. 算法只存在一个地方,所以容易修改。

    4. 这个模板方法提供了一个框架,可以让其它的咖啡因因类插进来。新的咖啡因饮料只需要实现自己的方法就可以了

    5. CaffeineBeverage专注在算法本身,而由子类提供完整的实现


    OK了,其实上面实现的东西 就叫做模板方法模式。




    定义模板方法模式

    模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法是的子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。



    模板就是一个方法,更具体地说,这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由其子类负责实现。这可以确保算法的结构保持不变,同时由子类提供部分实现。






    模板方法的挂钩

    钩子是一种被申明在抽象类中的方法,但是只有空的或者默认的实现。

    钩子的存在,可以让子类有能力对算法的不同点进行挂钩。而要不要挂钩也由子类决定。


    我们来看其中一个实例:带钩的咖啡因饮料类

    // 带钩的咖啡因饮料类
    public abstract class CaffeineBeverageWithHook {
        // 这个方法声明为final,防止子类改变模板方法中的算法
        void final prepareRecipe() {
            boilWater();
            brew();
            pourInCup();
            // 这里加入了一个小小的条件语句
            // 由子类决定是否加调料
            // 默认方法是加调料的
            if (customerWantsCondiments()) {
                addCondiments();
            }
        }
     
        abstract void brew();
     
        abstract void addCondiments();
     
        void boilWater() {
            System.out.println("Boiling water");
        }
     
        void pourInCup() {
            System.out.println("Pouring into cup");
        }
     
        // 这就是一个“钩子”,子类可以选择覆盖这个方法
        // 这里定义了一个方法,通常是空的缺省实现
        // 这个方法智慧返回true,即默认是加饮料的
        boolean customerWantsCondiments() {
            return true;
        }
    }
    


    使用钩子

    为了使用钩子,要在子类中覆盖钩子方法。

    我们可以让子类实现钩子的一个功能:询问客户是否要加调料?

    public class CoffeeWithHook extends CaffeineBeverageWithHook {
     
        public void brew() {
            System.out.println("Dripping Coffee through filter");
        }
     
        public void addCondiments() {
            System.out.println("Adding Sugar and Milk");
        }
     
        // 覆盖了这个“钩子”,提供自己的功能
        public boolean customerWantsCondiments() {
    
            // 获取用户的输入
            String answer = getUserInput();
    
            if (answer.toLowerCase().startsWith("y")) {
                return true;
            } else {
                return false;
            }
        }
     
        private String getUserInput() {
            String answer = null;
    
            System.out.print("Would you like milk and sugar with your coffee (y/n)? ");
    
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            try {
                answer = in.readLine();
            } catch (IOException ioe) {
                System.err.println("IO error trying to read your answer");
            }
            if (answer == null) {
                return "no";
            }
            return answer;
        }
    }
    


    设计原则

    好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你们


    在好莱坞原则下,我们允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件。换句话说,高层组件对待低层组件的方式是“别调用我们,我们会调用你们”。


    这个原则可以给我们一种防止“依赖腐败”的方法。当高层组件以来低层组件,而低层组件又依赖高层组件,而高层组件又依赖边侧组件,而边侧组件又依赖低层组件时,依赖腐败就发生了。这种情况下,没有人可以轻易地搞懂系统是如何设计的。





    好莱坞原则与模板方法:

    当我们设计模板方法时,我们告诉子类“不要调用我们,我们会调用你”

    在上面咖啡与茶的例子中,抽象类CaffeineBaverage是我们的高层组件,它能够控制冲泡法的算法,只有在需要子类实现某个方法时,才调用子类。而子类只提供实现的pourInCup和addCondiments方法,如果没有被调用,子类绝对不会直接调用抽象类。



    问题

    创建一个模板方法时,怎样才能知道什么时候该使用抽象方法,什么时候使用钩子呢?

    答:当你的子类“必须”提供算法中某个方法或步骤的实现时,就使用抽象方法。如果算法的这个部分是可选的,就用钩子。如果是钩子的话,子类可以选择实现


    使用钩子的真正目的是什么?

    钩子有几种用法。如,钩子可以让子类实现算法中的可选部分,或者钩子对于子类的实现并不重要时,子类可以不理会这个钩子。钩子的另一个用法是,让子类能够有机会对模板方法中的某些即将发生的(或刚刚发生的)步骤作出反映,例如,在屏幕上显示数据等。


    好莱坞原则和依赖倒置原则的关系?
    依赖倒置原则教我们尽量避免使用具体类,而多用抽象。而好莱坞原则是用在创建框架或组件上的一种技巧,好让低层组件能够被挂钩进计算中,而且又不会让高层组件依赖低层组件。两者的目标都是解藕,但是依赖倒置原则更加注重如何在设计中避免依赖。好莱坞原则教我们一个技巧,一个有弹性的设计,允许低层结构能够互相操作,而又防止其他类太过依赖他们。


    实际应用中的模板方法

    模板方法几乎无处不在 ,这里举几个例子:

    Java中的Array类的sort()模板方法,要实现comparTo();

    Java中的Swing窗口程序大量的使用了模板方法:Swing中的JFrame,它是一个最基本的Swing容器,继承了一个paint()方法,这是一个钩子,它不做什么事情,通过覆盖paint(),你可以将自己的代码插入到JFrame的算法中,显示出自己想要的画面。

    appleet,是一个能在网页上面执行的小程序,也提供了大量的钩子。




     

  • 相关阅读:
    Metro-UI系统-1-tile标签
    linux 修改文件最大数
    linux 利用cat写入一段文件
    linux for 循环的小应用
    办公区公网Ip访问不到阿里云ECS
    Django 构建一个项目
    Django 基础篇
    iptables 生产环境下基础设置
    centos7 部署LNMP
    查看sqlserver被锁的表以及如何解锁
  • 原文地址:https://www.cnblogs.com/bbsno1/p/3258249.html
Copyright © 2011-2022 走看看