(19)行为型模式——观察者

行为型模式——观察者(Observer)

问题背景

当需要用一种通知机制代替轮询时,考虑使用观察者。在一些游戏中,策划出于某种目的会把玩家的属性划分成一级属性(力量、智力、体质等)和二级属性(生命、魔法、攻击力、防御力等)。在其他因素(装备、修炼、BUFF等)不变的条件下,二级属性是一级属性的函数。因此,在一级属性发生变化时,需要立刻更新二级属性的值。要实现这一功能,最粗暴的做法就是轮询,每帧重新计算一次二级属性。但这显然是不可能的,游戏的一帧只有10毫秒左右,可没时间给你算这玩意。

解决方案

为了避免轮询产生大量不必要的计算,我们采用另一种策略:Don’t call me, I’ll call you。也就是说,不要让二级属性一直去访问一级属性,而是让一级属性发生变化时通知二级属性。这样,就能大幅降低系统的开销。

提取两个接口:IObserver和IObservable,分别表示观察者和被观察者。IObserver有一个Update方法,表示在被通知时执行的操作。IObservable有两个方法:Subscribe、Unsubscribe,分别表示订阅和注销。书中的被观察者接口还有一个Notify方法,表示通知订阅者发生了变化。把这个方法放到接口中就意味着外界也可以触发通知操作,说实话我并不赞同这种设计,C#也是(参见C#的事件机制)。

观察者如果想接收被观察者的通知,就调用它的Subscribe方法进行注册。这样,每当被观察者发生变化时,观察者就会被通知,并有机会执行一些操作。当观察者不想再订阅被观察者时,就调用它的Unsubscribe方法进行注销。被观察者必须保证一点:发生变化时一定会通知观察者。

使用观察者后的程序结构如下:
程序结构

效果

  1. 降低了观察者和被观察者的耦合度。
  2. 支持广播通信。
  3. 与轮询相比降低了系统开销。

缺陷

观察者模式维护难度大,观察者的详情无法在编译期确定,每个观察者都不了解其他观察者的情况,它的一次看似无害的更新可能导致其他观察者出错,这种错误通常很难定位。

相关模式

  1. 中介:可以用观察者实现中介。
  2. 单例:可以用单例实现一个全局的事件泵。

实现

using System;
using System.Collections.Generic;

namespace Observer
{
    class Client
    {
        public interface IObserver
        {
            void Update();
        }

        public interface IObservable
        {
            void Subscribe(IObserver observer);
            void Unsubscribe(IObserver observer);
        }

        public class Primary : IObservable
        {
            private List<IObserver> observers = new List<IObserver>();
            private int str;
            private int @int;
            private int con;
            public int STR
            {
                get => str;
                set { str = value; Notify(); }
            }
            public int INT
            {
                get => @int;
                set { @int = value; Notify(); }
            }
            public int CON
            {
                get => con;
                set { con = value; Notify(); }
            }
            private void Notify()
            {
                Console.WriteLine($"一级属性发生变化: [STR: {str}, INT: {@int}, CON: {con}]");
                observers.ForEach((item) => item.Update());
            }
            public void Subscribe(IObserver observer)
            {
                observers.Add(observer);
            }
            public void Unsubscribe(IObserver observer)
            {
                observers.Remove(observer);
            }
        }

        public class Secondary : IObserver
        {
            private Primary primary;
            public int HP { get; set; }
            public int MP { get; set; }
            public int ATK { get; set; }
            public int DEF { get; set; }
            public Secondary(Primary primary)
            {
                this.primary = primary;
            }
            public void Update()
            {
                HP = 5 * primary.STR + 10 * primary.CON;
                MP = 15 * primary.INT;
                ATK = 5 * primary.STR;
                DEF = 5 * primary.CON;
                Console.WriteLine($"二级属性: [HP: {HP}, MP: {MP}, ATK: {ATK}, DEF: {DEF}]");
            }
        }

        public class Character
        {
            public Primary Primary { get; }
            public Secondary Secondary { get; }
            public Character()
            {
                Primary = new Primary();
                Secondary = new Secondary(Primary);
            }
        }

        static void Main(string[] args)
        {
            var character = new Character();
            character.Primary.Subscribe(character.Secondary);
            character.Primary.STR = 10;
            character.Primary.INT = 10;
            character.Primary.CON = 10;
            character.Primary.STR = 20;
            character.Primary.CON = 45;
        }
    }
}

运行结果

发布了27 篇原创文章 · 获赞 41 · 访问量 2069

猜你喜欢

转载自blog.csdn.net/DIAX_/article/details/104336755