模板方法模式(行为型模式)
模式概述
模板方法模式是一种行为设计模式,它在父类中定义了一个操作的流程,允许子类在不修改流程结构的情况下重写特定的操作步骤。
问题
假如公司正在开发一款社交聚合应用,它可以通过单一用户账号,登录所有社交应用。刚开始只支持微博,在后续的不断升级中,逐渐可以支持越来越多的应用,例如QQ和微信。
但是随着越来越多的社交应用加入进来,工程师们发现这些聚合功能完全相同,除了登录的平台账户不同,诸如登录、发消息、修改个人信息和注销等功能几乎没有什么差别,代码也几乎完全一样,该怎么去掉这些重复代码呢?
方案
模板方法模式将抽象的实现过程分解为一系列操作,并将这些操作转化为一个个方法,最后在模板
中依次调用这些方法。达到重组流程的目的。
操作可以是抽象
的,也可以有一些默认的实现。客户端需要自己实现子类并实现所有的抽象步骤,如有必要还需重写一些步骤 ,但不包括模板自身。

结构

实现
Java
/**
* 社交网络父类
*
*/
public abstract class Network {
String username;
String password;
public Network() {}
// 抽象的模板方法
public boolean post(String message) {
// 登录方法
if (logIn(this.username, this.password)) {
// Send the post data.
boolean result = sendMessage(message.getBytes());
return result;
}
return false;
}
abstract boolean login(String username, String password);
abstract boolean sendMessage(byte[] data);
}
/**
* 微博
*
*/
public class Weibo extends Network {
public Weibo(String useruame, String password) {
this.useruame = useruame;
this.password = password;
}
public boolean login(String useruame, String password) {
System.out.println("\nChecking user's parameters");
System.out.println("Name: " + this.useruame);
for (int i = 0; i < this.password.length(); i++) {
System.out.print("*");
}
simulateNetworkLatency();
System.out.println("微博登录成功");
return true;
}
public boolean sendMessage(byte[] data) {
boolean messagePosted = true;
if (messagePosted) {
System.out.println("消息 " + new String(data) + " 已被发送给微博好友");
return true;
} else {
return false;
}
}
private void simulateNetworkLatency() {
try {
int i = 0;
System.out.println();
while (i < 10) {
System.out.print(".");
Thread.sleep(500);
i++;
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
/**
* 微信
*
*/
public class WeChat extends Network {
public WeChat(String useruame, String password) {
this.useruame = useruame;
this.password = password;
}
public boolean login(String useruame, String password) {
System.out.println("\nChecking user's parameters");
System.out.println("Name: " + this.useruame);
for (int i = 0; i < this.password.length(); i++) {
System.out.print("*");
}
simulateNetworkLatency();
System.out.println("微信登录成功");
return true;
}
public boolean sendMessage(byte[] data) {
boolean messagePosted = true;
if (messagePosted) {
System.out.println("消息 " + new String(data) + " 已被发送给微信好友");
return true;
} else {
return false;
}
}
private void simulateNetworkLatency() {
try {
int i = 0;
System.out.println();
while (i < 10) {
System.out.print(".");
Thread.sleep(500);
i++;
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
/**
* 客户端
*
*/
public class Client {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
Network network = null;
System.out.print("请输入用户名:");
String username = reader.readLine();
System.out.print("请输入密码:");
String password = reader.readLine();
System.out.print("请输入消息:");
String message = reader.readLine();
System.out.println("\n选择社交网络:\n" +
"1 - 微博\n" +
"2 - 微信");
int choice = Integer.parseInt(reader.readLine());
if (choice == 1) {
network = new Weibo(username, password);
} else if (choice == 2) {
network = new Wechat(username, password);
}
network.post(message);
}
}
Go
package main
import "fmt"
/**
* 发送一次性密码(OTP)模板方法
*
*/
type IOtp interface {
genRandomOTP(int) string
saveOTPCache(string)
getMessage(string) string
sendNotification(string) error
}
type Otp struct {
iOtp IOtp
}
func (o *Otp) genAndSendOTP(otpLength int) error {
otp := o.iOtp.genRandomOTP(otpLength)
o.iOtp.saveOTPCache(otp)
message := o.iOtp.getMessage(otp)
err := o.iOtp.sendNotification(message)
if err != nil {
return err
}
return nil
}
/**
* 短信发送OTP
*
*/
type Sms struct {
Otp
}
func (s *Sms) genRandomOTP(len int) string {
randomOTP := "1234"
fmt.Printf("SMS: generating random otp %s\n", randomOTP)
return randomOTP
}
func (s *Sms) saveOTPCache(otp string) {
fmt.Printf("SMS: saving otp: %s to cache\n", otp)
}
func (s *Sms) getMessage(otp string) string {
return "SMS OTP for login is " + otp
}
func (s *Sms) sendNotification(message string) error {
fmt.Printf("SMS: sending sms: %s\n", message)
return nil
}
/**
* 邮件发送OTP
*
*/
type Email struct {
Otp
}
func (s *Email) genRandomOTP(len int) string {
randomOTP := "1234"
fmt.Printf("EMAIL: generating random otp %s\n", randomOTP)
return randomOTP
}
func (s *Email) saveOTPCache(otp string) {
fmt.Printf("EMAIL: saving otp: %s to cache\n", otp)
}
func (s *Email) getMessage(otp string) string {
return "EMAIL OTP for login is " + otp
}
func (s *Email) sendNotification(message string) error {
fmt.Printf("EMAIL: sending email: %s\n", message)
return nil
}
/**
* 客户端
*
*/
func main() {
smsOTP := &Sms{}
o := Otp{
iOtp: smsOTP,
}
o.genAndSendOTP(4)
fmt.Println("")
emailOTP := &Email{}
o = Otp{
iOtp: emailOTP,
}
o.genAndSendOTP(4)
}
适用场景
当多个类的方法除一些细微不同,其他几乎完全一样时,可以使用该模式。但后果是只要算法发生变化,就可能需要修改所有的类。
当只希望客户端扩展某个特定操作步骤而不是整个操作流程时,可以使用模板方法模式。
优缺点
模板方法模式的优点。
只用重写某个大型操作流程中的特定部分,使得对其所造成的影响减到减小。
可将重复代码提取到一个类中集中处理。
模板方法模式的缺点。
可能会受到操作框架的限制。
子类重写某些操作步骤可能会违
反里氏替换原则
。模板中的步骤越多,维护工作可能会越难。
相关性
工厂方法模式
是模板方法模式
的一种特殊形式。同时,工厂方法可以作为一个大型模板方法中的一个步骤。模板方法模式
基于继承机制:它通过扩展子类中的部分内容来改变部分算法。而策略模式
基于组合机制:它通过对相应行为提供不同的策略来改变对象的部分行为。模板方法模式
在类层次上运作,它是静态的。而策略模式
在对象层次上运作,它是动态的。
感谢支持
更多内容,请移步《超级个体》。