设计模式15-模板方法

概念


定义

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的接口即可重新定义该算法的某些特定步骤。本质:“固定算法骨架”

结构与说明

模板模式有两个角色,抽象类和实体类。
抽象类。用来定义算法骨架和原语操作。在这个类里面,可以提供算法的通用实现
实体类。用来实现算法骨架中的某些步骤,完成跟特定子类相关的功能

这里写图片描述
原语操作就是抽象的操作,必须由子类提供实现的方法

核心

由于模板模式一般由算法骨架和原语操作组成的。其核心在算法骨架,算法骨架调用自身的原语操作方法,执行某些特定步骤。作为算法的子类,继承实现原语操作。那么对于用户来说,如果A算法去调用算法骨架方法,那么执行的所有步骤都是A原语操作算法的实现。所以,模板模式的核心是固定算法骨架

小实例


抽象类

/**
 * 定义模板方法、原语操作等的抽象类
 */
public abstract class AbstractClass {
    /**
     * 原语操作1,所谓原语操作就是抽象的操作,必须要由子类提供实现
     */
    public abstract void doPrimitiveOperation1();
    /**
     * 原语操作2
     */
    public abstract void doPrimitiveOperation2();
    /**
     * 模板方法,定义算法骨架
     */
    public final void templateMethod() {
        //1
        doPrimitiveOperation1();
        //2
        doPrimitiveOperation2();

    }
}

实体类

/**
 * 具体实现类,实现原语操作
 */
public class ConcreteClass extends AbstractClass {
    public void doPrimitiveOperation1() { 
        //具体的实现
    }
    public void doPrimitiveOperation2() { 
        //具体的实现
    }
}

一个简单的实例


这是关于登录的实例。有两种人的存在,一种普通用户的登录,一种工作人员的登录。然后会发现两种登录的相似程度极高。

不使用设计模式的做法

工作人员的登录

/**
 * 描述工作人员信息的数据模型
 */
public class WorkerModel {
    private String uuid,workerId,pwd,name;
    public String getUuid() {
        return uuid;
    }
    public void setUuid(String uuid) {
        this.uuid = uuid;
    }
    public String getWorkerId() {
        return workerId;
    }
    public void setWorkerId(String workerId) {
        this.workerId = workerId;
    }
    public String getPwd() {
        return pwd;
    }
    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
/**
 * 描述登录人员登录时填写的信息的数据模型
 */
public class LoginModel{
    private String workerId,pwd;
    public String getWorkerId() {
        return workerId;
    }
    public void setWorkerId(String workerId) {
        this.workerId = workerId;
    }
    public String getPwd() {
        return pwd;
    }
    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}
/**
 * 工作人员登录控制的逻辑处理
 */
public class WorkerLogin {
    /**
     * 判断登录数据是否正确,也就是是否能登录成功
     * @param lm 封装登录数据的Model
     * @return true表示登录成功,false表示登录失败
     */
    public boolean login(LoginModel lm) {
        //1:根据工作人员编号去获取工作人员的数据
        WorkerModel wm = this.findWorkerByWorkerId(lm.getWorkerId());
        //2:判断从前台传递过来的用户名和加密后的密码数据,和数据库中已有的数据是否匹配
        //先判断工作人员是否存在,如果wm为null,说明工作人员肯定不存在
        //但是不为null,工作人员不一定存在,
        //因为数据层可能返回new WorkerModel();因此还需要做进一步的判断
        if (wm != null) {
            //3:把从前台传来的密码数据,使用相应的加密算法进行加密运算
            String encryptPwd = this.encryptPwd(lm.getPwd());
            //如果工作人员存在,检查工作人员编号和密码是否匹配
            if (wm.getWorkerId().equals(lm.getWorkerId())
                    && wm.getPwd().equals(encryptPwd)) {
                return true;
            }
        }
        return false;
    }
    /**
     * 对密码数据进行加密
     * @param pwd 密码数据
     * @return 加密后的密码数据
     */
    private String encryptPwd(String pwd){
        //这里对密码进行加密,省略了
        return pwd;
    }
    /**
     * 根据工作人员编号获取工作人员的详细信息
     * @param workerId 工作人员编号
     * @return 对应的工作人员的详细信息
     */
    private WorkerModel findWorkerByWorkerId(String workerId) {
        // 这里省略具体的处理,仅做示意,返回一个有默认数据的对象
        WorkerModel wm = new WorkerModel();
        wm.setWorkerId(workerId);
        wm.setName("Worker1");
        wm.setPwd("worker1");
        wm.setUuid("Worker0001");
        return wm;
    }
}

普通人员的登录

/**
 * 描述登录人员登录时填写的信息的数据模型
 */
public class LoginModel {
    private String userId,pwd;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

}
/**
 * 描述用户信息的数据模型
 */
public class UserModel {
    private String uuid,userId,pwd,name;

