设计模式观察者模式

     观察者模式允许我们观察应用程序中一个对象的状态。最好的理解它的方式就是 Publish/ Subscribe 模式。
在观察者模式中有被观察的叫subject,一些观察subject 的对象叫观察者。该模式是松耦合的一个非常好的实例
因为我们的类可以依赖很少的信息进行交互。

subject 需要被三件事关注:
1. 注册一个观察者
2. 删除一个观察者
3. 通知事件的观察者

      比方说我们准备创建一个video game, 在我们的游戏中,我们有一些非玩家角色,这些角色依赖于玩家。
为了该需要,我们假设我们有一个弓箭手类和一个击剑手类,如果玩家在范围内,弓箭手类能够进行攻击,
如果玩家已经被关闭,那么击剑手类就能够进行攻击。那么,我们如何应用观察者模式实现这样的控制呢。

      这是一个很好的应用观察者模式的例子。 在这里 玩家就是 subject .  玩家没有任何概念关于有多少非玩家角色在
观察它。有意思是这都没有关系,因为我们正在广播玩家的位置给关心玩家的非玩家角色。
这里我们采用的是 2维模式。因为计算两点间的距离比较容易。

      为了实现观察者模式,我们先创建两个接口,第一个为subject.  Subject 需要能够注册观,删除,通知 观察者。
在这个实例中,观察者要基于subject (玩家) 的位置去尝试进行攻击。下面是定义的接口。

    namespace Observer
    
{
        
public interface ISubject
        
{
            
void RegisterObserver(IObserver observer);
            
void UnregisterObserver(IObserver observer);
            
void NofifyObservers();
        }


        
public interface IObserver
        
{
            
void Attack(ref Position p);
        }

    }

      我们需要创建一个帮助类来表示角色的位置. 这个类也包含两个方法来计算两个点间的距离。在这个类里的每个事
都应该能被自解释的... 即使你已经对几何生疏了,你也必须相信我正确的计算了两点间的距离。:)


 namespace Observer
   
{
     
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 . 玩家也要有一个属性表明我们当前
的位置.

 namespace Observer
    
{
       
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 接口内被指定了。


      之前提到过,我们实现了两个观察者为这个实例,弓箭手类和击剑手类,下面是它们的实现:

 namespace Observer
    
{
       
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(00);
         }

    
         
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(00);
         }

    
         
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 + “]”);
         }

      }

   }



      现在我们已经准备好了所有的片段,把它们放到一起进行测试。我们的引擎
是要不断跟踪玩家,并且我们添加两个弓箭手和两个击剑手作为观察者。为了
更好的观察,我们用下面图形表示每一个成员的起始点。

我们的引擎是一个简单的控制台程序,这是我添加了两个观察者和玩家后的代码:

   namespace Observer
    
{
       
class Program
       
{
          
static void Main(string[] args)
          
{
             
// Set up our observers.
             Archer archer1 = new Archer(“Robin Hood”);
             archer1.SetPosition(
new Position(13));
    
            Archer archer2 
= new Archer(“Legolas”);
            archer2.SetPosition(
new Position(17));
    
            Swordsman swordsman1 
= new Swordsman(“Conan”);
            swordsman1.SetPosition(
new Position(74));
            
            Swordsman swordsman2 
= new Swordsman(“Aragorn”);
            swordsman2.SetPosition(
new Position(76));
    
            
// 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(115));
    
            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接口的观察者实现。

希望本文能让你对观察者模式有一个清晰的认识。

原文地址:https://www.cnblogs.com/flyinthesky/p/1216530.html