• 设计模式之模板方法模式


     模板方法模式,我们将要深入封装算法块,好让子类可以在任何时候都可以将自己挂接进运算里。

    茶和咖啡的冲泡方式非常相似,不信你瞧瞧:

    快速搞定几个咖啡和茶的类。

    public class Coffee {
     
        void prepareRecipe() {
            boilWater();
            brewCoffeeGrinds();
            pourInCup();
            addSugarAndMilk();
        }
     
        public void boilWater() {
            System.out.println("Boiling water");
        }
     
        public void brewCoffeeGrinds() {
            System.out.println("Dripping Coffee through filter");
        }
     
        public void pourInCup() {
            System.out.println("Pouring into cup");
        }
     
        public void addSugarAndMilk() {
            System.out.println("Adding Sugar and Milk");
        }
    }

    接下来是茶:

    public class Tea {
     
        void prepareRecipe() {
            boilWater();
            steepTeaBag();
            pourInCup();
            addLemon();
        }
     
        public void boilWater() {
            System.out.println("Boiling water");
        }
     
        public void steepTeaBag() {
            System.out.println("Steeping the tea");
        }
     
        public void addLemon() {
            System.out.println("Adding Lemon");
        }
     
        public void pourInCup() {
            System.out.println("Pouring into cup");
        }
    }

    可以看到,我们发现了重复的代码,需要清理下设计了。

    我们可能想要设计一个基类,新的类层次如下:

    更进一步的设计

    星巴克咖啡冲泡法

    1.把水煮沸

    2.用沸水冲泡咖啡

    3.把咖啡 倒进杯子

    4.加糖和牛奶

    星巴克茶冲泡法

    1.把水煮沸

    2.用沸水浸泡茶叶

    3把茶倒进杯子

    4加柠檬

    注意2份冲泡法都采用了相同的算法:

    1.把水煮沸

    3.用热水泡咖啡或茶

    3.把饮料倒进杯子

    4在饮料内加入适当的调料

    抽象prepareRecipe()

      让我们从每一个子类(也就是茶和咖啡)中逐步抽象prepareRecipe()。。

    1.所遇到的 第一个问题,就是咖啡使用brewCoffeeGrinds()和addSugarAndMilk()方法,而茶

    使用的是steepTeaBag)(和addLemon()方法。

     让我们来思考这一点:浸泡steep和冲泡brew差异其实不多。所以我们给他一个新的方法名称,比方说brew(),然后不管是泡茶或是冲泡咖啡我们都用这个名称。类似地,加糖和牛奶很相似:都是在饮料中加入调料。我们也用一个新的方法名称来解决:叫做addCondiments()好了。这样一来,新的prepareRecipe()看起来像这样

    void prepareRecipe()

    {

       boilWater();

      brew();

      pourInCup();

    addCondiments();

    }

     2.现在,我们有了新的prepareRecipe()方法,但是需要让它能符合代码,我们先从超类开始:

    3最后,我们出来子类。子类依赖超类。

    现在我们已经将prepareRecipe()的实现放在超类中了。

    认识模板方法

      基本 上,我们刚做的就是模板方法模式。这是什么?让我们看看咖啡因饮料类的结果,它包含了实际的“模板方法”。

    注意,上面的有个小错误,final应放在void的前面,否则报错

    prepareRecipe()是我们的模板方法。为什么?》

    因为:

    1.毕竟他是一个方法

    2.它用作一个算法的模板,在这个例子中,算法是用来制作咖啡因饮料的。 

     在这个模板中,算法内的每一个步骤都被一个方法代表了。

    某些方法是有这个类(超类)处理的。某些是由子类处理的。需要由子类提供的方法,必须在超类中声明为抽象。

    模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现

    走,泡茶去。。。

     让我们逐步地泡茶,追踪这个模板方法是如何工作的。你会得知在算法内的某些地方,该模板方法控制了算法。它让子类能够提供某些步骤的实现。。。

    模板方法给我们带来了什么?

    上面的图值得细看。

    定义模板方法模式

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

     这个模式是用来创建一个算法的模板。什么是模板?如你所见,模板就是一个方法。更具体的说,这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由子类实现。这可以确保算法的结构保持不变,同时由子类提供部分实现

    让我们看看类图:

    让我们细看抽象类是如何被定义的,包括了它内含的模板方法和原语操作。

    让我们再靠近一点,详细看看抽象类可以有哪些类型的方法:

    对模板方法进行挂钩

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

    钩子有好几种用途,让我们先看其中一个:

    上面的customerWantsCondiments()就是一个钩子,子类可以覆盖这个方法,但不一定要覆盖

    使用钩子

     为了使用钩子,我们在子类中覆盖它。在这里,钩子控制了咖啡因饮料是否执行某部分的算法,说的更明确一些,就是饮料中是否要加调料。

     我们如何得知顾客是否想要调料呢?开口问不就知道了。

    public class CoffeeWithHook extends CaffeineBeverage{
    
                void brew() {
                    // TODO Auto-generated method stub
                    System.out.println("Dripping Coffee through filter");
                }
    
                void addCondiments() {
                    // TODO Auto-generated method stub
                    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.println("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 e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    if(answer==null)
                    {
                        return "no";
                        
                    }
                    return answer;
                }
    }

    执行测试程序:

    public class TestMain {
        public static void main(String[] args)
        {
         
            CoffeeWithHook coffeeWithHook=new CoffeeWithHook();
             
            coffeeWithHook.prepareRecipe();
        }
    
    }

    这个例子实在太酷了,使用钩子能够作为条件控制,影响抽象类中的算法流程。

    问:当我创建一个模板方法时,怎么才能知道什么时候使用抽象方法,什么时候使用钩子?

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

    好莱坞原则:don't call me ,i'll call you!.

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

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

    好莱坞原则和模板方法

      当我们设计模板方法模式时,我们告诉子类,“不要调用我们,我们会调用你”。怎样才能办到呢?让我们看看设计:

    还有工厂方法和观察者模式都采用了这个原则

    问:低层组件不可以调用高层组件中的方法呢?

    答:并不尽然。事实上,低层组件在结束时,常常会调用从超类中继承来的方法。我们要做的是,避免让高层组件和低层组件之间有明显的环状依赖

    荒野中的模板方法

     模板方法模式是一个很常见的模式,到处都是。尽管如此,你必须拥有一双锐利的眼睛,因为模板 方法有许多实现

    这个模式很常见是因为对创建框架来说,这个模式简直棒极了。由框架控制如何做事情,而由你(使用这个框架的人)
    指定框架算法中每个步骤的细节。

    让我们步入荒野,展开狩猎把!(好吧!荒野就是指java api)。。。

    写一个Swing的窗口程序
     JFrame有一个paint()方法,在默认状态下,paint()是不做明显的事情的,因为他是一个“钩子”!通过覆盖paint(),你可以将自己的代码插入JFrame的算法中,显示出你想要的画面。下面是一个简单的例子。

    public class MyFrame extends JFrame{
        public MyFrame(String title)
        {
            super(title);
            this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            
            this.setSize(300,300);
            this.setVisible(true);
        }
        
        public void paint(Graphics graphics)
        {
            super.paint(graphics);
            String msg="I rule!";
            graphics.drawString(msg,100,100);
        }
        
        public static void main(String[] args)
        {
            new MyFrame("hello");
        }
    
    }

    要点:


    为了防止子类改变模板方法中的算法,可以将模板方法声明为final

    好莱坞原则告诉我们,将决策权放在高层模块中,以便决定如何以及何时调用低层组件

    策略模式和模板方法模式都封装算法,一个用组合,一个用继承

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

  • 相关阅读:
    解决“iOS 7 app自动更新,无法在app中向用户展示更新内容”问题
    ios 数组排序
    六年谈游戏工作室与游戏开发过程简介(转)
    mysql按位的索引判断值是否为1
    mysql按位的索引判断位的值
    git bash here右键菜单
    将VSCode添加到右键
    给vscode添加右键打开功能
    winrar压缩过滤文件及文件夹
    编译wxWidgets
  • 原文地址:https://www.cnblogs.com/youxin/p/2712720.html
Copyright © 2020-2023  润新知