• 八、模板方法模式


    • 定义

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

    • UML类图


      说明:
      1、AbstractClass抽象中包含了模板方法,primitiveOpration1() 和primitiveOpration2() 是这个模板方法所用到的操作的抽象版本。
      2、模板方法在实现算法的过程中,用到了两个原语操作。模板方法本身和这两个操作的具体实现之间被解耦了。
      3、ConcreteClass 可以有多个具体类,每一个都实现了模板方法所需要的全部操作。ConcreteClass中的primitiveOpration1()和primitiveOpration2(),当模板方法需要这两个抽象方法时,会调用他们。

    简单实现

    AbstractMethod 抽象的模板方法定义

    /**
     *  模板方法模式在一个方法中定义了一个算法骨架,而将一些步骤延伸到子类中。
     *  模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤 
     */
    public abstract class CaffeineBeverage {
    
        public final void prepareRecipe() {
            boilWater();
            brew();
            pourInCup();
            addCondiments();
        }
    
        //该方法在模板的模式中被称作原语操作  具体子类必须实现它们
        protected abstract void addCondiments();
    
        protected abstract void brew();
    
        private final void pourInCup() {
            System.out.println("pour water in cup...");
        }
    
        private final void boilWater() {
            System.out.println("boil water...");
        }
    }

    ConcreteClass 抽象模板方法子类的实现

    public class Tea extends CaffeineBeverage {
    
        @Override
        protected void addCondiments() {
            System.out.println("Adding Lemon...");
        }
    
        @Override
        protected void brew() {
            System.out.println("Adding Tea in Cup...");
        }
    }
    
    public class Coffee extends CaffeineBeverage {
    
        @Override
        protected void addCondiments() {
            System.out.println("Adding Sugar and Milk...");
        }
    
        @Override
        protected void brew() {
            System.out.println("Dripping Coffee through filter...");        
        }
    }
    

    测试类

    public class BaristaTest {
        public static void main(String[] args) {
            Tea tea = new Tea();
            Coffee coffee = new Coffee();
            System.out.println("Making tea...");
            tea.prepareRecipe();
            System.out.println("Making coffee...");
            coffee.prepareRecipe();
        }
    }

    对模板方法进行挂钩操作

    CaffeineBeverageWithHook 带钩子的抽象模板方法定义

    public abstract class CaffeineBeverageWithHook {
    
        public final void prepareRecipe() {
            boilWater();
            brew();
            pourInCup();
            //条件判断语句,该条件是否成立,是有一个customerWantsCondiments()钩子方法决定的。
            if (customerWantsCondiments()) {
                addCondiments();
            }
        }
    
        abstract void brew();
    
        abstract void addCondiments();
    
        private final void boilWater() {
            System.out.println("Boiling water");
        }
    
        private final void pourInCup() {
            System.out.println("Pouring into cup");
        }
        //这是一个钩子,子类可以覆盖这个方法,但是不见得一定这么做
        boolean customerWantsCondiments() {
            return true;
        }
    }
    

    使用钩子 CoffeeWithHook 和 TeaWithHook

    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;
        }
    }
    public class TeaWithHook extends CaffeineBeverageWithHook {
    
        public void brew() {
            System.out.println("Steeping the tea");
        }
    
        public void addCondiments() {
            System.out.println("Adding Lemon");
        }
         //钩子的使用
        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;
        }
    }
    

    测试代码

    public class BeverageTestDrive {
        public static void main(String[] args) {
            TeaWithHook teaHook = new TeaWithHook();
            CoffeeWithHook coffeeHook = new CoffeeWithHook();
            System.out.println("
    Making tea...");
            teaHook.prepareRecipe();
            System.out.println("
    Making coffee...");
            coffeeHook.prepareRecipe();
        }
    }

    说明:钩子是一种被声明在抽象类中的方法,但是只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。需不需要钩子,由子类自行决定。

    • 模板方法在Java排序中的实现

      Duck类的排序

    public class Duck implements Comparable {
        String name;
        int weight;
    
        public Duck(String name, int weight) {
            this.name = name;
            this.weight = weight;
        }
    
        public String toString() {
            return name + " weighs " + weight;
        }
    
        public int compareTo(Object object) {
    
            Duck otherDuck = (Duck)object;
    
            if (this.weight < otherDuck.weight) {
                return -1;
            } else if (this.weight == otherDuck.weight) {
                return 0;
            } else { // this.weight > otherDuck.weight
                return 1;
            }
        }
    }

    测试方法

    public class DuckSortTestDrive {
        public static void main(String[] args) {
            Duck[] ducks = { 
                    new Duck("Daffy", 8), 
                    new Duck("Dewey", 2), 
                    new Duck("Howard", 7), 
                    new Duck("Louie", 2),
                    new Duck("Donald", 10), 
                    new Duck("Huey", 2) 
                    };
            System.out.println("Before sorting:");
            display(ducks);
            Arrays.sort(ducks);
            System.out.println("
    After sorting:");
            display(ducks);
        }
    
        public static void display(Duck[] ducks) {
            for (int i = 0; i < ducks.length; i++) {
                System.out.println(ducks[i]);
            }
        }
    }

    说明:
    1、数组的排序模板算法已经提供了算法,但是要让这些模板方法知道如何比较duck,需要做的就是实现compareTo() 方法。
    2、Array的设计者希望这个方法能够用于所有的数组,所以把sort()变成静态的方法,这样一来,任何数组都可以使用这个方法。
    3、因为sort()并不是真正定义在超类中,所以sort()方法需要知道已经实现了这个compareTo()方法,否则无法进行排序。
    4、compareTo()方法返回是一个int类型的变量,返回值为 1、0、-1 三种。
    5、实现Comparable接口,并重写compareTo()方法。这种模板方法的实现,侧重在于提供一个算法,并让子类实现某些步骤,而数组的排序算法很明显正是如此。
    6、在Duck类的排序算法中,由于无法继承Java数组,而sort()方法希望能够用于所有的数组(每个数组都是不同的类)。所以Arrays提供了一个静态方法,而由被排序对象内的每个元素自行提供比较大小的算法步骤。所以,这并不是标准的模板方法的实现,但是符合模板方法的模式精神。(由于不需要继承数组就可以实现排序算法,使得排序变得更有弹性、更有用)
    7、在Java API的实现中,java.io的InputStream类有一个read()方法,是由子类实现的,而这个方法又会被 read(byte b[], int off, int len) 模板方法使用。

    模板方法模式与策略模式的比较

    1、数组对象的排序,这部分和策略模式相似,都是使用对象的组合。但是在策略模式中,所组合的类实现了整个算法,数组所实现的排序算法并不完整,他需要一个类填补compareTo()方法的实现。因此更像模板方法。
    2、策略模式并不是使用继承来进行算法的实现,而是通过对象组合的方式,让客户可以选择算法的实现。而模板方法模式是定义一个算法大纲,而由子类定义其中某些步骤的内容。这样一来在算法的个别步骤可以有不同的细节实现,但算法的结构依然维持不变。
    3、模板方法模式使用的重复代码,被放入到超类中,好让所有子类共享。而策略模式使用的是委托模型,使用对象组合,使得策略模式更加有弹性。利用策略模式,客户可以在运行时改变自己的算法,而客户所需要的,只是改用不同的策略对象罢了。
    4、模板方法模式是经常被使用的模式,因为在超类中提供了一个基础的方法,达到代码的复用,并允许子类指定行为。这在创建框架的时候是非常有用的。
    5、模板方法模式由于必须依赖超类中的方法的实现,算作算法的一部分。策略模式不需要依赖,整个算法自己去实现。所以依赖程度相对比较低。

    要点

    1、“模板方法”定义了算法的步骤,把这些步骤的实现延迟到子类。
    2、模板方法为我们提供了一种代码复用的技巧。
    3、模板方法的抽象类可以定义具体的方法、抽象方法和钩子。
    4、抽象方法由子类实现。
    5、钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择要不要去覆盖它。
    6、为了防止子类改变模板方法中的算法,可以将模板方法声明为final
    7、将决策权放在高层模块中,以便决定如何及何时调用低层模块。
    8、在真实实现的模板方法中,模板方法由很多的变体,不要指望一眼辨认出来。
    9、策略模式和模板方法模式都封装算法,一个用组合,一个用继承。
    10、工厂方法是模板方法的一种特殊版本。

  • 相关阅读:
    vue2 下载scss依赖包
    fastjson使用
    vscode format
    flutter 中涉的深拷贝
    通过pom给maven添加编译插件
    IDEA添加动态模板(Live Templates)
    Maven启动tomcat:run异常
    Redis
    tomcat启动时启动窗口出现乱码的解决方案
    无效的源发行版,解决方案
  • 原文地址:https://www.cnblogs.com/huacesun/p/6622491.html
Copyright © 2020-2023  润新知