1.模板方法模式(Template method pattern)
模板方法模式通常又叫模板模式,是指定义一个算法的骨架,并允许之类为其中的一个或者多个步骤提供实现。模板方法模式使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤
模板方法(Template Method)模式包含以下主要角色:
- 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
- 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
- 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:
- 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。
- 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
- 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。
- 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。
1.1 代码实现
下面以一个简单的请假流程来通过代码来实现:
public abstract class DayOffProcess {
// 请假模板
public final void dayOffProcess() {
// 领取申请表
this.pickUpForm();
// 填写申请信息
this.writeInfo();
// 签名
this.signUp();
// 提交到不同部门审批
this.summit();
// 行政部备案
this.filing();
}
private void filing() {
System.out.println("行政部备案");
}
protected abstract void summit();
protected abstract void signUp();
private void writeInfo() {
System.out.println("填写申请信息");
}
private void pickUpForm() {
System.out.println("领取申请表");
}
}
public class ZhangSan extends DayOffProcess {
@Override
protected void summit() {
System.out.println("张三签名");
}
@Override
protected void signUp() {
System.out.println("提交到技术部审批");
}
}
public class Lisi extends DayOffProcess {
@Override
protected void summit() {
System.out.println("李四签名");
}
@Override
protected void signUp() {
System.out.println("提交到市场部审批");
}
}
// 测试方法
public static void main(String[] args) {
DayOffProcess zhangsan = new ZhangSan();
//领取申请表
//填写申请信息
//提交到技术部审批
//张三签名
//行政部备案
zhangsan.dayOffProcess();
DayOffProcess lisi = new Lisi();
//领取申请表
//填写申请信息
//提交到市场部审批
//李四签名
//行政部备案
lisi.dayOffProcess();
}
1.2 总结
适用场景:
- 一次性实现一个算法不变的部分,并将可变的行为留给子类来实现。
- 各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。
优点:
- 利用模板方法将相同处理逻辑的代码放到抽象父类中,可以提高代码的复用性。
- 将不同的代码不同的子类中,通过对子类的扩展增加新的行为,提高代码的扩展性。
- 把不变的行为写在父类上,去除子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则。
缺点:
- 类数目的增加,每一个抽象类都需要一个子类来实现,这样导致类的个数增加。
- 类数量的增加,间接地增加了系统实现的复杂度。
- 继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍。
2.策略模式(Strategy Pattern)
策略模式又叫政策模式(Policy Pattern),它是将定义的算法家族分别封装起来,让它们之间可以互相替换,从而让算法的变化不会影响到使用算法的用户。可以避免多重分支的if…else和switch语句。
策略模式的主要角色如下:
抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
环境(Context)类:持有一个策略类的引用,最终给客户端调用。
2.1 普通案例(会员卡打折)
// 会员卡接口
public interface VipCard {
public void discount();
}
public class GoldCard implements VipCard {
@Override
public void discount() {
System.out.println("金卡打7折");
}
}
public class SilverCard implements VipCard {
@Override
public void discount() {
System.out.println("银卡打8折");
}
}
public class CopperCard implements VipCard {
@Override
public void discount() {
System.out.println("铜卡打9折");
}
}
public class Normal implements VipCard {
@Override
public void discount() {
System.out.println("普通会员没有折扣");
}
}
// 会员卡容器类
public class VipCardFactory {
private static Map<String, VipCard> map = new ConcurrentHashMap<>();
static {
map.put("gold", new GoldCard());
map.put("silver", new SilverCard());
map.put("copper", new CopperCard());
}
public static VipCard getVIPCard(String level) {
return map.get(level) != null ? map.get(level) : new Normal();
}
}
// 测试方法
public static void main(String[] args) {
//金卡打7折
VipCardFactory.getVIPCard("gold").discount();
//银卡打8折
VipCardFactory.getVIPCard("silver").discount();
//普通会员没有折扣
VipCardFactory.getVIPCard("other").discount();
}
用一个容器(Map)装起来,可以通过传进来的参数直接获取对应的策略,避免了if…else。
2.2 支付方式案例
// 支付方式抽象类
public abstract class Payment {
public String pay(String uid, double money) {
double balance = queryBalance(uid);
if (balance < money) {
return "支付失败!余额不足!欠" + (money - balance) + "元!";
}
return "支付成功!支付金额:" + money + "余额剩余:" + (balance - money);
}
protected abstract String getPaymentName();
protected abstract double queryBalance(String uid);
}
// 现金支付 默认方式
public class Cash extends Payment{
@Override
protected String getPaymentName() {
return "现金支付";
}
@Override
protected double queryBalance(String uid) {
return 1000;
}
}
// 支付宝类
public class AliPay extends Payment {
@Override
protected String getPaymentName() {
return "支付宝";
}
@Override
protected double queryBalance(String uid) {
return 500;
}
}
// 微信支付类
public class WeChatPay extends Payment {
@Override
protected String getPaymentName() {
return "微信支付";
}
@Override
protected double queryBalance(String uid) {
return 300;
}
}
// 支付方式容器策略类
public class PaymentStrategy {
private static Map<String, Payment> map = new ConcurrentHashMap<>();
static {
map.put("WeChat", new WeChatPay());
map.put("Ali", new AliPay());
}
public static Payment getPayment(String payment) {
return map.get(payment) == null ? new Cash() : map.get(payment);
}
}
// 订单交易类
@AllArgsConstructor
public class Order {
private String uid;
private double amount;
public String pay() {
return pay("cash");
}
public String pay(String key) {
Payment payment = PaymentStrategy.getPayment(key);
System.out.println("欢迎使用" + payment.getPaymentName());
System.out.println("本次交易金额:" + this.amount + ",开始扣款...");
return payment.pay(this.uid, this.amount);
}
}
// 测试方法
public static void main(String[] args) {
Order order = new Order("20221014001", 500);
//欢迎使用微信支付
//本次交易金额:500.0,开始扣款...
//支付失败!余额不足!欠200.0元!
System.out.println(order.pay("WeChat"));
//欢迎使用支付宝
//本次交易金额:500.0,开始扣款...
//支付成功!支付金额:500.0余额剩余:0.0
System.out.println(order.pay("Ali"));
//欢迎使用现金支付
//本次交易金额:500.0,开始扣款...
//支付成功!支付金额:500.0余额剩余:500.0
System.out.println(order.pay());
}
2.3 总结
适用场景:
- 系统中有很多类,而它们的区别仅仅在于它们的行为不同。
- 系统需要动态地在几种算法中选择一种。
- 需要屏蔽算法规则。
优点:
- 符合开闭原则。
- 避免使用多重条件语句。
- 可以提高算法的保密性和安全性。
- 易于扩展。
缺点:
- 客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
- 代码中会产生非常多的策略类,增加维护难度。