观察者模式允许我们观察应用程序中一个对象的状态。最好的理解它的方式就是 Publish/ Subscribe 模式。
在观察者模式中有被观察的叫subject,一些观察subject 的对象叫观察者。该模式是松耦合的一个非常好的实例
因为我们的类可以依赖很少的信息进行交互。
subject 需要被三件事关注:
1. 注册一个观察者
2. 删除一个观察者
3. 通知事件的观察者
比方说我们准备创建一个video game, 在我们的游戏中,我们有一些非玩家角色,这些角色依赖于玩家。
为了该需要,我们假设我们有一个弓箭手类和一个击剑手类,如果玩家在范围内,弓箭手类能够进行攻击,
如果玩家已经被关闭,那么击剑手类就能够进行攻击。那么,我们如何应用观察者模式实现这样的控制呢。
这是一个很好的应用观察者模式的例子。 在这里 玩家就是 subject . 玩家没有任何概念关于有多少非玩家角色在
观察它。有意思是这都没有关系,因为我们正在广播玩家的位置给关心玩家的非玩家角色。
这里我们采用的是 2维模式。因为计算两点间的距离比较容易。
为了实现观察者模式,我们先创建两个接口,第一个为subject. Subject 需要能够注册观,删除,通知 观察者。
在这个实例中,观察者要基于subject (玩家) 的位置去尝试进行攻击。下面是定义的接口。
{
public interface ISubject
{
void RegisterObserver(IObserver observer);
void UnregisterObserver(IObserver observer);
void NofifyObservers();
}
public interface IObserver
{
void Attack(ref Position p);
}
}
我们需要创建一个帮助类来表示角色的位置. 这个类也包含两个方法来计算两个点间的距离。在这个类里的每个事
都应该能被自解释的... 即使你已经对几何生疏了,你也必须相信我正确的计算了两点间的距离。:)
{
public class Position
{
private int _x;
private int _y;
public int X
{
get { return _x; }
set { _x = value; }
}
public int Y
{
get { return _y; }
set { _y = value; }
}
public Position()
{
_x = _y = 0;
}
public Position(int x, int y)
{
_x = x;
_y = y;
}
// This could be done on 1 line, but I broke
// it apart for readability.
public double CalcDistance(Position pos)
{
int xsquared = (this.X - pos.X) * (this.X - pos.X);
int ysquared = (this.Y - pos.Y) * (this.Y - pos.Y);
int sum = xsquared + ysquared;
return Math.Sqrt(Convert.ToDouble(sum));
}
public override string ToString()
{
return _x.ToString() + “, “ + _y.ToString();
}
}
}
我们要把规则简化,我们要有两个类,射箭手类和击剑手类。我们假设一个弓箭手只能在距离
玩家四到十个单元的范围内攻击玩家。一个击剑手必须在两个单元格内攻击玩家。我们创建一个
玩家,它必须实作ISubject 接口,因为它要做为 subject . 玩家也要有一个属性表明我们当前
的位置.
{
public class Player : ISubject
{
private List<IObserver> _observers;
private Position _currentPosition;
public Position CurrentPosition
{
get { return _currentPosition; }
set { _currentPosition = value; }
}
public List<IObserver> Observers
{
get { return _observers; }
}
// We initialize our observer list in the constructor.
public Player()
{
_observers = new List<IObserver>();
}
// This is the key event in our scenario. The only thing
// that the observers care about is the position of the
// player, so we notify them when it changes.
public void SetPosition(Position pos)
{
_currentPosition = pos;
Console.WriteLine(“Player is at “ + pos.ToString());
NofifyObservers();
}
// This is our function to register a new observer.
public void RegisterObserver(IObserver observer)
{
if (!_observers.Contains(observer))
_observers.Add(observer);
}
// This takes an observer out of the list.
public void UnregisterObserver(IObserver observer)
{
if (_observers.Contains(observer))
_observers.Remove(observer);
}
// This does the notification. In our case
// that means each observer will try to attack
// based on the position of the player.
public void NofifyObservers()
{
foreach (IObserver obs in _observers)
obs.Attack(ref _currentPosition);
}
}
}
我们已经建立subject . 现在我们需要实现几个观察者,注意到,RegisterObserver 方法和
UnregisterObserver 方法 只是简单的从有效的观察者内部列表里添加和删除观察者。这些
方法都传递一个IObserver 参数,这意味着它们会接受任何实现IObserver 接口的对象。NotifyObserver
方法简单的轮循观察者列表,并且调用每一个对象的Attack 方法。我们知道每一个观察者都有
这样的方法,因为在IObserver 接口内被指定了。
之前提到过,我们实现了两个观察者为这个实例,弓箭手类和击剑手类,下面是它们的实现:
{
public class Archer : IObserver
{
private string _name;
private Position _current;
public string Name
{
get { return _name; }
set { _name = value; }
}
public Position Current
{
get { return _current; }
set { _current = value; }
}
public Archer(string name)
{
_name = name;
_current = new Position(0, 0);
}
public void SetPosition(Position pos)
{
_current = pos;
}
public void Attack(ref Position p)
{
// Archer can attack if use is between 4 and 10
// units away.
double distance = p.CalcDistance(_current);
string formatted = String.Format(“{0:0.00}”, distance);
if (distance >= 4 && distance <= 10)
Console.WriteLine(_name
+ ” is attacking! [” + formatted + “]”);
else if (distance < 4)
Console.WriteLine(_name
+ ” is too close to attack! [” + formatted + “]”);
else if (distance > 10)
Console.WriteLine(_name
+ ” is too far away to attack! [” + formatted + “]”);
}
}
public class Swordsman : IObserver
{
private string _name;
private Position _current;
public string Name
{
get { return _name; }
set { _name = value; }
}
public Position Current
{
get { return _current; }
set { _current = value; }
}
public Swordsman(string name)
{
_name = name;
_current = new Position(0, 0);
}
public void SetPosition(Position pos)
{
_current = pos;
}
public void Attack(ref Position p)
{
// Swordsman can attack if the distance is 2
// units or less from the player.
double distance = p.CalcDistance(_current);
string formatted = String.Format(“{0:0.00}”, distance);
if (distance <= 2)
Console.WriteLine(_name
+ ” is attacking! [” + formatted + “]”);
else
Console.WriteLine(_name
+ ” is too far away to attack! [” + formatted + “]”);
}
}
}
现在我们已经准备好了所有的片段,把它们放到一起进行测试。我们的引擎
是要不断跟踪玩家,并且我们添加两个弓箭手和两个击剑手作为观察者。为了
更好的观察,我们用下面图形表示每一个成员的起始点。
我们的引擎是一个简单的控制台程序,这是我添加了两个观察者和玩家后的代码:
{
class Program
{
static void Main(string[] args)
{
// Set up our observers.
Archer archer1 = new Archer(“Robin Hood”);
archer1.SetPosition(new Position(1, 3));
Archer archer2 = new Archer(“Legolas”);
archer2.SetPosition(new Position(1, 7));
Swordsman swordsman1 = new Swordsman(“Conan”);
swordsman1.SetPosition(new Position(7, 4));
Swordsman swordsman2 = new Swordsman(“Aragorn”);
swordsman2.SetPosition(new Position(7, 6));
// Set up the subject and register
// the observers we created.
Player p = new Player();
p.RegisterObserver(archer1);
p.RegisterObserver(archer2);
p.RegisterObserver(swordsman1);
p.RegisterObserver(swordsman2);
// Set the position of the player.
// This notifies all the observers
// and they will try to attack.
p.SetPosition(new Position(11, 5));
Console.ReadLine();
}
}
}
上面的代码 p.SetPosition(new Position(11, 5)); 会广播玩家的位置给观察者,观察者会尝试攻击运行程序会产生下面结果:
上面的玩家离的太远,我们调整一下玩家的位置
1: p.SetPosition(new Position(9, 5));
1: p.UnregisterObserver(archer1); 取消archer1观察者身份
2: p.SetPosition(new Position(9, 5));
archer1 不再响应玩家的动作,新的观察者很容易通过RegisterObserver() 方法添加
松耦合允许我们创建任何种类的观察者,而玩家类不需要知道它。玩家只关心我们创建
的IObserver接口的观察者实现。
希望本文能让你对观察者模式有一个清晰的认识。