人人都会设计模式---模版方法模式--Template-Method

模版方法模式大纲

PS:转载请注明出处 做者: TigerChain 地址: www.jianshu.com/p/6c6191a47… 本文出自 TigerChain 简书 人人都会设计模式java

教程简介git

  • 一、阅读对象 本篇教程适合新手阅读,老手直接略过
  • 二、教程难度 初级,本人水平有限,文章内容不免会出现问题,若是有问题欢迎指出,谢谢
  • 三、Demo 地址:Android Demo---github.com/githubchen0… 对应的 TemplateMethod Java Demo:github.com/githubchen0…

正文github

1、什么是模版方法模式

一、生活中的模版方法模式

一、烧茶、煮咖啡算法

身为苦逼的程序猿(媛),必定是茶叶和咖啡的忠实粉丝,多少个夜晚加班加点,累了困了喝红牛---不对是喝茶叶、咖啡「咱们无形中使用了一个设计模式--模版方法模式」。咱们知道无论是烧茶、煮咖啡都基本上分为如下几个步骤:数据库

  • 一、烧水
  • 二、把茶叶或咖啡放入水壶中
  • 三、加热不停的煮
  • 四、把煮好的茶叶或咖啡到入杯子中
  • 五、拿起杯子喝「不能直接喝,当心你的嘴」

咱们看到除了原材料放入的不一样「茶叶和咖啡」,其它的方法都一毛同样,那么咱们把这些方法就能够制定为一个模版「至关于咱们有一个既能烧茶又有煮咖啡的器具」,这就是模版定义了一个基本框架canvas

二、高考答题设计模式

说了上面的例子,你们可能还懵懵的。那么来讲一个更实际的例子,参加过考虑的同窗都知道一张考试卷子对全部的同窗都是如出一辙的,这个卷子就是一个模版,以数学卷子为例吧:有选择题、填空题、判断题、应用题「这都是固定的」--这就是一个题的框架,是一个模版,至于每位考生如何答题那就考生的事情app

二、程序中的模版方法模式

模版方法模式的定义框架

定义一个操做算法的骨架「咱们知道这个算法所须要的关键步骤」,而将一些步骤的实现延迟到子类中去实现。通俗的说模版就是一个抽象类,方法就是策略「一些固定的步骤」。模版方法模式是一个行为型模式ide

模版方法模式的特色

算法的结构不变,子类能够改变模版的某些步骤的实现方式,模版方法就是抽象的封装,通常状况下,模版方法中有一些具体方法「部分逻辑」,抽象方法实现其它剩余的逻辑「子类不一样实现的方式就不一样」,而且部分逻辑和剩余逻辑共同组成了算法的结构「通常是执行流程,这些流程是固定的,可是具体的实现细节不同,就可使用模版方法」

封装不变的部分,扩展可变的部分「可变的部分交给子类去实现」

模版方法模式的目的

模版方法模式的目的就是让子类扩展或者具体实现模版中的固定算法的中的某些算法的步骤

模版方法简单框架

模版方法模式的结构

角色 类别 说明
AbstractClass 抽象类 抽象模版类
ConcreateClass 具体模版 能够有多个「由于每一个具体模版实现的内容可能不同」
HookMethod 钩子方法 不是必须的,是一个开关,用来提供某些方法是否须要调用

模版方法模式简单的 UML

模版方法模式简单的 UML

2、模版方法模式举例

一、把大象装冰箱

把大象装冰箱一共分为几步?咱们都知道三步:第一步把冰箱门打开,第二步把大象装进去,第三步把冰箱门盖上。咱们把装大象的这三步运做能够看作一个算法的步骤「这个步骤不变」,可是具体的你是使用松下冰箱装大象,仍是海尔冰箱装大象,再进一步说使用冰箱装全部动物,大象只是其中的一种,那么就须要抽象出一个模版来,咱们使用模版方法模式来实现这一过程

把大象装冰箱简单的 UML

把大象装冰箱简单的 UML

根据 UML 撸码

  • 一、抽象出一个冰箱接口 IRefrige.java
/** * Created by TigerChain * 抽象冰箱 */
public interface IRefrige {
    //取得品牌的名字
    String getRefrigeModel() ;
    //设置冰箱品牌
    void setModel(String model) ;
}
复制代码
  • 二、抽象一个动物类 Animail.java
/** * Created by TigerChain * 定义动物的抽象类 */
public abstract class Animal {
    // 取得动物的名字
    abstract String getAnimailName() ;
}
复制代码
  • 三、定义抽象模版方法类 AbstractMothodWork.java
