设计模式(十二)观察者模式

老板回来时,前台会打电话到公司里,以防老板发现员工们在看股票。代码实现?

版本一  双向耦合的代码

前台秘书类:

class Secretary{
    // 同事列表
    private IList observers = new List();
    private sting action;
    // 增加
    // 有几个同事请前台帮忙就给集合增加几个对象
    public void Attach(StockObserver observer){
        observers.Add(observer);
    }
    
    // 通知
    // 待老板来时,就给所有的登记的同事们发通知“老板来了”
    public void Notify(){
        foreach(StockObserver o in obervers){
            o.Update();
        }
    }
    // 前台状态
    // 前台通过电话,所说的话或所做的事
    public string SecretaryAction{
        get{return action;}
        set{action=value;}
    }
}

 看股票同事类:

class StockObserver{
    private string name;
    private Secretary sub;
    public StockObserver(string name, Secretary sub){
        this.name=name;
        this.sub=sub;
    }
    public void Update(){
        Console.WriteLine("{0} {1} 关闭股票行情,继续工作!", sub.SecretaryAction, name);
    }
}

客户端程序:

static void Main(string[] args){
    // 前台小姐童童
    Secretary tongtong = new Secretary();
    // 看股票的同事
    StockObserver tongshi1 = new StockObserver("猪猪",tongtong);
    StockObserver tongshi2 = new StockObserver("猫猫",tongtong);

    // 前台记下了两位同事
    tongtong.Attach(tongshi1);
    tongtong.Attach(tongshi2);
    // 发现老板回来
    tongtong.SecretaryAction = "老板回来了!";
    // 通知两个同事
    tongtong.Notify();

    Console.Read();
}

写得不错,但是有没有发现?这个前台类和看股票类是互相耦合的——前台类要增加观察者,观察者类需要前台的状态。比如,如果观察者中还有人是想看NBA,前台类代码要怎么做?

 

版本二  解耦实践1(开放-封闭、依赖倒转)

增加了抽象的观察者:

abstract class Observer{
    protected string name;
    protected Secretary sub;

    public Observer(string name,Secretary sub){
        this.name=name;
        this.sub=sub;
    }

    public abstract void Update();
}

增加了两个具体的观察者:

// 看股票的同事
class StockObserver{
    public StockObserver(string name, Secretary sub):base(name,sub)
    {}
    public override void Update(){
        Console.WriteLine("{0} {1} 关闭股票行情,继续工作!", sub.SecretaryAction, name);
    }
}

// 看NBA的同事
class NBAObserver{
    public NBAObserver(string name, Secretary sub):base(name,sub)
    {}
    public override void Update(){
        Console.WriteLine("{0} {1} 关闭NBA直播,继续工作!", sub.SecretaryAction, name);
    }
}

这里让两个观察者去继承抽象观察者,对于Update的方法做重写操作。

下面是前台秘书类的编写,把所有的与具体观察者耦合的地方都改成了抽象观察者。

class Secretary{
    // 同事列表
    private IList observers = new List();
    private sting action;
    // 增加
    // 针对抽象编程,减少了与具体类的耦合
    public void Attach(Observer observer){
        observers.Add(observer);
    }
    
    // 减少
    // 针对抽象编程,减少了与具体类的耦合
    public void Detach(Observer observer){
        observers.Remove(observer);
    }

    // 通知
    public void Notify(){
        foreach(StockObserver o in obervers){
            o.Update();
        }
    }
    // 前台状态
    public string SecretaryAction{
        get{return action;}
        set{action=value;}
    }
}

客户端代码跟前面一样。 

可是这样考虑问题还不够全面——“前台秘书”也是一个具体的类,也应该抽象出来。即观察者不应该依赖具体的实现,而是一个抽象的通知者。

 

版本三  解耦事件2 

增加了抽象通知者接口:

// 通知者接口
interface Subject{
    void Attach(Observer observer);
    void Detach(Observer observer);
    void Notify();
    string SubjectState{
        get;
        set;
    }
}

具体的通知者类可能是前台,也可能是老板,它们也许有各自的一些方法,但对于通知者来说,他们都是一样的,所以它们都去实现这个接口。

class Boss:Subject{
    // 同事列表
    private IList observers = new List();
    private sting action;

    // 增加
    public void Attach(Observer observer){
        observers.Add(observer);
    }
    
    // 减少
    public void Detach(Observer observer){
        observers.Remove(observer);
    }

    // 通知
    public void Notify(){
        foreach(Observer o in obervers){
            o.Update();
        }
    }
    // 老板状态
    public string SecretaryState{
        get{return action;}
        set{action=value;}
    }
}

 前台类似。

对于具体的观察者,需更改的地方就是把与“前台”耦合的地方都改成针对抽象通知者。

