【pattern】设计模式(2) - 模版方法模式

前言

  一晃一年又过了,还是一样的渣。

  一晃2周又过去了,还是没有坚持写博客。

  本来前2天说填一下SQL注入攻击的坑,结果看下去发现还是ojdbc.jar中的代码,看不懂啊。这坑暂时填不动,强迫在元旦最后一天写一篇出来吧。

正文

  在所有的设计模式中,个人觉得最简单、易学易用、使用率也高的其实是 - 模版方法模式(Template Method)。

  相对单例,singleton我实际开发其实还没自己写过。也不清楚哪些情况下会用到singleton。

  但Template Method缺写过几次,虽然严格上说可能不是Template Method。但最近写的代码结构,都是与Template Method的结构很类似。

  主要是:(个人简单的理解,不一定全面)

    1、方法/类遵循单一职责、开闭原则。

       单一职责:把方法拆成块,阅读方便。(代码简洁之道)

       开闭原则:对扩展开放、修改关闭。

    2、方法复用。实际业务代码中,有些逻辑就应该调一个方法。(见下面demo)

Demo

  例子1:

    新生报名,需要登记个人信息。现在已有的途径是:1、填写表格,交给学校。2、自己去学校web官网填写。  后面扩展的途径:a、手机登陆学校app填写。

    i. 不管什么途径填写,必须登记的有:姓名、年龄。

    ii. 针对不同的填写途径,可能存在附带必须条件。 附带条件如下:

      1、出生日期。

      2、兴趣爱好。

      a、手机号码。

/** 学生信息 */
public class StudentBean {
    public final static int TYPE_FORM = 1;
    public final static int TYPE_WEB = 2;
    public final static int TYPE_APP = 3;
    /* 任何条件必须*/
    private String name;
    /* 任何条件必须*/
    private int age;
    /* TYPE_FORM必须*/
    private Long birthday;
    /* TYPE_WEB必须*/
    private String hobby;
    /* TYPE_APP必须*/
    private String phoneNumber;
    private int registerType;

    //...
}

一、什么是模版方法模式?

  预先定义好一些步骤,按这些步骤一步一步的执行。好比:起床、刷牙、吃早餐、坐车、上班。

摘自baike:

  无处不在的Template Method:如果你只想掌握一种设计模式,那么它就是Template Method

  意图(Intent):定义一个操作中的算法骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。(《设计模式》GoF)

二、实现demo例子

// 模板类
public abstract class ParentClass {
    /** 子类扩展方法:保存前 */
    public void saveBefore(StudentBean bean){

    }
    /** 子类扩展方法:保存后 */
    public abstract void saveAfter(StudentBean bean);

    /** 模版方法 */
    public final void saveStudentInfo(StudentBean bean){
        saveBefore(bean);
        // dao.save(bean);// 保存进数据库
        System.out.println(this.getClass().getSimpleName() + ": " + JSON.toJSONString(bean));
        saveAfter(bean);
    }
}
/**
 * 填表登记形式必须登记:出生日期
 */
public class FormClass extends ParentClass {
    @Override
    public void saveAfter(StudentBean bean) {
        //any code
    }

    @Override
    public void saveBefore(StudentBean bean) {
        bean.setRegisterType(StudentBean.TYPE_FORM);
        bean.setBirthday(20170102L);
    }
}
/**
 * web登记形式必须登记:兴趣爱好
 */
public class WebClass extends ParentClass {
    @Override
    public void saveAfter(StudentBean bean) {
        //any code
    }

    @Override
    public void saveBefore(StudentBean bean) {
        bean.setRegisterType(StudentBean.TYPE_WEB);
        bean.setHobby("吃饭睡觉打豆豆");
    }
}
/**
 * app登记形式必须登记:电话号码
 */
public class AppClass extends ParentClass {
    @Override
    public void saveAfter(StudentBean bean) {
        //any code
    }

    @Override
    public void saveBefore(StudentBean bean) {
        bean.setRegisterType(StudentBean.TYPE_APP);
        bean.setPhoneNumber("10086");
    }
}

说明:ParentClass是模版方法类。FormClass、WebClass、AppClass都是具体实现。

// 测试
public class TestTemplate {
    private static StudentBean f = new StudentBean("form",1);
    private static StudentBean w = new StudentBean("web",2);
    private static StudentBean a = new StudentBean("app",3);
    public static void main(String[] args) {
        FormClass form = new FormClass();
        form.saveStudentInfo(f);

        WebClass web = new WebClass();
        web.saveStudentInfo(w);

        AppClass app = new AppClass();
        app.saveStudentInfo(a);
    }
}
// 测试结果
FormClass: {"age":1,"name":"form","birthday":20170102,"registerType":1}
WebClass:  {"age":2,"name":"web", "hobby":"吃饭睡觉打豆豆","registerType":2}
AppClass:  {"age":3,"name":"app", "phoneNumber":"10086","registerType":3}

三、解析

  模版方法模式的核心是:1、对象优先调用本身的属性/方法。2、对象类本身没有的属性/方法,根据继承关系,依次查找其直接父类并调用。

  在demo中,在具体子类FromClass、WebClass、AppClass中都未定义saveStudentInfo()方法,所以调用的都是ParentClass中的saveStudentInfo()。

  然而,在ParentClass中定义了saveBefore()、声明了saveAfter()。但是,因为最后调用saveStudentInfo()的对象是FormClass、WebClass、AppClass。

  所以,并没有用到ParentClass中的saveBefore()、saveAfter()。(优先使用对象自身的属性/方法)

  其次对于demo中各部分声明的修饰符,可以根据实际情况选择。

  例如demo:为了确保模版方法不被重写,所以用final修饰了saveStudentInfo()方法;

    为了确保方法需要被重写(正常的Template Method可能都是有一套默认算法,以此保证默认的模版方法可用。即ParentClass不是抽象类,saveStudentInfo()默认有一套实现),所以我定义了抽象方法saveAfter()。

四、重点

  1、很明显的违反SOLID中的L(Liskov Substitution Principle) - 里氏替换原则 : 任何父类可以出现的地方,子类一定可以出现。

    但设计原则并不是强制约束,而且模版方法模式本身其实就不算是重写模版方法,而是实现/完整了模版方法。最终不管是子类/父类调模版方法,其目的、结果是没变的。

  2、个人觉得模版方法的写法很推荐。即单一职责,把方法拆分开,每个方法只做自己份内的“一件事”。

    例如excel导入数据,可以一个方法把:验证excel数据、excel转换JavaBean、保存JavaBean,都写在一个300行的代码中。也可以写在3个方法,每个方法100行。

    多看各种源码、《代码简洁之道》、《代码之美》多体验下SOLID,体验代码和代码之间的区别。

posted @ 2017-01-03 00:38  淡丶无欲  阅读(273)  评论(0编辑  收藏  举报