/** * Created by TigerChain * 抽象的模版类 */
public abstract class AbstractMothodWork {
    //打开冰箱
    abstract void open(IRefrige iRefrige) ;
    //把动物装进去
    abstract void putin(Animail animail) ;
    //把冰箱门盖上
    abstract void close() ;

    // 模版方法 定义算法骨架 为了防止子类篡改模版方法步骤,加一个 final
    public final void handle(IRefrige iRefrige,Animail animal){
        this.open(iRefrige); //第一步
        this.putin(animail); //第二步
        this.close();        //第三步
    }
}
复制代码

咱们看到冰箱装动物的步骤是固定的,可是具体步骤内部实现交给子类去处理吧,这就是模版模式的使用场景

  • 四、来一个具体的模版 ConcreateMethodWork.java
/** * Created by TigerChain * 具体的模版类 */
public class ConcreateMethodWork extends AbstractMothodWork {

    private IRefrige iRefrige ;
    private Animail animal ;

    @Override
    void open(IRefrige iRefrige) {
        this.iRefrige = iRefrige ;
        System.out.println("第 1 步把 "+iRefrige.getRefrigeModel()+" 门打开");
    }

    @Override
    void putin(Animail animail) {
        this.animail = animal ;
        System.out.println("第 2 步把 "+animail.getAnimailName()+" 装进去");
    }

    @Override
    void close() {
        System.out.println("第 3 步把冰箱门盖上");
    }
}
复制代码
  • 五、来一个松下冰箱「固然能够是任意品牌的冰箱」 PanasonnicRefrige.java
/** * Created by TigerChain * 定义一台松下冰箱 */
public class PanasonnicRefrige implements IRefrige {

    private String model ;
    @Override
    public String getRefrigeModel() {
        return this.model!=null?this.model:"";
    }

    @Override
    public void setModel(String model) {
        this.model = model ;
    }
}
复制代码
  • 六、被装的对象大象「固然还能够任何动物」 Elephant.java
/** * Created by TigerChain * 建立一个动物--大象 */
public class Elephant extends Animal {

    @Override
    String getAnimailName() {
        return "大象";
    }
}
复制代码
  • 七、测试一下 Test.java
/** * Created by TigerChain * 测试类 */
public class Test {
    public static void main(String args[]){
        // 要有冰箱
        IRefrige panasonnicRefrige = new PanasonnicRefrige() ;
        panasonnicRefrige.setModel("松下冰箱");

        // 要有动物,这里是装大象
        Animail elephant = new Elephant() ;

        //来个模版
        AbstractMothodWork  work = new ConcreateMethodWork() ;
		// 执行步骤
        work.handle(panasonnicRefrige,elephant);
    }
}
复制代码
  • 八、运行查看结果

把你们装冰箱的结果

到此为止,咱们就把大象装到冰箱里面了,固然你也能够把老虎、狼、猫装进冰箱「扩展模版便可」,其实咱们使用回调也能够实现一样的功能「本质上是模版方法的一种变异---是什么?仍是模版方法模式」,咱们使用回调方式修改上面代码「不破坏原来的结构,咱们直接新加类」

  • 九、咱们把想要扩展的方法所有抽象成接口,定义 ITemplate.java

抽象接口

  • 十、定义具体的模版 ConCreateTemplate.java 因为咱们把想要扩展的模版方法都抽象出来了,因此咱们新建模版的时候就不用抽象了「即定义具体的模版就能够了」

定义具体的模版

  • 十一、修改测试类 Test.java 只贴出调用代码

修改测试类

运行结果和 8 中的结果是同样的,这里就不贴图了,具体的代码能够看:github.com/githubchen0…

二、数据库增、删、改、查封装

操做过数据库的朋友对数据的增、删、改、查再熟悉不过了,数据库无非就是干这个的。那么咱们可使用模版方法模式把数据库的增、删、改、查封装,至于查什么,改什么,交给具体的模版吧,典型的模版方法模式

数据库增、删、改、查 简单的 UML

数据库增、删、改、查 简单的 UML

根据 UML 撸码

  • 一、定义抽象的模版--数据库增、删、改查抽象类 AbstractDAO.java
/** * Created by TigerChain * 定义抽象的数据库增、删、改、查的模版 */
public abstract class AbstractDAO<T> {
    // 增长数据
    abstract void add(T t) ;
    // 根据 id 删除数据
    abstract void delete(int id) ;
    // 更新数据
    abstract void update(T t) ;
    // 根据 id 查找数据
    abstract T findById(int id);
    // 查找全部数据
    abstract List<T> findall() ;
}
复制代码