    public String getUuid() {
        return uuid;
    }

    public void setUuid(String uuid) {
        this.uuid = uuid;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

/**
 * 普通用户登录控制的逻辑处理
 */
public class NormalLogin {
    /**
     * 判断登录数据是否正确,也就是是否能登录成功
     * @param lm 封装登录数据的Model
     * @return true表示登录成功,false表示登录失败
     */
    public boolean login(LoginModel lm) {
        //1:从数据库获取登录人员的信息, 就是根据用户编号去获取人员的数据
        UserModel um = this.findUserByUserId(lm.getUserId());
        //2:判断从前台传递过来的登录数据,和数据库中已有的数据是否匹配
        //先判断用户是否存在,如果um为null,说明用户肯定不存在
        //但是不为null,用户不一定存在,因为数据层可能返回new UserModel();
        //因此还需要做进一步的判断
        if (um != null) {
            //如果用户存在,检查用户编号和密码是否匹配
            if (um.getUserId().equals(lm.getUserId())
                    && um.getPwd().equals(lm.getPwd())) {
                return true;
            }
        }
        return false;
    }
    /**
     * 根据用户编号获取用户的详细信息
     * @param userId 用户编号
     * @return 对应的用户的详细信息
     */
    private UserModel findUserByUserId(String userId) {
        // 这里省略具体的处理,仅做示意,返回一个有默认数据的对象
        UserModel um = new UserModel();
        um.setUserId(userId);
        um.setName("test");
        um.setPwd("test");
        um.setUuid("User0001");
        return um;
    }
}

使用设计模式的做法

关于模板的相关设置

/**
 * 封装进行登录控制所需要的数据
 */
public class LoginModel {
    /**
     * 登录人员的编号,通用的,可能是用户编号,也可能是工作人员编号
     */
    private String loginId;
    /**
     * 登录的密码
     */
    private String pwd;
    public String getLoginId() {
        return loginId;
    }
    public void setLoginId(String loginId) {
        this.loginId = loginId;
    }
    public String getPwd() {
        return pwd;
    }
    public void setPwd(String pwd) {
        this.pwd = pwd;
    }   
}
/**
 * 封装进行登录控制所需要的数据,在公共数据的基础上,
 * 添加具体模块需要的数据
 */
public class NormalLoginModel extends LoginModel{
    /**
     * 密码验证问题
     */
    private String question;
    /**
     * 密码验证答案
     */
    private String answer;
    public String getQuestion() {
        return question;
    }
    public void setQuestion(String question) {
        this.question = question;
    }
    public String getAnswer() {
        return answer;
    }
    public void setAnswer(String answer) {
        this.answer = answer;
    }

}

使用到模板方法设计模式的类

抽象类

/**
 *  登录控制的模板
 */
public abstract class LoginTemplate {
    /**
     * 判断登录数据是否正确,也就是是否能登录成功
     * @param lm 封装登录数据的Model
     * @return true表示登录成功,false表示登录失败
     */
    public final boolean login(LoginModel lm){
        //1:根据登录人员的编号去获取相应的数据
        LoginModel dbLm = this.findLoginUser(lm.getLoginId());
        if(dbLm!=null){
            //2:对密码进行加密
            String encryptPwd = this.encryptPwd(lm.getPwd());
            //把加密后的密码设置回到登录数据模型里面
            lm.setPwd(encryptPwd);
            //3:判断是否匹配
            return this.match(lm, dbLm);
        }
        return false;
    }
    /**
     * 根据登录编号来查找和获取存储中相应的数据
     * @param loginId 登录编号
     * @return 登录编号在存储中相对应的数据
     */
    public abstract LoginModel findLoginUser(String loginId);
    /**
     * 对密码数据进行加密
     * @param pwd 密码数据
     * @return 加密后的密码数据
     */
    public String encryptPwd(String pwd){
        return pwd;
    }
    /**
     * 判断用户填写的登录数据和存储中对应的数据是否匹配得上
     * @param lm 用户填写的登录数据
     * @param dbLm 在存储中对应的数据
     * @return true表示匹配成功,false表示匹配失败
     */
    public boolean match(LoginModel lm,LoginModel dbLm){
        if(lm.getLoginId().equals(dbLm.getLoginId()) 
                && lm.getPwd().equals(dbLm.getPwd())){
            return true;
        }
        return false;
    }
}

实体类

/**
 * 普通用户登录控制的逻辑处理
 */
public class NormalLogin extends LoginTemplate{
    public LoginModel findLoginUser(String loginId) {
        // 这里省略具体的处理,仅做示意,返回一个有默认数据的对象
        LoginModel lm = new LoginModel();
        lm.setLoginId(loginId);
        lm.setPwd("testpwd");
        return lm;
    }
}
/**
 * 工作人员登录控制的逻辑处理
 */
public class WorkerLogin extends LoginTemplate{

    public LoginModel findLoginUser(String loginId) {
        // 这里省略具体的处理,仅做示意,返回一个有默认数据的对象
        LoginModel lm = new LoginModel();
        lm.setLoginId(loginId);
        lm.setPwd("workerpwd");
        return lm;
    }
    public String encryptPwd(String pwd){
        //覆盖父类的方法,提供真正的加密实现
        //这里对密码进行加密,比如使用:MD5、3DES等等,省略了
        System.out.println("使用MD5进行密码加密");
        return pwd;
    }
}
/**
 * 普通用户登录控制加强版的逻辑处理
 */
public class NormalLogin2 extends LoginTemplate{
    public LoginModel findLoginUser(String loginId) {
        // 这里省略具体的处理,仅做示意,返回一个有默认数据的对象
        //注意一点:这里使用的是自己需要的数据模型了
        NormalLoginModel nlm = new NormalLoginModel();
        nlm.setLoginId(loginId);
        nlm.setPwd("testpwd");
        nlm.setQuestion("testQuestion");
        nlm.setAnswer("testAnswer");

        return nlm;
    }
    public boolean match(LoginModel lm,LoginModel dbLm){
        //这个方法需要覆盖,因为现在进行登录控制的时候,
        //需要检测4个值是否正确,而不仅仅是缺省的2个

        //先调用父类实现好的,检测编号和密码是否正确
        boolean f1 = super.match(lm, dbLm);
        if(f1){
            //如果编号和密码正确,继续检查问题和答案是否正确

            //先把数据转换成自己需要的数据
            NormalLoginModel nlm = (NormalLoginModel)lm;
            NormalLoginModel dbNlm = (NormalLoginModel)dbLm;
            //检查问题和答案是否正确
            if(dbNlm.getQuestion().equals(nlm.getQuestion())
                    && dbNlm.getAnswer().equals(nlm.getAnswer())){
                return true;
            }
        }
        return false;
    }

}

测试类

public class Client {
    public static void main(String[] args) {
        //准备登录人的信息
        LoginModel lm = new LoginModel();
        lm.setLoginId("admin");
        lm.setPwd("workerpwd");
        //准备用来进行判断的对象
        LoginTemplate lt = new WorkerLogin();
        LoginTemplate lt2 = new NormalLogin();
        //进行登录测试
        boolean flag = lt.login(lm);
        System.out.println("可以登录工作平台="+flag);

        boolean flag2 = lt2.login(lm);
        System.out.println("可以进行普通人员登录="+flag2);

        //准备登录人的信息
        NormalLoginModel nlm = new NormalLoginModel();
        nlm.setLoginId("testUser");
        nlm.setPwd("testpwd");
        nlm.setQuestion("testQuestion");
        nlm.setAnswer("testAnswer");
        //准备用来进行判断的对象
        LoginTemplate lt3 = new NormalLogin2();
        //进行登录测试
        boolean flag3 = lt3.login(nlm);
        System.out.println("可以进行普通人员加强版登录="+flag3);
    }
}

关于模板方法设计模式的完整定义实例


一般的模板设计模式有以下这些操作类型
1.模板方法:定义算法骨架,是不可更改不能继承实现的最终方法
2.具体的操作:是模板方法中的某个步骤的方法,其算法一般是固定的,不怎么变化的。可以根据访问限制符,分为公共功能,不允许子类访问,只允许子类访问三种
3.具体的抽象类操作:是在模板中实现某些公共功能,可以提供给子类使用,但是一般不作为模板方法的一个步骤
4.原语操作:模板定义的抽象操作,子类去实现具体算法。一般由模板方法调用的操作
5.钩子操作:在模板中有默认的具体算法实现。但是子类可以通过覆写,扩展该方法。
6.工厂方法:在模板方法中,如果需要某个具体事例。通过抽象工厂方法模式获取,其具体代码实现延伸到子类去获取。

/**
 * 一个较为完整的模版定义示例
 */
public abstract class AbstractTemplate {
    /**
     * 模板方法,定义算法骨架
     */
    public final void templateMethod(){
        //第一步
        this.operation1();
        //第二步       
        this.operation2();
        //第三步
        this.doPrimitiveOperation1();
        //第四步
        this.doPrimitiveOperation2();
        //第五步
        this.hookOperation1();
    }
    /**
     * 具体操作1,算法中的步骤,固定实现,而且子类不需要访问
     */
    private void operation1(){
        //在这里具体的实现
    }
    /**
     * 具体操作2,算法中的步骤,固定实现,子类可能需要访问,
     * 当然也可以定义成public的,不可以被覆盖,因此是final的
     */
    protected final void operation2(){
        //在这里具体的实现
    }
    /**
     * 具体的AbstractClass操作,子类的公共功能,
     * 但通常不是具体的算法步骤
     */
    protected void commonOperation(){
        //在这里具体的实现
    }
    /**
     * 原语操作1,算法中的必要步骤,父类无法确定如何真正实现,需要子类来实现
     */
    protected abstract void doPrimitiveOperation1();
    /**
     * 原语操作2,算法中的必要步骤,父类无法确定如何真正实现,需要子类来实现
     */
    protected abstract void doPrimitiveOperation2();
    /**
     * 钩子操作,算法中的步骤,不一定需要,提供缺省实现
     * 由子类选择并具体实现
     */
    protected void hookOperation1(){
        //在这里提供缺省的实现
    }
    /**
     * 工厂方法,创建某个对象,这里用Object代替了,在算法实现中可能需要
     * @return 创建的某个算法实现需要的对象
     */
    protected abstract Object createOneObject();
}

典型应用


java.util中的Collections类,实现了对列表排序的功能,它有一个静态的sort方法,接受一个列表和一个Comparator接口的实例。这就是一个典型的模板算法模式,也有人称是策略模式.

关于模板方法,策略模式,状态模式,三者思想及其相近,还需好好琢磨

猜你喜欢

转载自blog.csdn.net/qq_32020035/article/details/80992905