行为型模式主要处理类或对象如何交互及如何分配职责。它共有 11 种模式,策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式和解释器模式。
策略模式
策略模式能够解决当写代码时总会遇到的很多的 if…else 或者 case。过多的条件语句或者多重语句嵌套会使代码变得臃肿,维护的成本也会加大,这显然违背了开放封闭原则。
定义:定义一系列的算法,把每一个算法封装起来,并且使它们可相互替换。策略模式使得算法可独立于使用它的客户而独立变化。
在策略模式中有如下角色:
- Context:上下文角色,用来操作策略的上下文环境,起到承上启下的作用,屏蔽高层模块对策略、算法的直接访问。
- Stragety:抽象策略角色,策略、算法的抽象,通常为接口。
- ConcreteStragety:具体的策略实现。
策略模式的简单实现
假设张无忌制定了 3 种应战的策略,分别对付 3 个实力层次的对手。
定义策略接口:
1 2 3 4 5 6
|
public interface FightingStrategy{ void fighting(); }
|
具体策略实现:
分别定义了 3 个策略来实现策略接口,用来对付 3 个实力层次的对手。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class WeakRivalStrategy implements FightingStrategy{
@Override public void fighting() { System.out.println("较弱对手"); } } public class CommonRivalStrategy implements FightingStrategy{
@Override public void fighting() { System.out.println("普通对手"); } } public class StrongRivalStrategy implements FightingStrategy{
@Override public void fighting() { System.out.println("强大对手"); } }
|
上下文角色
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
public class Context{ private FightingStrategy fightingStrategy;
public Context(FightingStrategy fightingStrategy) { this.fightingStrategy = fightingStrategy; }
public void fighting(){ fightingStrategy.fighting(); } }
|
客户端调用:
1 2 3 4 5 6 7 8 9 10 11 12
| public class ZhangWuJi{ public static void main(String[] args) { Context context; context = new Context(new WeakRivalStrategy()); context.fighting(); context = new Context(new CommonRivalStrategy()); context.fighting(); context = new Context(new StrongRivalStrategy()); context.fighting(); } }
|
使用场景和优缺点
- 使用场景
- 对客户隐藏具体策略(算法)的实现细节,彼此完全独立。
- 针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。
- 在一个类中定义了很多行为,而且这些行为在这个类里的操作以多个条件语句的形式出现。策略模式将相关的条件分支移入它们各自的 Strategy 类中,以代替这些条件语句。
- 优点
- 使用策略模式可以避免使用多重条件语句。多重条件语句不易维护,而且易出错。
- 易于拓展。当需要添加一个策略时,只需要实现接口就可以了。
- 缺点
- 每一个策略都是一个类,复用性小。如果策略过多,类的数量会增多。
- 上层模块必须知道有哪些策略,才能够使用这些策略,这与迪米特原则相违背。
模板方法模式
在软件开发中,有时会遇到类似的情况;某个方法的实现需要多个步骤,其中有些步骤是固定的,而有些步骤并不固定,存在可变性。为了提高代码的复用性和系统的灵活性,可以使用模板方法模式来应对这类情况。
定义:定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类不改变一个算法的结构即可重定义算法的某些特定步骤。
在模板方法中有如下角色:
- AbstractClass:抽象类,定义了一套算法框架。
- ConcreteClass:具体实现类。
模板方法的简单实现:
模板方法实际就是封装固定的流程,像模板一样,第一步做什么,第二步又做什么,都在抽象类中定义好。子类可以有不同的算法实现,在算法框架不被修改的前提下实现某些步骤的算法替换。
创建抽象类,定义算法框架:
一个武侠要战斗时,也有一套固定的通用模式,那就是运行内功、开启经脉、准备武器和使用招式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
public abstract class AbstractSwordsman{ public final void fighting(){ neigong(); meridian(); if (hasWeapons()){ weapons(); } moves(); hook(); } protected void hook() {}
protected abstract void moves(); protected abstract void weapons(); protected abstract void neigong(); protected void meridian(){ System.out.println("开启经脉"); }
protected boolean hasWeapons() { return true; } }
|
具体实现类:
以武侠张无忌和张三丰为例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public class ZhangWuJi extends AbstractSwordsman{
@Override protected void moves() { System.out.println("使用招式"); }
@Override protected void weapons() {
}
@Override protected void neigong() { System.out.println("运行内功"); }
@Override protected boolean hasWeapons() { return false; } }
public class ZhangSanFeng extends AbstractSwordsman{
@Override protected void moves() { System.out.println("使用招式"); }
@Override protected void weapons() { System.out.println("使用武器"); }
@Override protected void neigong() { System.out.println("运行内功"); }
@Override protected void hook() { System.out.println("处理自定义逻辑"); } }
|
客户端调用:
1 2 3 4 5 6 7 8
| public class Client{ public static void main(String[] args) { ZhangWuJi zhangWuJi = new ZhangWuJi(); zhangWuJi.fighting(); ZhangSanFeng zhangSanFeng = new ZhangSanFeng(); zhangSanFeng.fighting(); } }
|
使用场景和优缺点
- 使用场景
- 多个子类有共有的方法,并且逻辑基本相同时。
- 面对重要、复杂的算法,可以把核心算法设计为模板方法,周边相关细节功能则由各个子类实现。
- 需要通过子类来决定父类算法中的某个步骤是否执行,实现子类对父类的反向控制。
- 优点
- 模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。
- 子类实现算法的某些细节,有助于算法的拓展。
- 缺点
- 每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象。
观察者模式(发布-订阅模式)
定义:定义对象间一种一对多的依赖关系,每当一个对象改变状态时,则所有依赖于它的对象都会得到通知并被自动更新。
在观察者模式中有如下角色:
- Subject:抽象主题(抽象被观察者)。抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject:具体主题(具体被观察者)。该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- Observer:抽象观察者,是观察者的抽象类。它定义了一个更新接口,使得在得到主题更改通知时更新自己。
- ConcreteObserver:具体观察者。实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
观察者模式的简单实现
以微信公众号为例,假设用户是观察者,公众号是被观察者,当某个公众号更新时就会通知这些订阅的微信用户。
抽象观察者:
1 2 3 4 5 6
|
public interface Observer{ public void update(String message); }
|
具体观察者:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public class WeixinUser implements Observer{
private String name;
public WeixinUser(String name) { this.name = name; }
@Override public void update(String message) { System.out.println(name+" - "+ message); } }
|
抽象被观察者:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notify(String string); }
|
具体被观察者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
public class SubscriptionSubject implements Subject{
private List<Observer> weixinUserList = new ArrayList<>();
@Override public void attach(Observer observer) { weixinUserList.add(observer); }
@Override public void detach(Observer observer) { weixinUserList.remove(observer); }
@Override public void notify(String message) { for (Observer observer : weixinUserList){ observer.update(message); } } }
|
客户端调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Client{ public static void main(String[] args) { SubscriptionSubject subscriptionSubject = new SubscriptionSubject(); WeixinUser weixinUser1= new WeixinUser("user1"); WeixinUser weixinUser2= new WeixinUser("user2"); WeixinUser weixinUser3= new WeixinUser("user3"); subscriptionSubject.attach(weixinUser1); subscriptionSubject.attach(weixinUser2); subscriptionSubject.attach(weixinUser3); subscriptionSubject.notify("更新消息"); } }
|
使用场景和优缺点
- 使用场景
- 关联行为场景。需要注意的是,关联行为是可拆分的,而不是 ”组合“ 关系。
- 事件多级触发场景。
- 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
- 优点
- 观察者和被观察者之间是抽象耦合,容易扩展。
- 方便建立一套触发机制。
- 缺点
- 在应用观察者模式时需要考虑一下开发效率和运行效率的问题。程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在 Java 中消息的通知一般是顺序执行的,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步方式。
备注
参考资料:
Android 进阶之光