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


    (文章的部分内容参考了《设计模式之禅》一书,大家也可以读读看,内容写的非常好)

    什么是模板方法模式

    它的定义如下:

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

    读起来很绕口,其实通俗的说就是父类里面定义一些抽象方法,自己不去实现,交由子类去具体实现,父类只负责管理这些抽象方法的运行顺序,管理运行顺序的方法就是模板方法。

    举个具体的使用场景:我们画画一般都是先画线稿,然后再进行上色(不要和我说厚涂。。),不过不同的画师的绘画习惯是不同的,比如有的人画线稿画的很细致,有的只是粗略的画出大概的轮廓,就进行上色了。不同画师上色的时候处理也不同,有的喜欢高饱和的色彩,有的喜欢淡色系。这时我们想建立一个通用的模板,用来归纳画师的绘画过程。

    那么这时就可以用到模板方法模式了。

    我们第一想到的就是先创建一个接口(IPainter)

    public interface IPainter {
        void drawOutLine();  //绘制线稿
        void setColor();     //上色
    }

    画师A实现 IPaInter 接口

    public class PainterA implements IPainter{
    
        //绘制线稿
        @Override
        public void drawOutLine(){
            System.out.println("绘制精致的线稿");    
        }
            
        //上色
        @Override
        public void setColor(){
            System.out.println("涂上色彩鲜艳的颜色");
        }
    }

    画师B实现 IPainter 接口

    public class PainterB implements IPainter{
        
        //绘制线稿
        @Override
        public void drawOutLine(){
            System.out.println("绘制粗略的线稿");
        }
        
        //上色
        @Override
        public void setColor(){
            System.out.println("涂上淡色");
        }
    }

    我们的两个画师 A 和 B 绘画的过程如下:

    public class Context {
    
        public static void main(String[] args) {
            PainterA a=new PainterA();
            a.drawOutLine();
            a.setColor();
            
            PainterB b=new PainterB();
            b.drawOutLine();
            b.setColor();
            
        }
    
    }

    这里我们可以看到两个画师都依次执行了drawOutline()方法和setColor()方法,故可以把这两个方法的执行顺序看做是一个固定的流程,我们再创建一个方法对其进行封装

    在IPainter接口中添加 draw()方法

    public interface IPainter {
        void drawOutLine();
        void setColor();
        void draw();   //绘画
    }

    PainterA,PainterB 中实现该方法

        //绘画
        @Override
        public void draw(){
            drawOutLine();
            setColor();
        }

    这样我们的绘画过程就变成了这样

    public class Context {
    
        public static void main(String[] args) {
            PainterA a=new PainterA();
            a.draw();
            
            PainterB b=new PainterB();
            b.draw();
            
        }
    }

    过程简洁了许多,但还是有个问题,就是draw()方法在 PainterA 和 PainterB 中的逻辑是相同的,相当于同一段代码被写了两次,代码的冗余不是我们所想要看到的。

    我们可以考虑创建一个父类,将冗余的代码放到父类中,子类再继承父类,就避免了代码冗余。

    //父类Painter
    public abstract class Painter implements IPainter{
            //绘画
          @Override
          public void draw(){
              drawOutLine();
              setColor();
          }
    }
    
    
    //PainterA类
    public class PainterA extends Painter{
    
        //绘制线稿
        @Override
        public void drawOutLine(){
            System.out.println("绘制精致的线稿");    
        }
            
        //上色
        @Override
        public void setColor(){
            System.out.println("涂上色彩鲜艳的颜色");
        }
        
    }
    
    
    //PainterB类
    public class PainterB extends Painter{
        
        //绘制线稿
        @Override
        public void drawOutLine(){
            System.out.println("绘制粗略的线稿");
        }
        
        //上色
        @Override
        public void setColor(){
            System.out.println("涂上淡色");
        }
        
    }

    到这里我们可以看到整个结构体系,父类是负责实现draw()方法,接口里面的其他方法交由子类去强制实现。记不记得继承里面也有一种要强制子类实现的方法?对,就是抽象方法。那么我们此时完全可以使用抽象方法来取代接口的作用,使得整个系统中只包含父类和子类两层结构,结构更加清晰。

    对父类进行修改:

    public abstract class Painter{
        
        //画线稿
        protected abstract void drawOutLine();
        
        //上色
        protected abstract void setColor();
        
        //绘画
        public final void draw(){
            drawOutLine();
            setColor();
        }
    }

    子类PainterA,PainterB 不需要进行任何改动, 这时的父类Painter就是一个标准的模板了,它的draw()方法就是模板方法。(注意:我们一般会在模板方法加上 final 关键字防止被覆写)

    假如我们有这样一个扩展,我们需要增加一个画师对象PainterC,但是这个画师不按常理出牌,他画画一般不画线稿,直接上色(厚涂大大上线。。。),如果按照之前的模板就无法满足画师C( 即使drawOutLine()方法什么都不写,但也确实是被执行了,并不算跳过了这一环节 ),对于这种扩展,我们可以使用钩子方法:

    public abstract class Painter{
    
        //画线稿
        protected abstract void drawOutLine();
        
        //上色
        protected abstract void setColor();
        
        //绘画
        public final void draw(){
            if(needDrawLine()){
                drawOutLine();
            }
            setColor();
        }
        
        public boolean needDrawLine(){
            return true;
        }
        
    }

    我们可以看到,Painter中加了一个新的方法 needDrawLine() ,然后在draw()方法中进行了判断,如果needDrawLine()方法返回 true,则绘制线稿,我们将该方法默认返回true,也就是说默认情况下都需要画线稿。

    对于画师C,我们可以重写needDrawLine()方法,返回false,这样就可以不用执行drawOutLine()的操作了,代码如下:

    public class PainterC extends Painter{
        
        //绘制线稿
        @Override
        public void drawOutLine(){
            System.out.println("该方法不会被执行");
        }
        
        //上色
        @Override
        public void setColor(){
            System.out.println("开始厚涂");
        }
        
        @Override
        public boolean needDrawLine() {
            return false;
        }
        
    }

    当然,我们还可以暴露一个方法,让外部自己去控制是否调用,将PainterC 进行修改

    public class PainterC extends Painter{
        
        private boolean need;
    
        //绘制线稿
        @Override
        public void drawOutLine(){
            System.out.println("该方法不会被执行");
        }
        
        //上色
        @Override
        public void setColor(){
            System.out.println("开始厚涂");
        }
        
        @Override
        public boolean needDrawLine() {
            return this.need;
        }
        
        public void setDrawLine(boolean need){
            this.need=need;
        }
        
    }

    我们可以通过给setDrawLine()方法传入不同的boolean值,来控制是否调用drawOutLine()方法,去画线稿。

    模板方法模式就分析到这里。

    当我们在重构代码,或者去除代码冗余时,模板方法模式会是一个不错的选择。

  • 相关阅读:
    简单实用游标更改数据
    C# Http以文件的形式上传文件
    简单例子理解数据库事务
    安卓 隐藏按钮
    jQuery EasyUI API 中文文档
    Linux搭建Tomcat环境
    linux教程之一
    Android服务之PackageManagerService启动源码分析
    DSP、Media、AdExchanger之间的关系及交互流程
    Unity3D中的Coroutine具体解释
  • 原文地址:https://www.cnblogs.com/weimore/p/7256524.html
Copyright © 2020-2023  润新知