咱们这里以泛型去接收实体类,至于查那个交给子类去实现--这样就把共同点抽象出来了

  • 二、咱们操做用户表吧,定义一个 Person.java
/** * Created by TigerChain * 定义一个 JavaBean 对应数据库中的表 */
public class Person {
    private int id ;         // id
    private String name ;    // 姓名
    private int age ;        // 年龄
    private String address ; // 地址
    // 省略 setter 和 getter 方法
    ...   
}
复制代码
  • 三、定义一个操做用户表的 DAO PersonConCreateDAO.java
**
 * Created by TigerChain
 * 一个具体的模版对用户表的增、删、改、查
 */
public class PersonConCreateDAO extends AbstractDAO<Person> {
    // 库中的用户列表
    private List<Person> persons = new ArrayList<>() ;

    @Override
    void add(Person person) {
        // 实际上应该作插入数据库操做,为了简单咱们直接输出语句
        persons.add(person) ;
        System.out.println("添加了 person "+person.toString());
    }

    @Override
    void delete(int id) {
        System.out.println("删除了 id 为 "+id+" person "+persons.get(id-1));
        persons.remove(id-1) ;
    }

    @Override
    void update(Person person) {
        person.setId(1);
        person.setName("TigerChain");
        person.setAge(30);
        person.setAddress("中国陕西西安");

        System.out.println("更新了 person "+person.toString());
    }

    @Override
    Person findById(int id) {
        // 实际这里应该从数据库中查出数据,为了简单模拟一个数据
        Person person = new Person() ;
        if(id ==1){
            person.setId(1);
            person.setName("TigerChain");
            person.setAge(28);
            person.setAddress("中国陕西");
        }
        System.out.println("查找id 为 "+id+" 的 person "+person.toString());
        return person;
    }

    @Override
    List<Person> findall() {
        System.out.println("查找全部的 person "+ persons.toString());
        return persons;
    }
}
复制代码
  • 四、测试一下 Test.java
public class Test {
    public static void main(String args[]){
        // 模拟两个用户数据
        Person person1 = new Person() ;
        person1.setId(1);
        person1.setName("TigerChain");
        person1.setAge(28);
        person1.setAddress("中国陕西");

        Person person2 = new Person() ;
        person2.setId(2);
        person2.setName("小陈");
        person2.setAge(30);
        person2.setAddress("中国陕西西安");

        PersonConCreateDAO personConCreateDAO = new PersonConCreateDAO() ;

        // 给库中添加用户
        personConCreateDAO.add(person1);
        personConCreateDAO.add(person2);

        // 更新用户 1 的数据
        personConCreateDAO.update(person1);
        personConCreateDAO.findById(1);
        personConCreateDAO.findall() ;

        // 删除一条数据
        personConCreateDAO.delete(1);
        // 查找全部库中的数据
        personConCreateDAO.findall() ;

    }
}
复制代码
  • 五、运行查看结果

运行结果

至此,咱们就使用模版方法模式实现了数据库的增、删、改、查、功能,至于你想操道别的表那直接写一个具体的模版继承抽象模版便可,你们动手写一下,好好的体验一下模版方法模式

三、考试答题

考试卷对每一个考生来讲都是同样的「考试卷就是一个模版」,至于每一个人如何答题那是每一个考生的事情,针对考试答题咱们可使用模版方法模式来模拟这一过程,代码就不贴了「我上传到了 github 上」,具体看这里:github.com/githubchen0…

**PS:**模版方法模式除了抽象模版、具体模版以外,还可能会有一个钩子方法「Hook」,也就是说抽象模版中把规定好了算法的步骤 1 2 3 4 ,若是我只想使用 1 2 3 ,不想使用 4 呢?Hook 方法就派上用场了,如下是抽象模版带 Hook 的伪代码

public abstract class AbstractTemplateMethod{

	abstract void step1() ;
	abstract void step2() ;
	abstract void step3() ;
	abstract void step4() ;
    void step5() ;
	// 模版方法
    public final void execute(){
		
	 this.step1() ;
     this.step2() ;	
     this.step3() ;
	 if(isUseStep4()){
       this.step4() ;
     }
     this.step5() ;
	}
    // 钩子方法
    protected boolean isUseStep4(){
		return false ;
    }
}
复制代码

