其他分享
首页 > 其他分享> > 我的设计模式之旅 ⑦ 观察者模式

我的设计模式之旅 ⑦ 观察者模式

作者:互联网

一个菜鸟的设计模式之旅,文章可能会有不对的地方,恳请大佬指出错误。

编程旅途是漫长遥远的,在不同时刻有不同的感悟,本文会一直更新下去。

程序介绍

本程序实现观察者模式。使用C#、Go两门语言分别进行实现。程序创建一个全局游戏死亡事件通知,5个玩家、1个Boss,当任意一方死亡时,在场存活者都能收到阵亡者的消息。

观察者模式
----------游戏回合开始----------
最终BOSS 击杀 二号玩家 !
一号玩家 知道 二号玩家 阵亡了!
三号玩家 知道 二号玩家 阵亡了!
四号玩家 知道 二号玩家 阵亡了!
五号玩家 知道 二号玩家 阵亡了!
最终BOSS 知道 二号玩家 阵亡了!
----------过了一段时间----------
最终BOSS 击杀 四号玩家 !
一号玩家 知道 四号玩家 阵亡了!
三号玩家 知道 四号玩家 阵亡了!
五号玩家 知道 四号玩家 阵亡了!
最终BOSS 知道 四号玩家 阵亡了!
----------过了一段时间----------
一号玩家 击杀 最终BOSS!
一号玩家 知道 最终BOSS 阵亡了!
三号玩家 知道 最终BOSS 阵亡了!
五号玩家 知道 最终BOSS 阵亡了!

C# 程序代码

observerOriginal.cs

image-20220911023322689

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace observer_original
{
    public abstract class Subject
    {
        private List<Observer> observers = new();

        public void Attach(Observer o)
        {
            observers.Add(o);
        }

        public void Detach(Observer o)
        {
            observers.Remove(o);
        }

        public void Notify()
        {
            foreach (Observer o in observers)
            {
                o.Update();
            }
        }
    }

    public class DeadSubject : Subject
    {
        public ICharacter? DeadEntity { get; set; }
    }

    public abstract class Observer
    {
        public abstract void Update();
    }

    public interface ICharacter
    {
        public string Name { get; }
        void Dead();
        void Kill(ICharacter who);
    }

    public class Player : Observer, ICharacter
    {
        private readonly DeadSubject? sub;
        public string Name { get; }

        public Player(string name)
        {
            sub = null;
            Name = name;
        }

        public Player(string name, DeadSubject subject)
        {
            sub = subject;
            Name = name;
        }

        public override void Update()
        {
            if (sub == null) return;
            Console.WriteLine($"{Name} 知道 {sub?.DeadEntity?.Name} 阵亡了!");
        }

        public void Dead()
        {
            if (sub == null) return;
            sub.DeadEntity = this;
            sub.Detach(this);
            sub.Notify();
        }

        public void Kill(ICharacter who)
        {
            Console.WriteLine($"{Name} 击杀 {who.Name}!");
            who.Dead();
        }
    }


    public class Boss : Observer, ICharacter
    {
        public string Name { get; }
        private DeadSubject? sub;

        public Boss(string name)
        {
            sub = null;
            Name = name;
        }

        public Boss(string name, DeadSubject subject)
        {
            sub = subject;
            Name = name;
        }

        public override void Update()
        {
            if (sub == null) return;
            Console.WriteLine($"{Name} 知道 {sub?.DeadEntity?.Name} 阵亡了!");
        }

        public void Dead()
        {
            if (sub == null) return;
            sub.DeadEntity = this;
            sub.Detach(this);
            sub.Notify();
        }

        public void Kill(ICharacter who)
        {
            Console.WriteLine($"{Name} 击杀 {who.Name} !");
            who.Dead();
        }
    }

    static class ObserverOriginal
    {
        public static void Start()
        {
            Console.WriteLine("观察者模式");
            DeadSubject sub = new DeadSubject();
            Boss boss = new Boss("最终BOSS", sub);
            Player p1 = new Player("一号玩家", sub);
            Player p2 = new Player("二号玩家", sub);
            Player p3 = new Player("三号玩家", sub);
            Player p4 = new Player("四号玩家", sub);
            Player p5 = new Player("五号玩家", sub);
            sub.Attach(boss);
            sub.Attach(p1);
            sub.Attach(p2);
            sub.Attach(p3);
            sub.Attach(p4);
            sub.Attach(p5);
            Console.WriteLine("----------游戏回合开始----------");
            boss.Kill(p2);
            Console.WriteLine("----------过了一段时间----------");
            boss.Kill(p4);
            Console.WriteLine("----------过了一段时间----------");
            p1.Kill(boss);
        }
    }
}

observerDelegate.cs

为什么使用事件委托

当观察者对象没有实现观察者接口的方法,而是各持一词,比如窗体的各个空间,方法已经写死无法添加,按原有设计通知者无法进行做到通知。这时候可以使用C#提供的事件委托功能,声明一个函数抽象,将各个观察者的同型函数进行类化,通过事件委托机制,通知各个函数的运行。原先的Obsever接口可以去除,Subject抽象类也不再需要AttachDetach方法,可以转变成接口,让具体通知者类去实现通知方法,具体通知类声明一个事件委托变量。

