行为型模式其七: 《观察者模式》
观察者模式概述
软件系统:一个对象的状态或行为的变化将导致其他对象的状态或行为也发生改变,它们之间将产生联动
观察者模式:
- 定义了对象之间一种一对多的依赖关系,让一个对象的改变能够影响其他对象
- 发生改变的对象称为观察目标,被通知的对象称为观察者。
- 一个观察目标可以对应多个观察者
观察者模式定义
观察者模式:定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象都得到通知并被自动更新。
别名
- 发布-订阅(Publish/Subscribe)模式
- 模型-视图(Model/View)模式
- 源-监听器(Source/Listener)模式
- 从属者(Dependents)模式
观察者模式结构
观察者模式包含以下4个角色:
- Subject(目标)
- ConcreteSubject(具体目标)
- Observer(观察者)
- ConcreteObserver(具体观察者)
在观察者模式中,目标类(也称为主题类)是一个抽象类或接口,用于定义观察者模式中的目标对象的基本行为。目标类通常包含以下几个核心方法:
- 注册观察者:将一个观察者对象注册到目标对象中,使得目标对象可以通知该观察者对象。
- 删除观察者:从目标对象中删除一个观察者对象,使得目标对象不再通知该观察者对象。
- 通知观察者:当目标对象的状态发生改变时,通知所有已注册的观察者对象进行相应的处理。
具体的目标类必须实现目标类中定义的所有方法,并且通常还包含了一些与具体业务逻辑相关的状态和行为。
通过将目标类与具体的目标类分离,观察者模式使得目标类可以独立于具体的实现进行扩展和修改。同时,由于目标类和具体的目标类都实现了相同的接口,因此观察者对象可以通过目标类接口与具体的目标类进行交互,而不需要知道具体目标类的实现细节。这种分离使得系统更加灵活、可扩展和可维护。
实例
在某多人联机对战游戏中,多个玩家可以加入同一战队组成联盟,当战队中的某一成员受到敌人攻击时将给所有其他盟友发送通知,盟友收到通知后将做出响应。 现使用观察者模式设计并实现该过程,以实现战队成员之间的联动。
抽象观察类
// 定义观察类方法
public interface Observer {
public String getName();
public void setName(String name);
public void help(); //声明支援盟友方法
public void beAttacked(AllyControlCenter acc); //声明遭受攻击方法
}
具体观察者类
//战队成员类:实现观察者类的方法
public class Player implements Observer {
private String name;
public Player(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
//支援盟友方法的实现
public void help() {
System.out.println("坚持住," + this.name + "来救你!");
}
//遭受攻击方法的实现,当遭受攻击时将调用战队控制中心类的通知方法notifyObserver()来通知盟友
public void beAttacked(AllyControlCenter acc) {
System.out.println(this.name + "被攻击!");
acc.notifyObserver(name);
}
}
目标类
//战队控制中心类:目标类
我们需要在此定义:
- 储存观察者的集合(这个例子里为战队)
- 添加观察者的方法(这个例子为注册成员)
- 移除观察者的方法(这个例子为注销成员)
- 通知观察者的方法(不用实现)
public abstract class AllyControlCenter {
protected String allyName; //战队名称
protected ArrayList<Observer> players = new ArrayList<Observer>(); //定义一个集合用于存储战队成员
public void setAllyName(String allyName) {
this.allyName = allyName;
}
public String getAllyName() {
return this.allyName;
}
//注册方法
public void join(Observer obs) {
System.out.println(obs.getName() + "加入" + this.allyName + "战队!");
players.add(obs);
}
//注销方法
public void quit(Observer obs) {
System.out.println(obs.getName() + "退出" + this.allyName + "战队!");
players.remove(obs);
}
//声明抽象通知方法
public abstract void notifyObserver(String name);
}
具体目标类
//具体战队控制中心类:具体目标类
public class ConcreteAllyControlCenter extends AllyControlCenter {
public ConcreteAllyControlCenter(String allyName) {
System.out.println(allyName + "战队组建成功!");
System.out.println("----------------------------");
this.allyName = allyName;
}
//实现通知方法
public void notifyObserver(String name) {
System.out.println(this.allyName + "战队紧急通知,盟友" + name + "遭受敌人攻击!");
//遍历观察者集合,调用每一个盟友(自己除外)的支援方法
for(Object obs : players) {
if (!((Observer)obs).getName().equalsIgnoreCase(name)) {
((Observer)obs).help();
}
}
}
}
客户端调用
public class Client {
public static void main(String args[]) {
//定义观察目标对象
AllyControlCenter acc;
acc = new ConcreteAllyControlCenter("金庸群侠");
//定义四个观察者对象
Observer player1,player2,player3,player4,player5;
player1 = new Player("杨过");
acc.join(player1);
player2 = new Player("令狐冲");
acc.join(player2);
player3 = new Player("张无忌");
acc.join(player3);
player4 = new Player("段誉");
acc.join(player4);
player5 = new Player("郭靖");
acc.join(player5);
//某成员遭受攻击
player1.beAttacked(acc);
}
}
输出:
金庸群侠战队组建成功!
杨过加入金庸群侠战队!
令狐冲加入金庸群侠战队!
张无忌加入金庸群侠战队!
段誉加入金庸群侠战队!
郭靖加入金庸群侠战队!
杨过被攻击!
金庸群侠战队紧急通知,盟友杨过遭受敌人攻击!
坚持住,令狐冲来救你!
坚持住,张无忌来救你!
坚持住,段誉来救你!
坚持住,郭靖来救你!
模式优缺点
模式优点
- 可以实现表示层和数据逻辑层的分离
- 在观察目标和观察者之间建立一个抽象的耦合
- 支持广播通信,简化了一对多系统设计的难度
- 符合开闭原则,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便
模式缺点
- 将所有的观察者都通知到会花费很多时间
- 如果存在循环依赖时可能导致系统崩溃
- 没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而只是知道观察目标发生了变化
模式适用环境
模式适用环境
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用
- 一个对象的改变将导致一个或多个其他对象发生改变,且并不知道具体有多少对象将发生改变,也不知道这些对象是谁
- 需要在系统中创建一个触发链