模板方法设计模式

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

强调:这里面的重定义既可以是延迟到子类必须实现的方法,也可以是父类中可以被重写的方法。

Paste_Image.png

正如上图中,算法骨架中operation3与operation4都是要求子类必须去实现的,但是如果用户需要的话,也可以重定义operation2,operation5等。

Father:抽象类,用来定义算法的骨架和原语操作,在这个类里面还可以提供算法中通用的实现。
Son:具体实现类。用来实现算法骨架中的某些特定步骤,完成跟特定子类相关的功能。

业务

不同角色的用户登录功能,在购物类网站开发中,经常会出现一个网站分为前台和后台(这里的后台主要是管理员或者提供商,不是服务端)的功能,但是都有一个共同点,那就是登录功能。再仔细的区分一下会发现,这些登录操作其实是拥有不同权限、不同角色的用户登录。

登录逻辑大致分为以下几个步骤:

1、 前台(客户端):用户输入用户名、密码、验证码等信息,提交请求
2、 后台(服务端):获取用户输入信息(对密码进行相应的操作),操作数据库
3、 后台(服务端):判断从前台传递过来的数据,是否和数据库中的记录匹配
4、 如果匹配从数据库中获取一条或几条用户信息,剪辑数据,返回给前台有用的信息。如果不匹配返回前台显示错误信息错误信息。

当前角色为:普通用户、供应商用户

登录流程图如下:

Paste_Image.png

不用模板的实现与使用模板的实现流程图如下

Paste_Image.png

在未使用模板方法的流程图中可以看出:

1、不同用户登录流程大体相同
2、重复或者相似的代码太多
3、如果后期业务修改登录这块的业务,可能就需要修改两个部分,如果再添加几个不同权限的用户,可能需要更多相似的代码。

这个时候就要考虑使用模板方法设计模式了。

无模板方法模式的示例代码:

普通用户登录

package templatemethod.notemplate;
/**
 * 普通用户登录Login
 */
public class UserLogin {
    public boolean login(LoginModel loginModel){
        //对用户传入的passworld进行加密,然后拿着用户名,加密密后的密码,用户类型查找用户
        loginModel.setPassword(encryptPwd(loginModel.getPassword()));
        UserModel userModel = findUserByInfo(loginModel);
        System.out.println(userModel);
        if(userModel!=null)
            return true;
        else
            return false;
    }

    public UserModel findUserByInfo(LoginModel loginModel) {
        //如果查找到用户就返回当前用户,此处模拟为查找到用户
        return new UserModel("1","张飞","1994-05-21");
    }

    public String encryptPwd(String password) {
        return password +"_base64";
    }
}

供应商用户登录

package templatemethod.notemplate;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
 * 供应商用户登录
 */
public class SupplierLogin {
    public boolean login(LoginModel loginModel){
        SupplierModel supplierModel = findUserByInfo(loginModel);
        System.out.println(supplierModel);
        if(supplierModel!=null)
            return true;
        else
            return false;
    }
    
    public SupplierModel findUserByInfo(LoginModel loginModel) {
        //对用户传入的passworld进行加密,然后拿着用户名,加密密后的密码,用户类型查找用户
        //如果查找到用户就返回当前用户,此处模拟为查找到用户
        String encryptPwd = encryptPwd(loginModel.getPassword());
        return new SupplierModel("2","种花家公司","丝绸,陶瓷");
    }
    /**
     * 对字符串进行MD5加密
     * @param password
     * @return
     */
    public String encryptPwd(String password) {
        try {
            MessageDigest md5=MessageDigest.getInstance("MD5");
            return new String(md5.digest(password.getBytes("utf-8")));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return password;
    }
}


执行流程几乎相同。如果在流程中需要修改其它的操作,比如把操作步骤换一下位置,那么得同时修改两份代码。而且在代码结构上,用了相同的方法,造成代码的重复。

使用模板设计模式修改

package templatemethod.notemplate;
/**
 * 模板方法设计模式
 */
public abstract class LoginTemplate<T> {
    /**
     * 定义算法的骨架,用final修饰不能修改
     * @param loginModel
     * @return
     */
    public final  boolean login(LoginModel loginModel){
        //1、对获取到的密码进行加密
        String encryptPwd = encryptPwd(loginModel.getPassword());
        loginModel.setPassword(encryptPwd);
        //2、从数据库中查找数据
        T resultModel = findUserByInfo(loginModel);
        System.out.println(resultModel);
        if(resultModel!=null)
            return true;
        else
            return false;
    }
    /**
     * 原语操作,子类必须要实现,因为查询不同的数据,涉及到的表可能也不相同,结果也会不同
     * @param loginModel
     * @return
     */
    public abstract  T findUserByInfo(LoginModel loginModel);
    /**
     * 钩子操作,提供默认实现,子类可以对该方法进行重写
     * @param password
     * @return
     */
    public String encryptPwd(String password) {
        return password +"_base64";
    }
}


其子类实现

package templatemethod.notemplate;
/**
 * 普通用户登录
 */
public class UserLogin2 extends LoginTemplate<UserModel> {

