1.简单工厂模式:
其中主要涉及到四方面内容:
一是:代码规范。这不仅需要初学者注意,也需要那些在这方面做得不够的老程序员加以重视。正如米老师经常说的一句话:代码是给别人看的!只有自己能看懂的代码,不是好代码。关于代码规范,提高班有专门的相关文档,是王鹏师哥和李雪茹师姐编写的,大家可以多看看。
第二是:面向对象的编程(封装,继承和多态)。以活字印刷为例,生动形象地说明了面向对象的四大特点:可维护,可复用,可扩展和灵活性好。对于我来说,以前接触的都是面向过程的编程,所以改变起来还是有一定难度,不过有志者事竞成,相信在不断学习的这个过程中,我一定会充分感触并理解到面向对象编程的艺术美。
第三点就是UML类图的四种关系:关联,继承,依赖和组合。一些关系的具体应用,我们会在稍后的机房收费系统中用到,多看,多用,自然就熟悉了。
第四点就是简单工厂模式了。它主要有三个参与者:抽象角色,具体产品角色和工厂角色。可以解决如何实例化对象的问题。
2.策略模式:
通过做一个商场收银软件为例了解策略模式。示例具体内容营业员根据客户所购买商品的单价和数量,向客户收费。
策略模式是一种定义一系列算法的方法,它的优点有:(1)以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合(2)它的Strategy类层次为Context定义了一系列的可供重用的算法或行为,继承有助于析取出这些算法中的公共功能。(3)简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
综上可用一句话来表示:策略模式就是用来封装算法的,换言之,策略模式封装了变化。
3.装饰模式:
通过小菜的不同穿着服饰为例,说明:装饰模式就是为已有功能动态地添加更多功能的一种方式。
它的优点是:它把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象。也就是把类中的装饰功能从类中搬移去除,这样可以简化原有的类。有效地把类的核心职责和装饰功能区分开了,这样可以去除相关类中重复的装饰逻辑。
4.代理模式:
讲了一个高中时代,为他人作嫁衣的故事,说明了代理模式。代理模式就是为其他对象提供一种代理以控制对这个对象的访问。
它的应用主要有四种:(1)远程代理,也就是为一个对在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实(2)虚拟代理,是根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象。(3)安全代理,用来控制真实对象访问时的权限。(4)智能指引,指当调用真实的对象时,代理处理另外一些事。
5.工厂方法模式:
通过讲述一群学生代替生病的同学,匿名照顾孤寡老人的事情,展开工厂方法模式的学习。工厂方法模式:是定义一个用于创建对象的接口,让子类决定实例化哪一个类。它使得一个类的实例化延迟到其子类。
细心的同学会发现,在工厂方法中选择判断的问题还是存在的,但是工厂方法把简单工厂的内部逻辑判断移到了客户端代码来进行,想要增加功能,本来是改工厂类的(在简单工厂中),现在只需要修改客户端即可。
6.原型模式:
通过小菜夸张并且大量复印的简历,引出原型模式。原型模式:也就是用原型实例指定创建对象的种类,并且通过拷贝这些原型,来创建新的对象。这其中涉及到了深复制和浅复制。
浅复制是被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。与之不同的深复制则是把引用对象的变量指向复制过的新对象。
7.模板方法模式:
通过标准化考试,讲解了这一模式。模版方法模式,定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。它通过把不便行为搬移到超类,去除子类中的重复代码来体现它的优势。可以说模版方法模式提供了一个很好的代码复用平台。
8.外观模式:
通过对比购买基金和个人炒股,来说明外观模式。外观模式:为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
那么,何时使用外观模式呢?(1)设计初期,有意识的分层(2)开发阶段,增加Facade提供一个简单的接口,减少子系统由于不断的重构造成的越来越复杂的情况。(3)维护一个遗留的大型系统时,为新系统开发一个外观Facade类。
9.建造者模式:
千万个饭店,千万不同味道的鱼香肉丝,但是千万个肯德基,却只有一种味道。由此程杰老师引出了这一模式--建造者模式。建造者模式,也就是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
利用建造者模式,用户只需指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需要知道了。简单来说,建造者模式是在当创建复杂对象的算法应该独立于该对象的组成部分以及它们的庄派方式时适用的模式。
10.观察者模式:
老板回来了,我却不知道,依然在浏览网页,看股票,于是。。。这种情形就是程杰老师说的典型的观察者模式。观察者模式就是让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,是它们能够自动更新自己。简单的说,就是一种一对多的依赖关系。
观察者模式是依赖倒转原则的最佳体现,它所做的工作就是解除耦合,让耦合的双方都依赖抽象,而不是依赖具体。
当然了,在某些情况下,我们是不可能用接口的方式来实现观察者模式的,那该怎么办呢?这就涉及到了委托。
委托就是一种引用方法的类型,一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法也具有参数和返回值,委托可以看作是对函数的抽象,是函数的“类”,委托的实例将代表一个具体的函数。
这里有一点是需要注意的,也就是委托使用的前提:委托对象所搭载的所有方法必须具有相同的原形和形式,也就是拥有相同的参数列表和返回值类型。
11.抽象工厂模式:
小菜遇到了一件麻烦事,用他的话来说:都是换数据库惹的祸。事情是这样的,小菜写好的一个原本用SQLServer作为数据库的电子商务网站,现在客户想要改成Access作为数据库。于是大量的改动来了,只有靠时间来解决问题。这时,大鸟就向小菜讲解了抽象工厂模式的使用。
抽象工厂模式,提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。这样,就算再换成Oracle为数据库,也不用担心了,可以很快的搞定。
抽象工厂模式最大的好处就是易于交换产品系列,它只需要改变具体工厂即可使用不同的产品配置。第二大好处便是让具体的创建实例过程与客户端分离,客户端通过抽象接口来操纵实例。
但是如果需求来自增加功能,抽象工厂模式就暴露的它的缺点,此时会有大批量的改动。这时就可以用简单工厂来改进抽象工厂。
12.状态模式:
不同的工作时间,会有不同的工作状态。针对一个对象之间大量的状态转换,程杰老师说明了状态模式。
状态模式是当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。简单来说,状态模式就是主要解决控制一个对象状态转换的条件表达式过于复杂的情况。也就是,将复杂的判断简单化。
13.适配器模式:
初到NBA的姚明必须要配一名翻译者,教练、队员与他的对话全部通过翻译来沟通。这个翻译者我们就可以理解为是适配器。
由此说到的适配器模式就是将一个类的接口转换成客户希望的另外一个接口。Adapter(适配器)使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
那么合适使用适配器模式呢?当两个类所做的事情相同或相似,但是具有不同的接口时,我们就可以使用适配器模式。这样客户代码可以统一调用同一接口。
14.备忘录模式:
如果再给我一次机会,我一定不会忘记存储数据!可是时间不会倒流,那么我们该如何避免再出现这种情况呢?
这里就可以学习备忘录模式了。在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
15.组合模式:
一家大的公司又有很多子公司,针对这种整体与部分可以被一致对待的问题,可以考虑组合模式。
组合模式,就是将对象组合成树形结构以表示“部分-整体”的层次结构。它使得用户对单个对象和组合对象的使用具有一致性。
至于何时使用该模式?当你发现需求中体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑这种模式了。
16.迭代器模式:
顾名思义,迭代,就是按顺序逐一遍历。这种模式,提供一种方法,顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。
当需要访问一个聚集对象,而且不管这些对象是什么都需要遍历的时候,就可以考虑使用迭代器模式。
17.单例模式:
对于一些刚刚学习面向对象思想的小菜们,普遍的会出现定义过多的类这一现象,对此,程杰老师提出,有些类,也需要计划生育。也就是说,类,并不是越多越好。这就涉及到了一个模式——单例模式。
单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。
此时需要注意的就是在多线程程序中,使用单例模式可能会造成创建多个实例。这时候我们就可以给进程加一把锁来处理。当然了,我们不能让线程每次都加锁,而只是在实例未被创建的时候再加锁处理,同时也能保证线程 的安全。这种做法被称为:多重锁定。
18.桥接模式:
首先先说一个原则——合成/聚合复用原则,即优先使用对象合成/聚合,而不是类继承。它的好处是,有助于保持每个类被封装,并被集中在单个任务上。这样类和类的继承层次会保持较小的规模,并且不太可能增长为不可控制的庞然大物。
对此涉及到的设计模式就是桥接模式。桥接模式将抽象部分与它的实现部分分离,使它们都可以独立的变化。其中,抽象与实现相分离,也就是实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。
19.命令模式:
饭店就餐人数众多时,服务员该如何记清所有菜单呢?这就类似了命令模式。将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
它的优点,第一是能较容易地设计一个命令队列,第二,在需要的情况下,可以较容易地将命令记入日志,第三,允许接受请求的一方决定是否要否决请求;第四,实现对请求的撤销和重做;第五就是对加进类的具体命令类不影响其他的类,因此增加新的具体命令类很容易。
20.职责链模式:
员工的加薪请求,历经了经理,人力资源总监和总经理三个人,涉及此问题的模式就是——职责链模式。
职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,知道有一个对象处理它为止。
在职责链模式中,接受者和发送者都没有对方的明确信息,且链中的对象自己也并不知道链的结构。结果是职责链可简化对象的相互连接,他们仅需保持一个指向其后继者的引用,而不需保持它所有的候选接受者的引用,大大降低了耦合度。
21.中介者模式:
联合国对世界和平的贡献不可估量,在其中,联合国就起到了中介者,也可以说是调停者的作用。
中介者模式,用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
中介者模式一般应用于一组对象以定义良好但是复杂的方式进行通信的场合。
22.享元模式:
如果要做三个产品展示的网站,其中它们本质的代码都是一样的,为了节约服务器的资源,我们可以使用享元模式。
享元模式,运用共享技术有效地支持大量细粒度的对象。享元模式可以避免大量非常相似类的开销。
如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用享元模式;还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很对组对象,此时也可以考虑使用享元模式。
23.解释器模式:
管理者的话语总是前回路转,我们是否能听懂他的弦外之音呢?这时候就谈到了解释器模式。给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
解释器模式需要解决的是,如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
24.访问者模式:
访问者模式表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
它把数据结构和作用于结构上的操作之间的耦合解脱开,是的操作集合可以相对自由地演化。访问者模式的目的就是把处理从数据结构分离出来。
访问者模式对于增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者。访问者模式将有关的行为集中到一个访问者对象中。
最常用的设计模式
设计模式通常是对于某一类的软件设计问题的可重用的解决方案,将设计模式引入软件设计和开发过程,其目的就在于要充分利用已有的软件开发经验。
最常用的设计模式根据我的经验我把我经常用到的设计模式在这里做个总结,按照我的经验,它们的排序如下:1)单件模式、2)抽象工厂模式和工厂模式、3)适配器模式、4)装饰模式、5)观察者模式、6)外观模式 其他模式目前还很少用到。
单件模式
这是用的最多的模式,每一个正式的软件都要用它,全局配置、唯一资源、还有一个就是所有的工厂我都设计为单件模式,因此它的使用量大于工厂模式和抽象工厂模式之和。是用来创建一个需要全局唯一实例的模式。只是需要纠正一点。singleton模式中,构造函数应该是protected.这样子类才可以扩展这个构造函数。
单件模式主要应用在以下场合:
1. 对于一个类,占用的系统资源非常多。而且这些资源可以被全局共享,则可以设计为singleton模式,强迫全局只有一个实例
2. 对于一个类,需要对实例进行计数。可以在createInstance中进行并可以对实例的个数进行限制。
3. 对于一个类,需要对其实例的具体行为进行控制,例如,期望返回的实例实际上是自己子类的实例。这样可以通过Singleton模式,对客户端代码保持透明。
Singleton模式是一个较为简单的模式,下面的代码就可以建立一个Singlton模式的例子,这是一个写系统日志的类,实际应用的意义在于在内存中只保存一个实例,避免开辟多个功能相同的工具类实例而耗用系统资源。当多个应用调用同一个工具类或控制类时特别有意义,建议团队开发时采用。
public class LogWriter
{
//申明一个静态的变量,类型为类本身
private static LogWriter _instance = null;
//将类的构造函数私有化,使得这个类不可以被外界创建
private LogWriter()
{
}
//提供静态的方法,创建类的实例
public static LogWriter GetInstance()
{
if (_instance == null)
{
_instance = new LogWriter();
}
return _instance;
}
}
分析:如果是多线程的情况下该如何保持单件模式?
A: 多线程时应该也是单件的吧,因为线程之间是共享内存的。
请问要是我想限制实例个数不超过10个 应该怎么办呢?
A: 做个类似线程池的东西吧
多线程下面也可以用,可以有很多种方法
1、对静态方法进行同步,2、急切创建实例,3、双重检查加锁
public class Singleton {
private volatile static Singleton singleton ;
private Singleton () {}
public Singleton ( String name ) {}
public static Singleton getInstance () {
if ( singleton == null ) {
synchronized ( Singleton.class ) {
if ( singleton == null ) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
单例模式:单例的实现和上面模式演变中的最后一种很相似,只要把构造器私有便OK。
简单工厂模式
简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。
public class SteamedBread
{
public SteamedBread() // 构造方法
{ }
private double price=0.5;
public double Price
{
get { return price; }
set { price = value; }
}
}
OK,产品对象建立好了,下面就是创建工厂(Factory)对象了。
public class Factory
{
public static SteamedBread CreateInstance() // 创建一个馒头(SteamedBread)对象
{
return new SteamedBread();
}
}
此时,客户端可以这样来调用:
public class Client
{
public static void Main(string[] args)
{
//通过工厂创建一个产品的实例
SteamedBread sb = Factory.CreateInstance();
Console.WriteLine("馒头{0}元一个!", sb.Price);
}
}
工厂模式
public interface IFruit
{
}
public class Orange:IFruit
{
public Orange()
{
Console.WriteLine("An orange is got!");
}
}
public class Apple:IFruit
{
public Apple()
{
Console.WriteLine("An apple is got!");
}
}
我们的FruitFactory应该是怎么样呢?上面的结构图中它给的是CreateProductA,那好,我就MakeOrange,还有一个CreateProductB,俺MakeOrange还不行??
public class FruitFactory
{
public Orange MakeOrange()
{
return new Orange();
}
public Apple MakeApple()
{
return new Apple();
}
}
怎么使用这个工厂呢?我们来写下面的代码:
string FruitName = Console.ReadLine();
IFruit MyFruit = null;
FruitFactory MyFruitFactory = new FruitFactory();
switch (FruitName)
{
case "Orange":
MyFruit = MyFruitFactory.MakeOrange();
break;
case "Apple":
MyFruit = MyFruitFactory.MakeApple();
break;
default:
break;
}
抽象工厂模式
示意性代码如下:
/// <summary>
/// 食品接口----扮演抽象产品角色
/// </summary>
public interface IFood
{
/// <summary>
/// 每种食品都有销售价格,这里应该作为共性提升到父类或是接口来
/// 由于我们只需要得到价格,所以这里就只提供get属性访问器
/// </summary>
double price{get;}
}
------------------------------------------------------------------------------------
/// <summary>
/// 馒头
/// </summary>
public class SteamedBread:IFood
{
/// <summary>
/// 构造方法
/// </summary>
public SteamedBread()
{ }
public double price
{
get
{
return 0.5;
}
}
}
------------------------------------------------------------------------------------
/// <summary>
/// 包子
/// </summary>
public class SteamedStuffed:IFood
{
public SteamedStuffed()
{ }
/// <summary>
/// 销售价格
/// </summary>
public double price
{
get
{
return 0.6; //0.6元一个
}
}
}
------------------------------------------------------------------------------------
/// <summary>
/// 工厂角色
/// </summary>
public class Factory
{
/// <summary>
/// 创建一个馒头(SteamedBread)对象
/// </summary>
/// <returns></returns>
public static IFood CreateInstance(string key)
{
if (key == "馒头")
{
return new SteamedBread();
}
else
{
return new SteamedStuffed();
}
}
}
------------------------------------------------------------------------------------
public class Client
{
public static void Main(string[] args)
{
//通过工厂创建一个产品的实例
IFood food = Factory.CreateInstance("馒头");
Console.WriteLine("馒头{0}元一个!", food.price);
food = Factory.CreateInstance("包子");
Console.WriteLine("包子{0}元一个!", food.price);
}
}
此时的设计就已经完全符合简单工厂模式的意图了。顾客(Client)对早餐店营业员(Factory)说,我要“馒头”,于是营业员便根据顾客所提供的数据(馒头),去众多食品中找,找到了然后就拿给顾客。
其他模式
适配器模式
配器模式有两种类的适配器和对象适配器,对象适配器更多一些,对象适配器的优点在很多大侠的著作了已经论述n次了,我这里不多啰嗦,我用的较多的原因还有一个,我从C++转到C#,由于C#不支持多重继承,我又不比较懒,较少定义interface,因此大多数情况下用C#时也只能使用对象适配器模式了。其实适配器和装饰模式功能上有很大的相似性,在下面的装饰模式中加以论述。
装饰模式
也叫油漆工模式,装饰模式和适配器模式相似都是用来利用现成代码加以调整来满足新的需求,其实采用设计模式的目的之一就是复用,这两个模式正是复用的体现。当你要用这两种模式的时候都是为你现有软件新增新的功能,一般情况下,如果你是让你的软件新增新的功能操作,你一般要用装饰模式,你如果要为软件新增功能支持,你最好选择适配器模式,你如果想为你的类新增操作你用装饰模式,你如果要引入其他来源的现成代码,你用适配器模式。
观察者模式
这个模式我用的多一个原因就是它可以实现事件功能,当然在C#中可以直接使用事件,但是在C++中却是用可以用此模式发挥的淋漓尽致了,网上曾经的一个考题(猫大叫一声,主人醒来,耗子跑开),就是使用这个模式的最好应用。
外观模式
开发就是能复用的地方就复用,这样才能节省资源,提高效率,外观模式也是一个提供复用其他现成代码的解决方案,你需要实现一个功能,可能现成的代码中就有此功能,但是现成代码可能远远多于你所需要的功能,此时你把这些功能封装起来,再重新提供一个你所需要的接口,这就是这个模式的精髓所在。
1.静态成员:
属性,方法和字段是对象实例所特有的,静态成员可看作是类的全局对象。 静态属性和字段可访问独立于任何对象实例的数据,静态方法可执行与对象类型相关而与对象实例无关的命令。使用静态成员时,不需要实例化对象。且不能用实例来调用静态字段,属性,方法。
2.静态类:
只包含静态成员,不能实例化,密封,不需要构造函数的定义
C# 之观察者模式
观察者模式有很多实现方式,从根本上说,该模式必须包含两个角色:观察者和被观察对象。在刚才的例子中,业务数据是被观察对象,用户界面是观察者。观察者和被观察者之间存在“观察”的逻辑关联,当被观察者发生改变的时候,观察者就会观察到这样的变化,并且做出相应的响应。
观察者模式(Observer)完美的将观察者和被观察的对象分离开。举个例子,用户界面可以作为一个观察者,业务数据是被观察者,用户界面观察业务数据的变化,发现数据变化后,就显示在界面上。面向对象设计的一个原则是:系统中的每个类将重点放在某一个功能上,而不是其他方面。一个对象只做一件事情,并且将他做好。观察者模式在模块之间划定了清晰的界限,提高了应用程序的可维护性和重用性。
观察者模式有很多实现方式,从根本上说,该模式必须包含两个角色:观察者和被观察对象。在刚才的例子中,业务数据是被观察对象,用户界面是观察者。观察者和被观察者之间存在“观察”的逻辑关联,当被观察者发生改变的时候,观察者就会观察到这样的变化,并且做出相应的响应。如果在用户界面、业务数据之间使用这样的观察过程,可以确保界面和数据之间划清界限,假定应用程序的需求发生变化,需要修改界面的表现,只需要重新构建一个用户界面,业务数据不需要发生变化。
“观察”不是“直接调用”
实现观察者模式的时候要注意,观察者和被观察对象之间的互动关系不能体现成类之间的直接调用,否则就将使观察者和被观察对象之间紧密的耦合起来,从根本上违反面向对象的设计的原则。无论是观察者“观察”观察对象,还是被观察者将自己的改变“通知”观察者,都不应该直接调用。
实现观察者模式的例子
实现观察者模式有很多形式,比较直观的一种是使用一种“注册——通知——撤销注册”的形式。下面的三个图详细的描述了这样一种过程:
1:观察者(Observer)将自己注册到被观察对象(Subject)中,被观察对象将观察者存放在一个容器(Container)里。
2:被观察对象发生了某种变化(如图中的AskPriceChanged),从容器中得到所有注册过的观察者,将变化通知观察者。
3:观察者告诉被观察者要撤销观察,被观察者从容器中将观察者去除。
观察者将自己注册到被观察者的容器中时,被观察者不应该过问观察者的具体类型,而是应该使用观察者的接口。这样的优点是:假定程序中还有别的观察者,那么只要这个观察者也是相同的接口实现即可。一个被观察者可以对应多个观察者,当被观察者发生变化的时候,他可以将消息一一通知给所有的观察者。基于接口,而不是具体的实现——这一点为程序提供了更大的灵活性。
下面代码是使用C#实现观察者模式的例子:
//“观察者”接口
public interface IObserver {
void Notify(object anObject);
}
//“被观察对象”接口
public interface IObservable {
void Register(IObserver anObserver);
void UnRegister(IObserver anObserver);
}
观察者和被观察对象都分别从这两个接口实现,所有的操作都是由这两个接口定义的,而不是具体的实现。所以观察者和被观察对象没有绑定在一起。我们可以方便的更改观察者和被观察对象的任意部分而不影响其他部分。
下面实现具体的被观察对象。下面的类是所有被观察对象的基类,实现了所有被观察对象都必须的方法。我们使用一个Hashtable作为观察者的容器。代码如下:
//所有被观察对象的基类
public class ObservableImpl : IObservable {
//保存观察对象的容器
protected Hashtable _observerContainer = new Hashtable();
//注册观察者
public void Register(IObserver anObserver){
_observerContainer.Add(anObserver,anObserver);
}
//撤销注册
public void UnRegister(IObserver anObserver){
_observerContainer.Remove(anObserver);
}
//将事件通知观察者
public void NotifyObservers(object anObject) {
//枚举容器中的观察者,将事件一一通知给他们
foreach(IObserver anObserver in _observerContainer.Keys) {
anObserver.Notify(anObject);
}
}
}
上面的类不是最终要实现的被观察对象,而是所有被观察者的基类,其中实现了所有观察对象共有的功能。这个类可以干脆定义为abstract,使得程序员不可以创建其实例。接口以及实现这个接口的虚类既保持了类之间松散的耦合,又使多个具体实现可以使用相同的功能。
下面最终实现观察者模式,使用用户界面——业务数据作为例子:
//业务数据(被观察对象)
public class SomeData : ObservableImpl {
//被观察者中的数据
float _askPrice;
//改变数据的属性
public float AskPrice {
set {
_askPrice = value;
base.NotifyObservers(_askPrice);//将改变的消息通知观察者
}
}
}
//用户界面(观察者)
public class SomeKindOfUI : IObserver {
public void Notify(object anObject){
Console.WriteLine("The new ask price is:" + anObject);
}
}
//实际调用的过程
public class MainClass{
public static void Main() {
//创建观察者和被观察者
SomeKindOfUI ui = new SomeKindOfUI();
SomeData data = new SomeData();
//在被观察对象中注册观察者
data.Register(ui);
//改变被观察对象中的数据,这时被观察者会通知观察者
data.AskPrice = 1000f;
//注销观察者,停止观察
stock.UnRegister(stockDisplay);
}
}
.NET中更好的实现方式
上面的形式是我们用一种最基本的方式实现了观察者模式,我们为观察者模式开发了一种特定的类型。在.NET框架中,使用代理以及事件,可以更好的实现观察者模式。C#中代理和事件的介绍可以看这一篇文章:在C#中使用代理的方式触发事件,里面有对代理和事件的详细描述,还有例程,这里就不多说了。在.NET支持的其他语言中也有各自的实现方式。
在事件的模式下,声明事件的类就是被观察者。被观察者不需要实现对观察者的注册,只需要公开一个事件,而不实行任何操作。被观察者也不需要将自己注册到观察对象中,而是要创建一个特定的代理的实例,将这个代理绑定到某个方法上。用这样的方式注册或者撤销观察者对观察对象的观察。仔细研究代理和事件的模式就不难发现,IObserver和IObservable接口的方法可以减少观察者和观察对象之间的耦合,而代理和事件几乎消除了这两个模块之间的耦合,灵活性提高了很多。
C#事件委托和观察者模式之比较
看了C#的事件,发觉跟学java时见到的观察者模式相似,网上搜了一下,有总结的帖子,转载如下:
using System.Collections;
namespace MyCollections
{
//-----------------------------------------------------------------------------
//该委托定义相当于观察者模式中的 Notify()函数,
//用来通知观察者关于subject 的变化
//通知观察者,观察者会自动执行观察者内的Update(),Update()方法会暴露给subjects.
// A delegate type for hooking up change notifications.
public delegate void ChangedEventHandler(object sender, EventArgs e);
//------------------------------------------------------------------------------
//该类相当于观察者模式中的subjects.
// A class that works just like ArrayList, but sends event
// notifications whenever the list changes.
public class ListWithChangedEvent: ArrayList
{
// An event that clients can use to be notified whenever the
// elements of the list change.
public event ChangedEventHandler Changed; //事件相当于实做Notify()函数
// Invoke the Changed event; called whenever list changes
protected virtual void OnChanged(EventArgs e)
{
if (Changed != null) //这里表示subjects状态有变化,需要通知observers.
Changed(this, e);
}
// Override some of the methods that can change the list;
// invoke event after each
public override int Add(object value)
{
int i = base.Add(value);
OnChanged(EventArgs.Empty);
return i;
}
public override void Clear()
{
base.Clear();
OnChanged(EventArgs.Empty);
}
public override object this[int index]
{
set
{
base[index] = value;
OnChanged(EventArgs.Empty);
}
}
}
}
using MyCollections;
namespace TestEvents
{
//侦听者类似于观察者模式中的observers
class EventListener
{
private ListWithChangedEvent List;
public EventListener(ListWithChangedEvent list)
{
List = list;
// Add "ListChanged" to the Changed event on "List".
List.Changed += new ChangedEventHandler(ListChanged);//这里类似于AddObserver();
}
// This will be called whenever the list changes.
private void ListChanged(object sender, EventArgs e)
{
Console.WriteLine("This is called when the event fires."); //这里类似于观察者暴露给subjects 的Update()方法
Thread.Sleep(5000);
}
public void Detach()
{
// Detach the event and delete the list
List.Changed -= new ChangedEventHandler(ListChanged);//这里类似于RemoveObserver();
List = null;
}
}
class Test
{
// Test the ListWithChangedEvent class.
public static void Main()
{
// Create a new list.
ListWithChangedEvent list = new ListWithChangedEvent(); //创建一个subject(observable)
// Create a class that listens to the list's change event.
EventListener listener = new EventListener(list); //创建一个观察者
// Add and remove items from the list.
list.Add("item 1"); //subject 的状态发生变化,通知观察者作相应的动作 ListChanged()
list.Clear(); //同上
listener.Detach();//删除观察者
}
}
}