这一篇主要讲行为型模式的中间四种
行为型模式关注的是各个类之间的相互作用,将职责划分清楚,使得我们的代码更加地清晰。
模版方法模式
模板方法模式常用于含有继承结构的代码中
先定义一个抽象类,apply() 是抽象方法,子类必须实现它。可以随意指定多少抽象方法,抽象类指定n多个方法,至于怎么做都是由子类去实现
public abstract class AbstractTemplate {
// 这就是模板方法
public void templateMethod() {
init();
apply(); // 这个是重点
end(); // 可以作为钩子方法
}
protected void init() {
System.out.println("init 抽象层已经实现,子类也可以选择覆写");
}
// 留给子类实现
protected abstract void apply();
protected void end() {
}
}
实现类
public class ConcreteTemplate extends AbstractTemplate {
public void apply() {
System.out.println("子类实现抽象方法 apply");
}
public void end() {
System.out.println("我们可以把 method3 当做钩子方法来使用,需要的时候覆写就可以了");
}
}
使用
public static void main(String[] args) {
AbstractTemplate t = new ConcreteTemplate();
// 调用模板方法
t.templateMethod();
}
优点:
- 封装不变部分,扩展可变部分。不变部分的方法封装到父类中实现,把可变部分由子类继承实现,便于子类继续扩展。
- 在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,间接地增加了系统实现的复杂度。
- 由于继承关系自身的缺点,如果父类添加新的抽象方法,所有子类都要改一遍。
模板方法在spring中还有其余的相似名字,如:JdbcTemplate
、RedisTemplate
等
设计模式之模板设计模式-以spring的各种template为例
JdbcTemplate
是不同dao框架(mybatis/hibernate)的封装
RestTemplate
是不同http框架(jdk api/httpclient/okhttp)的封装
Slf4j
是对不同日志框架(logback/log4j等)的封装,但它是外观(门面)模式。之前文章介绍过。
- 模板定义父子逻辑,用子类实现抽象类的抽象方法,实现不同的子类
- 门面是给多个对象提供单一创建方式
更通用点的例子,核心点就在templateMethod()
方法,子类实现一系列抽象方法
public class TemplateMethodPattern {
public static void main(String[] args) {
AbstractClass tm = new ConcreteClass();
tm.templateMethod();
}
}
//抽象类
abstract class AbstractClass {
//模板方法
public void templateMethod() {
specificMethod();
abstractMethod1();
abstractMethod2();
}
//具体方法
public void specificMethod() {
System.out.println("抽象类中的具体方法被调用...");
}
//抽象方法1
public abstract void abstractMethod1();
//抽象方法2
public abstract void abstractMethod2();
}
//具体子类
class ConcreteClass extends AbstractClass {
public void abstractMethod1() {
System.out.println("抽象方法1的实现被调用...");
}
public void abstractMethod2() {
System.out.println("抽象方法2的实现被调用...");
}
}
状态模式
举个例子,商品库存中心的减库存和补库存
定义状态接口
public interface State {
public void doAction(Context context);
}
减库存的实现类
public class DeductState implements State {
public void doAction(Context context) {
System.out.println("商品卖出,准备减库存");
context.setState(this);
//... 执行减库存的具体操作
}
public String toString() {
return "Deduct State";
}
}
补库存的实现类
public class RevertState implements State {
public void doAction(Context context) {
System.out.println("给此商品补库存");
context.setState(this);
//... 执行加库存的具体操作
}
public String toString() {
return "Revert State";
}
}
环境类Context,这里是作为商品存在的
public class Context {
private State state;
private String name;
public Context(String name) {
this.name = name;
}
public void setState(State state) {
this.state = state;
}
public void getState() {
return this.state;
}
}
使用
public static void main(String[] args) {
// 我们需要操作的是 iPhone X
Context context = new Context("iPhone X");
// 补库存
State revertState = new RevertState();
revertState.doAction(context);
// 减库存
State deductState = new DeductState();
deductState.doAction(context);
// 如果需要我们可以获取当前的状态
// context.getState().toString();
}
核心就在State类中的doAction方法中的context.setState(this),把当前State类存到商品中
应用场景:
- 上面的商品例子,对象需要有明确的状态时,可以考虑使用
- 对象的行为取决于它的状态,并且必须在运行时根据状态改变它的行为时,可以考虑使用。(如启用/禁用的切换)
再举一个例子,状态模式一共就只有两个角色:环境类Context和状态类State。核心在环境类的Handle()方法中调用状态类的Handle()方法,做context.setState()存放不同的State实现类
public class StatePatternClient {
public static void main(String[] args) {
Context context = new Context(); //创建环境
context.Handle(); //处理请求
context.Handle();
context.Handle();
context.Handle();
}
}
//环境类
class Context {
private State state;
//定义环境类的初始状态
public Context() {
this.state = new ConcreteStateA();
}
//设置新状态
public void setState(State state) {
this.state = state;
}
//读取状态
public State getState() {
return (state);
}
//对请求做处理
public void Handle() {
state.Handle(this);
}
}
//抽象状态类
abstract class State {
public abstract void Handle(Context context);
}
//具体状态A类
class ConcreteStateA extends State {
public void Handle(Context context) {
System.out.println("当前状态是 A.");
context.setState(new ConcreteStateB());
}
}
//具体状态B类
class ConcreteStateB extends State {
public void Handle(Context context) {
System.out.println("当前状态是 B.");
context.setState(new ConcreteStateA());
}
}
运行结果如下:
当前状态是 A.
当前状态是 B.
当前状态是 A.
当前状态是 B.
命令模式
一般代码中方法的请求者和实现者之间经常存在紧密的耦合关系,这不利于扩展和维护,为了使其解耦,可以使用命令模式
该模式有三个角色,调用者,命令类和接收者。
public class CommandPattern {
public static void main(String[] args) {
Command cmd = new ConcreteCommand();
Invoker ir = new Invoker(cmd);
System.out.println("客户访问调用者的call()方法...");
ir.call();
}
}
//调用者
class Invoker {
private Command command;
public Invoker(Command command) {
this.command = command;
}
public void setCommand(Command command) {
this.command = command;
}
public void call() {
System.out.println("调用者执行命令command...");
command.execute();
}
}
//抽象命令
interface Command {
public abstract void execute();
}
//具体命令
class ConcreteCommand implements Command {
private Receiver receiver;
ConcreteCommand() {
receiver = new Receiver();
}
public void execute() {
receiver.action();
}
}
//接收者
class Receiver {
public void action() {
System.out.println("接收者的action()方法被调用...");
}
}
调用者中传入命令类,命令类跑接收者方法。命令类作为中间商,负责解耦
优点:
- 通过引入中间件(抽象接口)降低系统耦合度。
- 扩展性良好,增删非常方便。采用命令模式增删不会影响其他类,且满足“开闭原则”。
- 可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现 Undo 和 Redo 操作。与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
- 可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。
缺点:
- 可能产生大量命令类。因为每一个具体操作都需要设计具体命令类,这会增加系统的复杂性。
中介者模式
前面说的命令模式是降低调用者和接收者之间的耦合,而中介者是降低对象和对象之间的耦合。
该模式有两个角色,中介者和对象类(该例子里是同事类)和前面命令模式一样,为了降低耦合就需要中间对象,中介者就是为了降低同事类之间的耦合而存在的。
import java.util.*;
public class MediatorPattern {
public static void main(String[] args) {
Mediator md = new ConcreteMediator();
Colleague c1, c2;
c1 = new ConcreteColleague1();
c2 = new ConcreteColleague2();
md.register(c1);
md.register(c2);
c1.send();
System.out.println("-------------");
c2.send();
}
}
//抽象中介者
abstract class Mediator {
public abstract void register(Colleague colleague);
public abstract void relay(Colleague cl); //转发
}
//具体中介者
class ConcreteMediator extends Mediator {
private List<Colleague> colleagues = new ArrayList<Colleague>();
public void register(Colleague colleague) {
if (!colleagues.contains(colleague)) {
colleagues.add(colleague);
colleague.setMedium(this);
}
}
public void relay(Colleague cl) {
for (Colleague ob : colleagues) {
if (!ob.equals(cl)) {
((Colleague) ob).receive();
}
}
}
}
//抽象同事类
abstract class Colleague {
protected Mediator mediator;
public void setMedium(Mediator mediator) {
this.mediator = mediator;
}
public abstract void receive();
public abstract void send();
}
//具体同事类
class ConcreteColleague1 extends Colleague {
public void receive() {
System.out.println("具体同事类1收到请求。");
}
public void send() {
System.out.println("具体同事类1发出请求。");
mediator.relay(this); //请中介者转发
}
}
//具体同事类
class ConcreteColleague2 extends Colleague {
public void receive() {
System.out.println("具体同事类2收到请求。");
}
public void send() {
System.out.println("具体同事类2发出请求。");
mediator.relay(this); //请中介者转发
}
}
本例子模拟了同事之间的发送和接受消息,先注入中介者,可以发现中介者的代码里用了list.add()去注册,并且将中介者set到同事类中,然后send的时候做中介者的转发,调用所有非当前同事的receice方法
运行结果如下:
具体同事类1发出请求。
具体同事类2收到请求。
-------------
具体同事类2发出请求。
具体同事类1收到请求。
结构如图
优点:
- 中介者模式会将对象之间的复杂关系由“网状结构”改为“星形结构”,将大大降低它们之间的“耦合性”,“中介者”就是作为星形中间那个点
使用场景:
- 在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者