Android 设计模式Android开发经验谈程序员

08、模版方法模式--Template-Method

2017-12-10  本文已影响118人  TigerChain
模版方法模式大纲

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

教程简介

正文

一、什么是模版方法模式

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

1、烧茶、煮咖啡

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

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

2、高考答题

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

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

模版方法模式的定义

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

模版方法模式的特点

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

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

模版方法模式的目的

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

模版方法简单框架

模版方法模式的结构

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

模版方法模式简单的 UML

模版方法模式简单的 UML

二、模版方法模式举例

1、把大象装冰箱

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

把大象装冰箱简单的 UML

把大象装冰箱简单的 UML

根据 UML 撸码

/**
 * Created by TigerChain
 * 抽象冰箱
 */
public interface IRefrige {
    //取得品牌的名字
    String getRefrigeModel() ;
    //设置冰箱品牌
    void setModel(String model) ;
}
/**
 * Created by TigerChain
 * 定义动物的抽象类
 */
public abstract class Animal {
    // 取得动物的名字
    abstract String getAnimailName() ;
}
/**
 * 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();        //第三步
    }
}

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

/**
 * 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 步把冰箱门盖上");
    }
}
/**
 * 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 ;
    }
}
/**
 * Created by TigerChain
 * 创建一个动物--大象
 */
public class Elephant extends Animal {

    @Override
    String getAnimailName() {
        return "大象";
    }
}
/**
 * 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);
    }
}
把大家装冰箱的结果

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

抽象接口 定义具体的模版 修改测试类

运行结果和 8 中的结果是一样的,这里就不贴图了,具体的代码可以看:https://github.com/githubchen001/designpattern_javademo/tree/master/src/template/putAnimalInRefrigerator

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

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

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

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

根据 UML 撸码

/**
 * 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() ;
}

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

/**
 * Created by TigerChain
 * 定义一个 JavaBean 对应数据库中的表
 */
public class Person {
    private int id ;         // id
    private String name ;    // 姓名
    private int age ;        // 年龄
    private String address ; // 地址
    // 省略 setter 和 getter 方法
    ...   
}
**
 * 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;
    }
}
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() ;

    }
}
运行结果

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

3、考试答题

考试卷对每个考生来说都是一样的「考试卷就是一个模版」,至于每个人如何答题那是每个考生的事情,针对考试答题我们可以使用模版方法模式来模拟这一过程,代码就不贴了「我上传到了 github 上」,具体看这里:https://github.com/githubchen001/designpattern_javademo/tree/master/src/template/examination_page

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 步骤,这就是钩子方法,大家自行感受一下,其实就是一个开关而已

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

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

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

draw 核心代码

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

2、最熟悉的 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 知道它是模版模式吗?」

3、封装 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 ,代码不贴了,直接上地址:https://github.com/githubchen001/DesignPattern 查看 TemplateMethod 相关代码即可

最终运行效果如下:

模版方法模式实现 BaseActivity 结果

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

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

优点

缺点

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

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

六、总结

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

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


TigerChain
上一篇下一篇

猜你喜欢

热点阅读