聊聊设计模式 — 观察者模式

本文从网络游戏的任务系统出发,探讨了观察者模式在游戏开发中的应用,通过玩家与任务系统之间的交互,展示了如何利用观察者模式优化性能,减少不必要的资源消耗。同时,文章讨论了观察者模式的优点,如降低耦合度,提高灵活性,并提出了可能的改进措施,以应对大量观察者和多级触发可能导致的效率问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原帖地址: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.也正因为单一职责原则,我们可以把观察者模式串起来,做成一个触发链,就像多米诺骨牌那样,建立一套触发机制。好比你到一个网站注册会员,系统会马不停蹄的给你发短信、创建账号、发邮件、推荐商品、推送广告等等,而他们之间又存在逻辑的对应关系,那我们可以把这些步骤做成一个层次性的触发链,这种方式也叫做广播链(注意与责任链模式区分,观察者模式侧重的是一对多的依赖)。但链的规模应该控制,因为中间的类既是观察者也是被观察者,赋予了双重身份,如果消息转发次数过多,只会把系统变得复杂,如下图所示。
这里写图片描述

缺点
  观察者模式最大的问题就是效率,比如订阅的观察者非常多,需要多级触发时,只要一个观察者出了问题或是耗时久,那整个链的执行都会受到影响。尤其是模块甚至子系统间的触发时,因为每个模块的处理能力和负载量是不一样的,导致情况变得非常复杂,如果消息的传递和反馈不是那么的紧迫,那么可以使用消息队列这种异步的方式来实现。

进一步优化
  既然代码都到这一步了,我们何不再优化下,让它接地气,更像实际项目中的代码。帖子篇幅有限,有兴趣的读者可以点击此处查看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值