公告
如果您是第一次阅读我的设计模式系列文章,建议先阅读设计模式开篇,希望能得到您宝贵的建议。
前言
Alice
这个购买机器人女友的事情闹的沸沸扬扬,Alice
的爸爸实在不能同意Alice
这样胡闹下去,于是Alice
的爸爸就去了Alice
家(他们平时不住在一起)。
无巧不巧的是那天正好Alice
不在家,只有女友Samu
在家。于是Alice的爸爸
叫Samu
开门表明身份是Alice的爸爸
,可是Samu
最终仍是拒绝了开门……最后Alice的爸爸
在寒冷的北风中站了8个小时后,Alice
回来后 Samu
才开的门。
正文
Alice的爸爸
:Samu
开开门,我是Alice的爸爸
。
Samu
:无法鉴定身份,抱歉无法为你开门。
Alice的爸爸
:我出示我的身份证给你看,Samu
。(出示了身份证)
Samu
:无法鉴定身份,抱歉无法为你开门。
Alice的爸爸
:……(吐血中)
……(Alice
回来了)
Alice
:Samu
开开门。
Samu
:欢迎回家,Alice
!
Alice的爸爸
:……(吐血中)
程序员视角
现在要实现身份识别验证后开门的功能,起初Alice
并没有把他爸爸的信息告知Samu
,所以Samu
无法为他开门。后面Alice
允许Samu
授予他爸爸开门的权限。
在本章中不将重心放在权限层级(如果要考虑权限相对复杂,可考虑享元模式),仅考虑如何识别身份后的分支操作 — — 开门或不开门
。
当然如果只是IF-ELSE
自然是TOO YOUNG TOO SIMPLE
。
如何实现
策略模式本质:分离算法,选择实现。
参考状态模式 命令模式中的经验,单个命令或状态只处理其自身的逻辑。— — 职责单一原则
。
为了保证23个GOF都使用同一个GITHUB项目,所以计划23个GOF模式都会使用同一个场景“机器人”作为基础,所以故事之间会有一些关联。
延续前一篇文章“命令行模式”,本次要求能够接收开门命令,所以需要实现接口开门命令。
定义开门命令
public class OpenDoorCommandImpl implements ICommand {
private User user;
private Machine machine;
public OpenDoorCommandImpl(User user, Machine machine) {
this.user = user;
this.machine = machine;
}
@Override
public void excute() {
machine.getStrategy(user).operation();
}
}
同时为了模拟机器人管理开门策略,所以在Machine
中作了些许变更。
public class Machine {
private String name;
public Machine(String name) {
this.name = name;
System.out.println("创建了机器人 " + name);
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
// 管理了所有的策略(这里是策略接口)
private HashMap map = new HashMap<>();
public void configure(User user, IStrategy strategy) {
map.put(user, strategy);
}
public IStrategy getStrategy(User user) {
return map.get(user);
}
@Override
public String toString() {
return name;
}
}
定义命令接收器
public class OpenDoorCommandReceiver {
private CommandManager invoke = new CommandManager();
private Machine machine;
public OpenDoorCommandReceiver(Machine machine) {
this.machine = machine;
System.out.printf("机器人%s的接收功能正常开启%n", machine);
}
public void onReceive(String command, User user) {
System.out.printf("机器人%s接收到指令:%s,%s%n", machine, command, user);
invoke.invoke(new OpenDoorCommandImpl(user, machine));
}
}
原先在调用播放歌曲命令时,使用适配器适配了String类型作为命令参数。在本例时,暂时不做适配器适配开门命令接口,下一篇文章会仔细描述适配器模式。
定义策略的接口:
public interface IStrategy {
void operation();
}
实现成功开门策略:
public class VerifySuccessStrategy implements IStrategy {
@Override
public void operation() {
System.out.println("验证通过,已将门打开");
}
}
实现失败开门策略:
public class VerifyFailStrategy implements IStrategy {
@Override
public void operation() {
System.out.println("验证失败,无法为您开门");
}
}
客户端调用测试
public class Client {
public static void main(String[] args) {
User alice = new User("Alice");
User aliceParent = new User("Alice's Parent");
Machine machine = new Machine("Samu");
machine.configure(alice, new VerifySuccessStrategy());
machine.configure(aliceParent, new VerifyFailStrategy());
OpenDoorCommandReceiver receiver = new OpenDoorCommandReceiver(machine);
System.out.println("++aliceParent++");
receiver.onReceive("开门", aliceParent);
System.out.println("--aliceParent--");
System.out.println("++alice++");
receiver.onReceive("开门", alice);
System.out.println("--alice--");
}
}
测试结果
创建了机器人 Samu
机器人Samu的接收功能正常开启
++aliceParent++
机器人Samu接收到指令:开门,com.bookbuf.gof23.User@1b6d3586
验证失败,无法为您开门
--aliceParent--
++alice++
机器人Samu接收到指令:开门,com.bookbuf.gof23.User@28d93b30
验证通过,已将门打开
--alice--
总结
策略模式的本质:分离算法,选择实现
- 策略模式是一个比较容易理解和使用的设计模式,策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
策略模式的优点
- 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上* 选择算法或行为,也可以灵活地增加新的算法或行为。
- 策略模式提供了管理相关的算法族的办法。
- 策略模式提供了可以替换继承关系的办法。
- 使用策略模式可以避免使用多重条件转移语句。
策略模式的缺点
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
在以下情况下可以使用策略模式:
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
一个系统需要动态地在几种算法中选择一种。 - 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
- 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。