    @Override
    public UserModel findUserByInfo(LoginModel loginModel) {
        return new UserModel("1","张飞","1994-05-21");
    }
}


package templatemethod.notemplate;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
 * 供应商用户登录
 */
public class SupplierLogin2 extends LoginTemplate<SupplierModel> {

    @Override
    public SupplierModel findUserByInfo(LoginModel loginModel) {
        return new SupplierModel("2","种花家公司","丝绸,陶瓷");
    }
    /**
     * 不同用户涉及到的密码保存方式不同,Supplier用户需要进行MD5
     */
    @Override
    public String encryptPwd(String password) {
        try {
            MessageDigest md5=MessageDigest.getInstance("MD5");
            return new String(md5.digest(password.getBytes("utf-8")));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return password;
    }

}


从代码示例中可以看出,我们将算法估计抽离出来(login方法中的登录步骤),而不同角色的用户登录,获取用户信息的方式不同(这些操作被延迟到子类中去实现)

模板实现类图

Paste_Image.png

模式的功能:

    模板方法的功能在于固定算法的骨架,而让具体的实现可以延迟到子类中进行扩展。比如在DAO的实现中,设计通用的增删该查功能,然后开发者只需要在实现的子类中处理特定的方法,就可以实现不同的操作。
    模板方法可以控制子类的扩展。因为在父类中已经定义好算法的骨架,只是在几个固定的点才会调用到被子类实现的方法,因此也就只允许在这几个点来扩展功能,这些个可以被子类扩展和覆盖的方法称为钩子方法。
    钩子方法:就是可以在父类中提供默认的实现,如果有必要,可以再子类的重写该方法。和模板方法还不太一样(模板方式在子类中是必须要实现的方法)。

模板方法与实现接口的区别

    抽象类有这样一个作用:约束子类行为,为子类提供公共方法。
    而模板方法模式中需要固定定义算法的骨架,而这个骨架在子类中是不能够重写的,里面具体的步骤的实现又可能是各不相同的,恰好符合选择抽象类的原则。这里面的具体步骤在子类中去实现,约束了子类的行为。
    至于为什么不选择接口,因为接口根本就没法固定算法的骨架,执行流程没法控制。

设计理念

变与不变!
分析程序中哪些功能是可变的,哪些功能是不变的,把不变的部分抽象出来,进行公共的实现,把变化的部分分离出去,用接口来封装隔离,或用抽象类来约束子类型为。
模板方法中,不变的部分是算法骨架(各种方法的执行顺序),变化的部分是抽象出去被子类实现或者重写的方法。

业务拓展

1、 不修改算法骨架,此时只需要提供新的子类即可
2、 算法骨架中需要添加新的功能,修改骨架。
3、 既要添加新的功能,又要实现新的子类。此时考虑修改骨架

模板方法结构

1、 模板方法:定义算法骨架的方法
2、 具体操作:某些步骤的实现方法是固定不变的,直接在模板中定义实现。如果子类需要可以提供给子类protected final 修饰,不需要的话直接private修改即可
3、 普通方法:跟算法骨架没多大关系,辅助功能
4、 原语操作:模板中定义的抽象操作,是骨架中需要实现的操作,且在父类中没法确定,需要子类去实现
5、 钩子操作:在模板中提供默认实现操作。通常被视为可以拓展的功能,但是这个扩展是非必须的,子类有选择的去覆盖它。
6、 Factory method:获取子对象的时候,可以考虑使用工厂方法模式来获取,其具体的实现延迟到子类中去操作。


package templatemethod.notemplate;
/**
 * 复杂模板方法结构
 */
public abstract class AbstractTemplate {
    //模板方法:定义算法的骨架
    public final void templateMethod(){
        //具体操作
        operation();
        //原语操作
        primitiveOperation1();
        primitiveOperation2();
        //钩子操作
        hookOperation();
    }
    /**
     * 具体操作,如果使用private修饰不让子类访问,如果子类想要访问,可以使用public final或者protected final修饰,保证子类修改不了
     */
    private void operation(){
    }
    /**
     * 普通方法,不是算法骨架中的特定步骤
     */
    protected void commonOperation(){
    }
    /**
     * 原语操作1,算法中的必要步骤,父类无法确定如何真正实现,需要子类来实现
     */
    protected abstract void primitiveOperation1();
    protected abstract void primitiveOperation2();
    /**
     * 钩子操作,算法中的步骤,提供缺省实现,子类选择是否去实现
     */
    protected void hookOperation(){
        //在这里提供缺省的实现
    }
    /**
     * 工厂方法,主要是用来保护子类对象的创建过程
     * @return 创建的某个算法实现需要的对象
     */
    protected abstract<T> T createOneObject();
}
//createOneObject这个比较有意思

如果是简单的模板方法,只需要提供算法骨架和原语操作即可。

API中使用模板

Collections.sort()
InputStream.read()
可参考连接:
Java中模板方法和钩子的使用示例

总结:模板方法的本质和优缺点

模板方法的本质:固定算法骨架
优点:

1、模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码,实现代码复用
2、子类实现算法的某些细节,有助于算法的扩展
3、通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”。

缺点:

1、 算法骨架不易升级
2、 每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容