程序代码

image-20220911025908731

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace observer_delegate
{
  public delegate void DeadEventHandler();
  public interface Subject
  {
    void Notify();
  }

  public class DeadSubject : Subject
  {
    public event DeadEventHandler? DeadEvent;
    public ICharacter? DeadEntity { get; set; }
    public void Notify()
    {
      DeadEvent?.Invoke();
    }
  }

  public interface ICharacter
  {
    public string Name { get; }
    void Dead();
    void Kill(ICharacter who);
  }

  public class Player : ICharacter
  {
    private readonly DeadSubject? sub;
    public string Name { get; }

    public Player(string name)
    {
      sub = null;
      Name = name;
    }

    public Player(string name, DeadSubject subject)
    {
      sub = subject;
      Name = name;
    }

    // 处理通知
    public void PlayerUpdate()
    {
      if (sub == null) return;
      Console.WriteLine($"{Name} 知道 {sub?.DeadEntity?.Name} 阵亡了!");
    }

    public void Dead()
    {
      if (sub == null) return;
      sub.DeadEntity = this;
      sub.DeadEvent -= PlayerUpdate;
      sub.Notify();
    }

    public void Kill(ICharacter who)
    {
      Console.WriteLine($"{Name} 击杀 {who.Name}!");
      who.Dead();
    }
  }


  public class Boss : ICharacter
  {
    public string Name { get; }
    private DeadSubject? sub;

    public Boss(string name)
    {
      sub = null;
      Name = name;
    }

    public Boss(string name, DeadSubject subject)
    {
      sub = subject;
      Name = name;
    }

    public void BossUpdate()
    {
      if (sub == null) return;
      Console.WriteLine($"{Name} 知道 {sub?.DeadEntity?.Name} 阵亡了!");
    }

    public void Dead()
    {
      if (sub == null) return;
      sub.DeadEntity = this;
      sub.DeadEvent -= BossUpdate;
      sub.Notify();
    }

    public void Kill(ICharacter who)
    {
      Console.WriteLine($"{Name} 击杀 {who.Name} !");
      who.Dead();
    }
  }

  static class ObserverDelegate
  {
    public static void Start()
    {
      Console.WriteLine("观察者模式");
      DeadSubject sub = new DeadSubject();
      Boss boss = new Boss("最终BOSS", sub);
      Player p1 = new Player("一号玩家", sub);
      Player p2 = new Player("二号玩家", sub);
      Player p3 = new Player("三号玩家", sub);
      Player p4 = new Player("四号玩家", sub);
      Player p5 = new Player("五号玩家", sub);
      sub.DeadEvent += p1.PlayerUpdate;
      sub.DeadEvent += p2.PlayerUpdate;
      sub.DeadEvent += p3.PlayerUpdate;
      sub.DeadEvent += p4.PlayerUpdate;
      sub.DeadEvent += p5.PlayerUpdate;
      sub.DeadEvent += boss.BossUpdate;
      Console.WriteLine("----------游戏回合开始----------");
      boss.Kill(p2);
      Console.WriteLine("----------过了一段时间----------");
      boss.Kill(p4);
      Console.WriteLine("----------过了一段时间----------");
      p1.Kill(boss);
    }
  }
}

Program.cs

Programusing System;
using observer_original;
using observer_delegate;

namespace observer
{
  class Program
  {
    public static void Main(string[] args)
    {
      // ObserverOriginal.Start();
      ObserverDelegate.Start();
    }
  }
}

Console

观察者模式
----------游戏回合开始----------
最终BOSS 击杀 二号玩家 !
一号玩家 知道 二号玩家 阵亡了!
三号玩家 知道 二号玩家 阵亡了!
四号玩家 知道 二号玩家 阵亡了!
五号玩家 知道 二号玩家 阵亡了!
最终BOSS 知道 二号玩家 阵亡了!
----------过了一段时间----------
最终BOSS 击杀 四号玩家 !
一号玩家 知道 四号玩家 阵亡了!
三号玩家 知道 四号玩家 阵亡了!
五号玩家 知道 四号玩家 阵亡了!
最终BOSS 知道 四号玩家 阵亡了!
----------过了一段时间----------
一号玩家 击杀 最终BOSS!
一号玩家 知道 最终BOSS 阵亡了!
三号玩家 知道 最终BOSS 阵亡了!
五号玩家 知道 最终BOSS 阵亡了!

Go 程序代码

observer.go

package main

import "fmt"

type IObserver interface {
	Update()
}

type ISubject interface {
	Attach(o IObserver)
	Detach(o IObserver)
	Notify()
}

type Subject struct {
	observers []IObserver
}

func (sub *Subject) Attach(o IObserver) {
	sub.observers = append(sub.observers, o)
}