子类重写 isUseStep4() 的方法返回 true 或 fals 决定是否使用 step4 步骤,这就是钩子方法,你们自行感觉一下,其实就是一个开关而已

3、Android 源码中的模版方法模式

一、View 中的 draw(Canvas canvas) 方法

咱们自定义 View 的时候有时调用 ondraw(Canvas canvas) 方法,这里就用到了模版方法模式,咱们来看一下 ondraw 在什么状况下调用「在 View 的 draw() 方法中调用了」,看看 draw() 方法的核心代码

draw 核心代码

这里只不过把抽象方法改为了 protected 的一个空方法而已「本质上是同样的」,具体代理就不贴了,你们动手扒扒这部分源码,其实模版方法模式咱们常常用「只不过没有意识到而已」

二、最熟悉的 Activity

Activity 就是一个模版,其中生命周期的方法就是"不固定"的方法,若是要改变子类重写便可

public class Activity extends ApplicationContext {
      protected void onCreate(Bundle savedInstanceState);
 
      protected void onStart();
 
      protected void onRestart();
 
      protected void onResume();
 
      protected void onPause();
 
      protected void onStop();
 
      protected void onDestroy();
}
复制代码

这里定义成 protected 方法,那么这个方法既能够是固定的也能够是不固定的「子类实现就不固定,若是不实现就是固定的,很灵活」,Activity 就是一个模版方法模式「你每天使用 Activity 知道它是模版模式吗?」

三、封装 BaseActivity

作过 Android 的朋友确定都封装过 BaseActivity ,把一些共公的部分抽象出来,而后封装变化,好比咱们的 app 应用界面都有共公的头、下面是内容区域,以下图

封装 BaseActivity

而后不一样的界面写不一样的子类继承便可,咱们使用伪代码来模拟一下

public abstract class TemplateMethodActivity extends AppCompatActivity {
    private Button titlebar_btn_left,titlebar_btn_right ;// 左右按钮 
    private TextView titlebar_tv_center ; // 中间文字
    private RelativeLayout content ;      // 内容布局

    private View titleView ;
	 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
		// 加载模版 xml 文件
        setContentView(R.layout.templatemethod_activity);
        initView() ;
		// 设置子类布局
        setContentLayout(getLayoutResID());
        getLayoutResID() ;
        
        init() ;
    }
	// 初始化操做,好比修改按钮样式等
    protected abstract void init();
    // 取得子布局的 xml 文件
    protected abstract int getLayoutResID();
	// 设置到 content 布局上
    private void setContentLayout(int ResId) {
        LayoutInflater.from(this).inflate(ResId, content);
    }
    // 省略若干方法
}
复制代码

而后子类继承这个 Activity 重写抽象方法便可实现本身的界面内容,咱们写一个登陆界面继承 TemplateMethodActivity ,代码不贴了,直接上地址:github.com/githubchen0… 查看 TemplateMethod 相关代码便可

最终运行效果以下:

模版方法模式实现 BaseActivity 结果

怎么样,是否是一直在使用模版方法模式「只是不知道而已」

4、模版方法模式的优缺点

优势

  • 一、封装不变的部分,扩展可变的部分「交给子类去实现」,这是设计模式一惯的原则「开、闭原则」
  • 二、实现了代码复用

缺点

  • 继承关系自己的缺点,若是父类添加一个新的抽象方法,全部的子类都要改一遍--痛苦

5、模版方法模式 VS 策略模式

之前介绍过策略模式,是对算法的封装,而模版方法模式也是对算法执行,可是它们之间有明显的区别

  • **策略模式:**目的是使不一样的算法能够被相互替换,不影响客户端的使用

  • **模版方法模式:**针对定义一个算法的流程,而将一些不太同样的“具体实现步骤”交给子类去实现「不改变算法的流程」

6、总结

  • 一、抽象类就是一个模版,而接口是一个标准,按这样的规则能够肯定该使用接口仍是抽象类
  • 二、模版方法模式就是把不固定的步骤实现方式延迟到子类实现的一种方式,它是一种行为模式
  • 三、模版方法模式基本步骤是固定的「实际开发中会有不少变种,好比回调替换,没有固定的步骤全是不固定的等」
  • 四、 通常状况下为了防止子类去更新算法的实现步骤,在抽象的模版方法上加一个 final 关键字

到上为止,模版方法模式就介绍完了,仍是那句话,必定要动手试试哦,关注博主,更多精彩内容等着你,手把手教你学会知识点

公众号:TigerChain 欢迎你们关注--更多精彩内容等着你

TigerChain
相关文章
相关标签/搜索