设计模式17---设计模式之模板方法模式(Template Method)(行为型)

1.场景模拟

使用软件模拟登录控制,普通用户和工作人员用户,工作人员的密码在数据库中是加密的。
步骤大致如下:
前台提交,后台获取登录信息,同数据库中的登陆信息进行比较,只不过工作人员是加密的,普通用户是不加密的。
如果匹配,跳到下一界面,如果不匹配,那么返回登录界面,并且显示错误信息。
代码如下

普通用户:

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


}
*********************************************************************
package demo15.templatemethod.example1;


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

工作人员

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


/**
 * 工作人员登录控制的逻辑处理
 */
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;
	}
}
**********************************************************************
package demo15.templatemethod.example2;
/**
 * 描述工作人员信息的数据模型
 */
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;
	}
}

2.有何问题

1.重复或者相似代码太多了
2.扩展起来很不方便,如果改动一个功能的话,那么两种用户可能都要更改。

3.使用模板方法模式来解决问题

3.1定义

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

3.2模板方法的结构和说明

模板方法就是要抽象出相同或者类似的,找到变与不变的地方,那么就可以使用模板方法模式了。
结构图如下
 

3.3模板方法模式示例代码

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


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

4.使用模板方法重写示例

4.1LoginModel不变,下面是定义公共的登录控制算法

package demo15.templatemethod.example4;
/**
 *	登录控制的模板
 */
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;
	}
}

4.2普通用户

package demo15.templatemethod.example4;


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

4.3工作人员登陆

package demo15.templatemethod.example4;
/**
 * 工作人员登录控制的逻辑处理
 */
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;
	}
}

4.4客户端使用

package demo15.templatemethod.example4;


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);
	}
}

5.模式讲解

5.1要点

功能:在于固定算法骨架,而让具体算法实现可扩展。
为啥不是接口:接口是一种特殊的抽象类,抽象类不一定包含抽象方法,有抽象方法的一定是抽象类。
什么时候使用抽象类:纪要约束子类的行为,又要为子类提供公共功能
好莱坞法则:不要找我们,我们会联系你。作为父类的模板会在需要的时候调用子类相应的方法,也就是父类来找子类,而不是子类找父类。
模板方法很好的体现了开闭原则和里氏替换原则
java回调技术,就是一种特殊的模板方法模式

5.2本质

固定算法骨架

5.3优缺点

优点:实现代码的复用
缺点:算法骨架不容易升级

posted @ 2013-07-29 19:21  jlins  阅读(181)  评论(0编辑  收藏  举报