abstract class Observer{
    protected string name;
    protected Subject sub;

    public Observer(string name,Subject sub){
        this.name=name;
        this.sub=sub;
    }

    public abstract void Update();
}
// 看股票的同事
class StockObserver{
    public StockObserver(string name, Subject sub):base(name,sub)
    {}
    public override void Update(){
        Console.WriteLine("{0} {1} 关闭股票行情,继续工作!", sub.SubjectState, name);
    }
}

// 看NBA的同事
class NBAObserver{
    public NBAObserver(string name, Subject sub):base(name,sub)
    {}
    public override void Update(){
        Console.WriteLine("{0} {1} 关闭NBA直播,继续工作!", sub.SubjectState, name);
    }
}

代码结构图:

设计模式(十二)观察者模式_第1张图片

 

观察者模式

观察者模式又叫做发布-订阅(Publish/Subscribe)模式 ,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

设计模式(十二)观察者模式_第2张图片 

Subject类,可翻译为主题或抽象通知者,一般用一个抽象类或一个接口实现,它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

abstract class Subject{
    private IList observers = new List();

    // 增加观察者
    public void Attach(Observer observer){
        observers.Add(observer);
    }
    
    // 减少
    public void Detach(Observer observer){
        observers.Remove(observer);
    }

    // 通知
    public void Notify(){
        foreach(StockObserver o in obervers){
            o.Update();
        }
    }
}

Oberser类,抽象观察者。为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个Upadate()方法,这个方法叫做更新方法。

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

ConcreteSubject类,叫做具体主题或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。

class ConcreteSubject : Subject{
    private string subjectState;
    // 具体被观察者状态
    public string SubjectState{
        get{return subjectState;}
        set{subjectState = value;}
    }
}

ConcreteObserver类,具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。

class ConcreteObserver : Observer{
    private string name;
    private string observerState;
    private ConcreteSubject subject;

    public ConcreteObserver(ConcreteSubject subject, string name){
        this.subject=subject;
        this.name=name;
    }

    public override void Update(){
        observerState=subject.SubjectState;
        Console.WriteLine("观察者{0}的新状态是{1}", name, observerState);

    }

    public ConcreteSubject Subject{
        get{return subject;}
        set{subject = value;}
    }
}

客户端代码:

static void Main(string[] args){
    ConcreteSubject s = new ConcreteSubject();

    s.Attach(new ConcreteObserver(s,"X"));
    s.Attach(new ConcreteObserver(s,"Y"));
    s.Attach(new ConcreteObserver(s,"Z"));

    s.SubjectState = "ABC";
    s.Notify();
    
    Console.Read();
}

结果显示:

 

将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不变。而观察者模式的关键对象是主题Subject和观察者Observer,一个Subject可以有任意数目的依赖它的Observer,一旦Subject的状态发生了改变,所有的Observer都可以得到通知。Subject发出通知时并不需要知道谁是它的观察者,也就是说,具体观察者是谁,它根本不需要知道。而任何一个具体观察者不知道也不需要知道其他观察者的存在。

那么,什么时候考虑使用观察者模式呢?

一个对象的改变需要同时改变其他对象,而且,它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。

当一个抽象模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用

总的来讲,观察者模式所做的工作其实就是在解除耦合让耦合的双方都依赖于抽象,而不是依赖于具体,从而使得各自的变化都不会影响另一边的变化。——这是依赖倒转原则的最佳体现!

 

观察者模式的不足

尽管用了依赖倒转原则,但是“抽象通知者”还是依赖“抽象观察者”,也就是说,万一没有了抽象观察者这样的接口,这通知的功能就完不成了。另外就是每个具体观察者不一定是“更新”的方法要调用,有可能每个具体观察者需要调用的方法都不一样。

如果通知者和观察者之间根本就互相不知道,由客户端来决定通知谁,那就好了——

还记得我们序章的内容么?没错,就是事件委托! 

 

事件委托

委托就是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值。委托可以看作是对函数的抽象,是函数的”类“,委托的实例将代表一个具体的函数。一个委托可以搭载多个方法,所有方法被依次唤起。更重要的是,它可以使得委托对象所搭载的方法并不需要属于同一个类。

 这就使得本来在“老板”类中的增加和减少的抽象观察者集合以及通知时遍历的抽象观察者都不必要了。转到客户端来让委托搭载多个方法,这就解决了本来与抽象观察者的耦合问题。

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

 

 

 本章完。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 本文是连载文章,此为第十二章,学习将让多个观察者对象同时监听某一个主题对象,当这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己的观察者模式,该模式充分体现了依赖倒置原则,解除了耦合。同时,我们还复习了事件委托。 

上一章:https://blog.csdn.net/qq_36770641/article/details/82818444  建造者模式

下一章:

------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 

你可能感兴趣的:(设计模式)