应对软件需求变化-模板方法模式的应用

Posted windpoplar

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了应对软件需求变化-模板方法模式的应用相关的知识,希望对你有一定的参考价值。

一、缘起

变化--是软件设计的永恒主题,如何管理变化带来的复杂性?

设计模式的艺术性和复杂度就在于如何分析,并发现系统中的变化点和稳定点,并使用特定的设计方法来应对这种变化。

在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。

如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?我们下面看下模板方法模式是怎样应对这样的需求变化的。

二、定义

GOF给出的模板方法模式的定义:

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

三、应用举例

1、需求

应用系统一般都需要系统登录控制的功能,有些系统有多个登录控制的功能,比如,普通用户可以登录前台,进行相应的业务操作,工作人员可以登录后台,进行相应的系统管理或业务处理。

2、不用设计模式的解决方案

普通用户登录和工作人员登录是不同的模块,有不同的页面、不同的逻辑处理和不同的数据存储,因此,在实现上完全作为两个独立的小模块来完成。

先看看普通用户登录的逻辑处理:

/**
 * 普通用户登录控制的逻辑处理
 */
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;
    }
}

对应的LoginModel:

/**
 * 描述登录人员登录时填写的信息的数据模型
 */
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;
    }

}

对应的UserModel:

/**
 * 描述用户信息的数据模型
 */
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 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;
    }
}

对应的LoginModel:

/**
 * 描述登录人员登录时填写的信息的数据模型
 */
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;
    }
}

对应的WorkerModel:

/**
 * 描述工作人员信息的数据模型
 */
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;
    }
}

3、有何问题

两种登录的实现很相似,现在是两个独立模块来实现。如果今后有新的需求要扩展功能,比如要添加“控制同一个编号同时只能登录一次”的功能,那么这两个模块都需要修改。

总结起来,主要有两个明显的问题:一是重复或相似代码太多,没有做到复用;而是扩展起来不方便,每次需要修改原有代码,违反开闭原则。

4、使用模板方法模式来解决问题

使用模板方法模式来解决刚才的问题。定义出一个抽象的父类,在这个父类中定义模板方法,这个模板方法应该实现进行登录控制的整体的算法步骤。对于公共的功能,就放到这个父类中实现,而这个父类无法决定的功能,就延迟到子类去实现。

首先数据模型可以统一起来。当然,如果各个子类实现需要其他的数据,还可以自行扩展。统一的LoginModel:

/**
 * 封装进行登录控制所需要的数据
 */
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;
    }    
}

 

以上是关于应对软件需求变化-模板方法模式的应用的主要内容,如果未能解决你的问题,请参考以下文章

GOF之模板模式

敏捷开发模式

敏捷开发:采取灵活机动的战术来应对需求的不断变化

模板模式

设计模式之面向接口编程

敏捷开发综述