原帖地址:http://blog.csdn.net/roderick2015/article/details/52728053,转载请注明。
想必玩过网络游戏的读者都知道任务系统,就是有人交给你个任务,然后你去完成这个目标。比如打打怪什么的,像下面这张图所示,是让你去消灭一个叫做隔壁家老王的怪物。
当你达成目标后,系统会提示你任务完成,还会给你点小奖励。那系统是怎么跟踪任务的,它怎么就知道你完成了呢?今天我们就从游戏的角度来了解观察者模式,首先我们需要编写玩家和任务系统两个对象,最后在场景类中进行调用。
玩家类
玩家就是干三件事:1接受任务,2找到任务怪,3消灭怪物,然后更新notify标志位,好让监听对象知道你的状态。
啥,隔壁老王长啥样?可能是这个样子吧。
public class Player {
private String name = "张三"; //玩家的名字
//标志位为true时,表示有状态更新,监听者会获取最新状态
private boolean notify = false;
/**
* 接受任务
*/
public void receiveTask() {
System.out.println(this.name + "接受任务:危险:隔壁家老王");
System.out.println("任务进度:0/1 消灭隔壁家老王");
}
/**
* 找啊找啊找怪物
*/
public void findMonsters() throws InterruptedException {
Thread.sleep(1000); //找怪物是要时间的
System.out.println(this.name + "找到隔壁家老王,开始揍它!");
}
/**
* 攻击怪物
*/
public void attack() {
System.out.println(this.name + "扇了隔壁家老王两个大耳巴子!");
System.out.println("隔壁家老王被" + this.name + "揍扁了。");
//怪物揍完了,修改更新标志位notify
setNotify(true);
}
//setter和getter方法省略
}
任务系统类
这个类其实是个线程,所以继承了Thread类。它会不停的循环获取玩家notify状态,当为true时,更新任务信息。
public class TaskSystem extends Thread {
private Player player = null;
public TaskSystem(Player player) {
if (player == null) //非空验证
throw new NullPointerException("Player required not null!");
this.player = player;
}
@Override
public void run() {
System.out.println("任务系统:开始监听。");
while (true) { //会一直监听玩家的notify状态
if (player.isNotify()) {
player.setNotify(false);
//更新任务进度
System.out.println("任务进度:1/1 消灭隔壁家老王");
System.out.println("任务系统:" + this.player.getName() + "完成任务,监听结束。");
break; //任务结束不再监听
}
}
}
}
最后在场景类中调用他们
public class Client {
public static void main (String[] args) {
//创建玩家对象
Player player = new Player();
//创建任务系统对象,并传入需要监听的玩家和任务信息,全程就盯你们两了
TaskSystem system = new TaskSystem(player);
system.start(); //启动监听线程
player.receiveTask(); //接受任务
//玩家开始执行任务
player.findMonsters(); //寻找任务怪
player.attack(); //开扁
}
}
好了,我们跑起来看看结果,如下图所示。
敲了半天代码,好不容易把需求实现了,但是仔细一看发现问题还挺多,可小用却不堪大用啊。代码规范什么的先不说,单性能这一关就过不去。你这任务系统不断的循环去获取玩家状态,这可多浪费资源啊,就好比有人不停的问你肚子饿不饿,肯定烦啊。那每隔30秒询问一下行不,能接受了吧,这样虽然好了很多,但是玩家的状态就更新不及时了。除此之外任务系统在创建时,就指定了玩家对象,意思是我只盯着你,那1000个玩家,就得1000个系统对象了?这也是极大的浪费。而且玩家可能不仅仅被任务系统监听,还可能被成就系统、奖励系统之类的监听,那这代码还写的下去嘛。
所以我们换个思路,任务系统跟管家婆一样吃力不讨好,那咱就矫情点呗,让玩家把事情干好了知会我一声就成,我还要监听其他人呢。对设计模式熟悉的读者,可能已经发现了,这里遵循了好莱坞原则。可是玩家把事情做完了,又怎么通知任务系统呢,难不成打电话?电话是没有的,但是作为对象咱有引用啊,我把引用交给你,这样你就可以在内存中找到我了。
从角色分类上看,任务系统是观察者,玩家是被观察者,所以我们增加两个接口,观察者和被观察者,代码如下。
/**
* 观察者接口
*/
public interface Observer {
//只需要负责收到通知后,执行预先设定好的处理逻辑就行
public void update(Player player);
}
/**
* 被观察者接口,它的职责就是动态的添加、删除和通知观察者,
*/
public interface Observable {
//添加观察者,可以添加多个,然后集中管理
public void addObserver(Observer observer);
//删除观察者
public void deleteObserver(Observer observer);
//通知观察者,调用Observer的update方法
public void notifyObserver();
}
有了这两个接口,我们再重新规划一下代码,玩家类实现被观察者接口,任务系统类实现观察者接口,在场景类中将任务系统的对象引用传入玩家对象中管理,玩家在消灭怪物后,通过该引用直接调用任务系统预先设定好的更新方法即可,这样任务系统只要等着就行,其他时候该干嘛干嘛去。
任务系统
public class TaskSystem implements Observer {
public void update(Player player) {
if (player == null) //非空验证
throw new NullPointerException("Player required not null!");
System.out.println("任务系统:" + player.getName() + "完成任务 危险:隔壁家老王");
}
}
奖励系统
我们还可以增加一个奖励系统类,做完任务再给你点奖赏,逻辑与任务系统类似,代码如下。
public class RewardSystem implements Observer {
public void update(Player player) {
if (player == null) //非空验证
throw new NullPointerException("Player required not null!");
System.out.println("奖励系统:" + player.getName() + "获得奖励247金币");
}
}
玩家
public class Player implements Observable {
private String name = "张三";
//注册的观察者列表
private List<Observer> observerList = new LinkedList<Observer>();
@Override
public void addObserver(Observer observer) { this.observerList.add(observer); }
@Override
public void deleteObserver(Observer observer) { this.observerList.remove(observer); }
@Override
public void notifyObserver() {
//逐个通知所有的观察者,实际项目中可按需处理。
for (Observer observer : this.observerList) {
observer.update(this, this.task);
}
}
/**
* 攻击怪物
*/
public void attack() {
System.out.println(this.name + "扇了隔壁家老王两个大耳巴子!");
System.out.println("隔壁家老王被" + this.name + "揍扁了。");
//怪物揍完了,通知观察者
notifyObserver();
}
//其余代码不变,故省略
}
场景类
public static void main (String[] args) throws InterruptedException {
//创建任务系统对象 — 观察者
TaskSystem taskSystem = new TaskSystem();
//创建奖励系统对象 — 观察者
RewardSystem rewardSystem = new RewardSystem();
//创建玩家对象 — 被观察者
Player player1 = new Player();
//添加观察者
player1.addObserver(taskSystem);
player1.addObserver(rewardSystem);
//玩家开始执行任务了
player1.receiveTask(); //先接受任务
player1.findMonsters(); //再找到任务怪
player1.attack(); //开扁
}
}
运行结果如下图所示。
观察者模式的定义:定义对象间一对多的依赖关系,使得每当一个对象改变状态时,所有依赖它的对象都会得到通知并自动更新。这个模式也叫做发布-订阅模式,即被观察者发布主题,观察者订阅关心的主题,当主题状态发生改变时,所有已订阅的观察者都将收到通知。
优点
1.分离了观察者和被观察者,将主动权交给了被观察者,只在需要的时候通知观察者,而且被观察者也不是直接调用,是通过接口抽象耦合的,这样两者的独立开发和扩展就很方便了。
2.主题的发布和订阅变得非常灵活,观察者订阅主题就如我们订阅公众号一样轻松,不想接收消息时,从观察者列表中移除就行了。这里有一个误区,是不是观察者可以订阅多个不同类型的主题,再分别对接收到的消息进行处理呢?从代码实现的角度上是没有问题的,但违背了单一职责原则,这会大大增加系统的复杂度,因此并不建议。
3.也正因为单一职责原则,我们可以把观察者模式串起来,做成一个触发链,就像多米诺骨牌那样,建立一套触发机制。好比你到一个网站注册会员,系统会马不停蹄的给你发短信、创建账号、发邮件、推荐商品、推送广告等等,而他们之间又存在逻辑的对应关系,那我们可以把这些步骤做成一个层次性的触发链,这种方式也叫做广播链(注意与责任链模式区分,观察者模式侧重的是一对多的依赖)。但链的规模应该控制,因为中间的类既是观察者也是被观察者,赋予了双重身份,如果消息转发次数过多,只会把系统变得复杂,如下图所示。
缺点
观察者模式最大的问题就是效率,比如订阅的观察者非常多,需要多级触发时,只要一个观察者出了问题或是耗时久,那整个链的执行都会受到影响。尤其是模块甚至子系统间的触发时,因为每个模块的处理能力和负载量是不一样的,导致情况变得非常复杂,如果消息的传递和反馈不是那么的紧迫,那么可以使用消息队列这种异步的方式来实现。
进一步优化
既然代码都到这一步了,我们何不再优化下,让它接地气,更像实际项目中的代码。帖子篇幅有限,有兴趣的读者可以点击此处查看。