func (sub *Subject) Detach(o IObserver) {
	obs := make([]IObserver, 0, len(sub.observers)-1)
	for _, v := range sub.observers {
		if v != o {
			obs = append(obs, v)
		}
	}
	sub.observers = obs
}

func (sub Subject) Notify() {
	for _, v := range sub.observers {
		v.Update()
	}
}

type ICharacter interface {
	Name() string
	Kill(who ICharacter)
	Dead()
}

type DeadSubject struct {
	*Subject
	Character ICharacter
}

type Character struct {
	name        string
	deadSubject *DeadSubject
}

// ^ 抽象角色共有的方法,表示属性
func (c Character) Name() string {
	return c.name
}

type Player struct {
	Character
}

func (p Player) Update() {
	fmt.Printf("%s 知道 %s 阵亡了\n", p.name, p.deadSubject.Character.Name())
}

func (p Player) Kill(who ICharacter) {
	fmt.Printf("%s 杀死 %s \n", p.name, who.Name())
	who.Dead()
}

// ^ *Player 获取真实实例而不是复制实例,确保Detach工作正常
func (p *Player) Dead() {
	p.deadSubject.Character = p
	p.deadSubject.Detach(p)
	p.deadSubject.Notify()
}

type Boss struct {
	Character
}

func (p Boss) Update() {
	fmt.Printf("%s 知道 %s 阵亡了\n", p.name, p.deadSubject.Character.Name())
}

func (p Boss) Kill(who ICharacter) {
	fmt.Printf("%s 杀死 %s \n", p.name, who.Name())
	who.Dead()
}

func (p *Boss) Dead() {
	p.deadSubject.Character = p
	p.deadSubject.Detach(p)
	p.deadSubject.Notify()
}

main.go

package main

import "fmt"

func main() {
	sub := &DeadSubject{
		&Subject{make([]IObserver, 0)},
		&Player{},
	}
	p1 := &Player{Character{"一号玩家", sub}}
	p2 := &Player{Character{"二号玩家", sub}}
	p3 := &Player{Character{"三号玩家", sub}}
	p4 := &Player{Character{"四号玩家", sub}}
	p5 := &Player{Character{"五号玩家", sub}}
	boss := &Boss{Character{"最终Boss", sub}}
	sub.Attach(p1)
	sub.Attach(p2)
	sub.Attach(p3)
	sub.Attach(p4)
	sub.Attach(p5)
	sub.Attach(boss)
	boss.Kill(p1)
	fmt.Println("-------过了一会-------")
	boss.Kill(p4)
	fmt.Println("-------过了一会-------")
	p2.Kill(boss)
}

Console

最终Boss 杀死 一号玩家 
二号玩家 知道 一号玩家 阵亡了
三号玩家 知道 一号玩家 阵亡了
四号玩家 知道 一号玩家 阵亡了
五号玩家 知道 一号玩家 阵亡了
最终Boss 知道 一号玩家 阵亡了
-------过了一会-------
最终Boss 杀死 四号玩家 
二号玩家 知道 四号玩家 阵亡了
三号玩家 知道 四号玩家 阵亡了
五号玩家 知道 四号玩家 阵亡了
最终Boss 知道 四号玩家 阵亡了
-------过了一会-------
二号玩家 杀死 最终Boss 
二号玩家 知道 最终Boss 阵亡了
三号玩家 知道 最终Boss 阵亡了
五号玩家 知道 最终Boss 阵亡了

思考总结

事件委托

委托是一种引用方法的类型。一旦委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值。委托可以看作是对函数的抽象,是函数的类,是对函数的封装。委托的实例将代表一个具体的函数。

事件是委托的一种特殊形式,当发生有意义的事情时,事件对象处理通知过程。

  public delegate void DeadEventHandler(); //声明了一个特殊的“类”

  public class DeadSubject : Subject
  {
    // 声明了一个事件委托变量叫DeadEvent
    public event DeadEventHandler? DeadEvent;
    ...
  }
  ...
  // 创建委托的实例并搭载给事件委托变量
  sub.DeadEvent += new DeadEventHandler(p1.PlayerUpdate)  // 等同 sub.DeadEvent += p1.PlayerUpdate;	

一个事件委托变量可以搭载多个方法,所有方法被依次唤起。委托对象所搭载的方法并不需要属于同一个类。

委托对象所搭载的所有方法必须具有相同的原形和形式,也就是拥有相同的参数列表和返回值类型。

什么是观察者模式

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。

image-20220911022822586

观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

由于对象间相互的依赖关系,很容易违背依赖倒转原则开放-封闭原则。因此需要我们对通知方和观察者之间进行解耦。让双方依赖抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

如何解决:使用面向对象技术,可以将这种依赖关系弱化。

关键代码:C#中,Subject抽象类里有一个 ArrayList 存放观察者们。Go中,使用切片存放观察者门。

应用实例:

优点:

缺点:

使用场景:

注意事项:

参考资料

标签:sub,之旅,观察者,玩家,Player,设计模式,阵亡,public,Name
来源: https://www.cnblogs.com/linxiaoxu/p/16683365.html