??xml version="1.0" encoding="utf-8" standalone="yes"?> 1. 前言 单例(Singleton)应该是开发者们最熟悉的设计模式了Qƈ且好像也是最Ҏ实现?#8212;—基本上每个开发者都能够随手写出——但是Q真的是q样吗? 作ؓ一个Java开发者,也许你觉得自己对单例模式的了解已l够多了。我q不惛_a耸听说一定还有你不知道的——毕竟我自q了解也的有限,但究竟你自己了解的程度到底怎样呢?往下看Q我们一h聊聊看~ 2. 什么是单例Q?/p> 单例对象的类必须保证只有一个实例存?#8212;—q是l基癄上对单例的定义,q也可以作ؓҎ囑֮现单例模式的代码q行验的标准?/p> 对单例的实现可以分ؓ两大c?#8212;—懒汉式和饿汉式,他们的区别在于: 懒汉式:指全局的单例实例在W一ơ被使用时构建?/p> 饿汉式:指全局的单例实例在c装载时构徏?/p> 从它们的区别也能看出来,日常我们使用的较多的应该是懒汉式的单例,毕竟按需加蝲才能做到资源的最大化利用嘛~ 3. 懒汉式单?/p> 先来看一下懒汉式单例的实现方式?/p> 3.1 单版?/p> 看最单的写法Version 1Q?/p> 或者再q一步,把构造器改ؓU有的,q样能够防止被外部的c调用?/p> 我仿佛记得当初学校的教科书就是这么教的?—— 每次获取instance之前先进行判断,如果instance为空new一个出来,否则q接返回已存在的instance?/p> q种写法在大多数的时候也是没问题的。问题在于,当多U程工作的时候,如果有多个线E同时运行到if (instance == null)Q都判断为nullQ那么两个线E就各自会创Z个实?#8212;—q样一来,׃是单例了?/p> 3.2 synchronized版本 那既然可能会因ؓ多线E导致问题,那么加上一个同步锁吧! 修改后的代码如下Q相对于Version1.1Q只是在Ҏ{上多加了一个synchronizedQ?/p> OKQ加上synchronized关键字之后,getInstanceҎ׃锁上了。如果有两个U程QT1、T2Q同时执行到q个ҎӞ会有其中一个线ET1获得同步锁,得以l箋执行Q而另一个线ET2则需要等待,当第T1执行完毕getInstance之后Q完成了null判断、对象创建、获得返回g后)QT2U程才会执行执行?#8212;—所以这端代码也避免了Version1中,可能出现因ؓ多线E导致多个实例的情况?/p> 但是Q这U写法也有一个问题:lgitInstanceҎ加锁Q虽然会避免了可能会出现的多个实例问题,但是会强刉T1之外的所有线E等待,实际上会对程序的执行效率造成负面影响?/p> 3.3 双重查(Double-CheckQ版?/p> Version2代码相对于Version1d代码的效率问题,其实是ؓ了解?%几率的问题,而用了一?00%出现的防护盾。那有一个优化的思\Q就是把100%出现的防护盾Q也改ؓ1%的几率出玎ͼ使之只出现在可能会导致多个实例出现的地方?/p> ——有没有这LҎ呢?当然是有的,改进后的代码Vsersion3如下Q?/p> q个版本的代码看h有点复杂Q注意其中有两次if (instance == null)的判断,q个叫做『双重检?Double-Check』?/p> W一个if (instance == null)Q其实是Z解决Version2中的效率问题Q只有instance为null的时候,才进入synchronized的代码段——大大减少了几率?/p> W二个if (instance == null)Q则是跟Version2一P是ؓ了防止可能出现多个实例的情况?/p> —— q段代码看v来已l完无瑕了?/p> …… …… …… —— 当然Q只是『看h』,q是有小概率出现问题的?/p> q弄清楚Z么这里可能出现问题,首先Q我们需要弄清楚几个概念Q原子操作、指令重排?/p> 知识点:什么是原子操作Q?/p> 单来_原子操作QatomicQ就是不可分割的操作Q在计算ZQ就是指不会因ؓU程调度被打断的操作?/p> 比如Q简单的赋值是一个原子操作: 假如m原先的gؓ0Q那么对于这个操作,要么执行成功m变成?Q要么是没执行mq是0Q而不会出现诸如m=3q种中间?#8212;—即是在q发的线E中?/p> 而,声明q赋值就不是一个原子操作: 对于q个语句Q至有两个操作Q?/p> ①声明一个变量n ②ln赋gؓ6 ——q样׃有一个中间状态:变量n已经被声明了但是q没有被赋值的状态?/p> ——q样Q在多线E中Q由于线E执行顺序的不确定性,如果两个U程都用mQ就可能会导致不E_的结果出现?/p> 知识点:什么是指o重排Q?/p> 单来_是计算Zؓ了提高执行效率,会做的一些优化,在不影响最l结果的情况下,可能会对一些语句的执行序q行调整?/p> 比如Q这一D代码: 正常来说Q对于顺序结构,执行的顺序是自上CQ也?234?/p> 但是Q由于指令重排的原因Q因Z影响最l的l果Q所以,实际执行的顺序可能会变成3124或?324?/p> ׃语句3?没有原子性的问题Q语?和语?也可能会拆分成原子操作,再重排?/p> ——也就是说Q对于非原子性的操作Q在不媄响最l结果的情况下,其拆分成的原子操作可能会被重新排列执行顺序?/p> OKQ了解了原子操作和指令重排的概念之后Q我们再l箋看Version3代码的问题?/p> 下面q段话直接从陈皓的文?深入出单实例SINGLETON设计模式)中复制而来Q?/p> 主要在于singleton = new Singleton()q句Q这q是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情?/p> 1. l?singleton 分配内存 2. 调用 Singleton 的构造函数来初始化成员变量,形成实例 3. singleton对象指向分配的内存空_执行完这?singleton才是?null 了) 但是?JVM 的即时编译器中存在指令重排序的优化。也是说上面的W二步和W三步的序是不能保证的Q最l的执行序可能?1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕? 未执行之前,被线E二抢占了,q时 instance 已经是非 null 了(但却没有初始化)Q所以线E二会直接返?instanceQ然后用,然后理成章地报错?/p> 再稍微解释一下,是_׃有一个『instance已经不ؓnull但是仍没有完成初始化』的中间状态,而这个时候,如果有其他线E刚好运行到W一层if (instance == null)q里Q这里读取到的instance已经不ؓnull了,所以就直接把这个中间状态的instance拿去用了Q就会生问题?/p> q里的关键在?#8212;—U程T1对instance的写操作没有完成Q线ET2执行了L作?/p> 3.4 l极版本Qvolatile 对于Version3中可能出现的问题Q当然这U概率已l非常小了,但毕竟还是有的嘛~Q,解决Ҏ是:只需要给instance的声明加上volatile关键字即可,Version4版本Q?/p> volatile关键字的一个作用是止指o重排Q把instance声明为volatile之后Q对它的写操作就会有一个内存屏障(什么是内存屏障Q)Q这P在它的赋值完成之前,׃用会调用L作?/p> 注意QvolatileL的不singleton = new Singleton()q句话内部[1-2-3]的指令重排,而是保证了在一个写操作Q[1-2-3]Q完成之前,不会调用L作(if (instance == null)Q?/p> ——也就d防止了Version3中的问题发生?/p> ——好了Q现在彻底没什么问题了吧? …… …… …… 好了Q别紧张Q的没问题了。大名鼎鼎的EventBus中,其入口方法EventBus.getDefault()是用这U方法来实现的?/p> …… …… …… 不过Q非要挑点刺的话q是能挑出来的,是q个写法有些复杂了,不够优雅、简z?/p> 4. 饿汉式单?/p> 下面再聊了解一下饿汉式的单例?/p> 如上所_饿汉式单例是指:指全局的单例实例在c装载时构徏的实现方式?/p> ׃c装载的q程是由cd载器QClassLoaderQ来执行的,q个q程也是由JVM来保证同步的Q所以这U方式先天就有一个优?#8212;—能够免疫许多由多U程引v的问题?/p> 4.1 饿汉式单例的实现方式 饿汉式单例的实现如下Q?/p> 对于一个饿汉式单例的写法来_它基本上是完的了?/p> 所以它的缺点也只是饿汉式单例本n的缺Ҏ在了——׃INSTANCE的初始化是在cd载时q行的,而类的加载是由ClassLoader来做的,所以开发者本来对于它初始化的时机很隑֎准确把握Q?/p> 可能׃初始化的太早Q造成资源的浪?/p> 如果初始化本w依赖于一些其他数据,那么也就很难保证其他数据会在它初始化之前准备好?/p> 当然Q如果所需的单例占用的资源很少Qƈ且也不依赖于其他数据Q那么这U实现方式也是很好的?/p> 知识点:什么时候是c装载时Q?/p> 前面提到了单例在c装载时被实例化Q那I竟什么时候才是『类装蝲时』呢Q?/p> 不严格的_大致有这么几个条件会触发一个类被加载: 1. new一个对象时 2. 使用反射创徏它的实例?/p> 3. 子类被加载时Q如果父c还没被加蝲Q就先加载父c?/p> 4. jvm启动时执行的ȝ会首先被加蝲 5. 一些其他的实现方式 5.1 Effective Java 1 —— 静态内部类 《Effective Java》一书的W一版中推荐了一个中写法Q?/p> q种写法非常巧妙Q?/p> 对于内部cSingletonHolderQ它是一个饿汉式的单例实玎ͼ在SingletonHolder初始化的时候会由ClassLoader来保证同步,使INSTANCE是一个真·单例?/p> 同时Q由于SingletonHolder是一个内部类Q只在外部类的Singleton的getInstance()中被使用Q所以它被加载的时机也就是在getInstance()ҎW一ơ被调用的时候?/p> ——它利用了ClassLoader来保证了同步Q同时又能让开发者控制类加蝲的时机。从内部看是一个饿汉式的单例,但是从外部看来,又的是懒汉式的实现?/p> 直是乎其技?/p> 5.2 Effective Java 2 —— 枚D 你以为到q就完了?不,q没有,因ؓ厉害的大又发现了其他的Ҏ?/p> 《Effective Java》的作者在q本书的W二版又推荐了另外一U方法,来直接看代码Q?/p> 看到了么Q这是一个枚丄?#8230;…qclass都不用了Q极?/p> ׃创徏枚D实例的过E是U程安全的,所以这U写法也没有同步的问题?/p> 作者对q个Ҏ的评P q种写法在功能上与共有域Ҏ相近Q但是它更简z,无偿地提供了序列化机Ӟl对防止Ҏ实例化,即是在面对复杂的序列化或者反攻ȝ时候。虽然这中方法还没有q泛采用Q但是单元素的枚丄型已l成为实现Singleton的最x法?/p> 枚D单例q种Ҏ问世一些,许多分析文章都称它是实现单例的最完美Ҏ——写法单,而且又能解决大部分的问题?/p> 不过我个U方法虽然很优秀Q但是它仍然不是完美?#8212;—比如Q在需要承的场景Q它׃适用了?/p> 6. ȝ OKQ看到这里,你还会觉得单例模式是最单的设计模式了么Q再回头看一下你之前代码中的单例实现Q觉得是无懈可击的么Q?/p> 可能我们在实际的开发中Q对单例的实现ƈ没有那么严格的要求。比如,我如果能保证所有的getInstance都是在一个线E的话,那其实第一U最单的教科书方式就够用了。再比如Q有时候,我的单例变成了多例也可能对程序没什么太大媄?#8230;… 但是Q如果我们能了解更多其中的细节,那么如果哪天E序Z些问题,我们L能多一个排查问题的炏V早点解决问题,p早点回家吃饭…… —— q有Q完的Ҏ是不存在QQ何方式都会有一个『度』的问题。比如,你的觉得代码已经无懈可击了,但是因ؓ你用的是JAVA语言Q可能ClassLoader有些BUG?#8230;…你的代码谁运行在JVM上的Q可能JVM本n有BUG?#8230;…你的代码q行在手ZQ可能手机系l有问题?#8230;…你生zdq个宇宙里,可能宇宙本n有些BUG?#8230;… 所以,力做到能做到的最好就行了?/p> —— 感谢你花费了不少旉看到q里Q但愿你没有觉得虚度?/p>// Version 1 public class Single1 { private static Single1 instance; public static Single1 getInstance() { if (instance == null) { instance = new Single1(); } return instance; } }
// Version 1.1 public class Single1 { private static Single1 instance; private Single1() {} public static Single1 getInstance() { if (instance == null) { instance = new Single1(); } return instance; } }
// Version 2 public class Single2 { private static Single2 instance; private Single2() {} public static synchronized Single2 getInstance() { if (instance == null) { instance = new Single2(); } return instance; } }
// Version 3 public class Single3 { private static Single3 instance; private Single3() {} public static Single3 getInstance() { if (instance == null) { synchronized (Single3.class) { if (instance == null) { instance = new Single3(); } } } return instance; } }
m = 6; // q是个原子操?/span>
int n = 6; // q不是一个原子操?/span>
// Version 4 public class Single4 { private static volatile Single4 instance; private Single4() {} public static Single4 getInstance() { if (instance == null) { synchronized (Single4.class) { if (instance == null) { instance = new Single4(); } } } return instance; } }
//饿汉式实?/span> public class SingleB { private static final SingleB INSTANCE = new SingleB(); private SingleB() {} public static SingleB getInstance() { return INSTANCE; } }
// Effective Java W一版推荐写?/span> public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
// Effective Java W二版推荐写?/span> public enum SingleInstance { INSTANCE; public void fun1() { // do something } } // 使用 SingleInstance.INSTANCE.fun1();
]]>
该模式是对下面的代码改进Q?/p>
q段代码的目的是?resource 延迟初始化。但是每ơ访问的时候都需要同步。ؓ了减同步的开销Q于是有了双重检查模式?/p>
?Java 中双重检查模式无效的原因是在不同步的情况下引用类型不是线E安全的。对于除?long ?double 的基本类型,双重查模式是适用 的。比如下面这D代码就是正的Q?/p>
上面是关于java中双重检查模式(double-check idiomQ的一般结论。但是事情还没有l束Q因为java的内存模式也在改q中。Doug Lea 在他的文章中写道Q?#8220;Ҏ最新的 JSR133 ?Java 内存模型Q如果将引用cd声明?volatileQ双重检查模式就可以工作?#8221;Q参?nbsp;http://gee.cs.oswego.edu/dl/cpj/updates.html ?/p>
所以以后要?Java 中用双重检查模式,可以使用下面的代码:
当然了,得是在遵?JSR133 规范?Java 中?/p>
所以,double-check ?J2SE 1.4 或早期版本在多线E或?JVM 调优时由?out-of-order writesQ是不可用的?q个问题?J2SE 5.0 中已l被修复Q可以?volatile 关键字来保证多线E下的单例?/p>
推荐Ҏ 是Initialization on Demand HolderQIODHQ,
详见 http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom
1. 概述
有时被称作发?订阅模式Q观察者模式定义了一U一对多的依赖关p,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时Q会通知所有观察者对象,使它们能够自动更新自己?/p>
2. 解决的问?/p>
一个系l分割成一个一些类怺协作的类有一个不好的副作用,那就是需要维护相兛_象间的一致性。我们不希望Zl持一致性而各类紧密耦合Q这样会l维护、扩展和重用都带来不ѝ观察者就是解册cȝ耦合关系的?/p>
3. 模式中的角色
3.1 抽象主题QSubjectQ:它把所有观察者对象的引用保存C个聚集里Q每个主题都可以有Q何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象?/p>
3.2 具体主题QConcreteSubjectQ:有关状态存入具体观察者对象;在具体主题内部状态改变时Q给所有登记过的观察者发出通知?/p>
3.3 抽象观察者(ObserverQ:为所有的具体观察者定义一个接口,在得C题通知时更新自己?/p>
3.4 具体观察者(ConcreteObserverQ:实现抽象观察者角色所要求的更新接口,以便使本w的状态与主题状态协调?/p>
4. 模式解读
4.1 观察者模式的cd
4.2 观察者模式的代码
/// <summary> /// 抽象主题c? /// </summary> public abstract class Subject { private IList<Observer> observers = new List<Observer>(); /// <summary> /// 增加观察? /// </summary> /// <param name="observer"></param> public void Attach(Observer observer) { observers.Add(observer); } /// <summary> /// U除观察? /// </summary> /// <param name="observer"></param> public void Detach(Observer observer) { observers.Remove(observer); } /// <summary> /// 向观察者(们)发出通知 /// </summary> public void Notify() { foreach (Observer o in observers) { o.Update(); } } } /// <summary> /// 抽象观察者类Qؓ所有具体观察者定义一个接口,在得到通知时更新自? /// </summary> public abstract class Observer { public abstract void Update(); } /// <summary> /// 具体观察者或具体通知者,有关状态存入具体观察者对象;在具体主题的内部状态改变时Q给所有登记过的观察者发出通知。具体主题角色通常用一个具体子cd现? /// </summary> public class ConcreteSubject : Subject { private string subjectState; /// <summary> /// 具体观察者的状? /// </summary> public string SubjectState { get { return subjectState; } set { subjectState = value; } } } /// <summary> /// 具体观察者,实现抽象观察者角色所要求的更新接口,已是本n状态与主题状态相协调 /// </summary> public class ConcreteObserver : Observer { private string observerState; private string name; private ConcreteSubject subject; /// <summary> /// 具体观察者用一个具体主题来实现 /// </summary> public ConcreteSubject Subject { get { return subject; } set { subject = value; } } public ConcreteObserver(ConcreteSubject subject, string name) { this.subject = subject; this.name = name; } /// <summary> /// 实现抽象观察者中的更新操? /// </summary> public override void Update() { observerState = subject.SubjectState; Console.WriteLine("The observer's state of {0} is {1}", name, observerState); } }
4.3 客户端代?/p>
class Program { static void Main(string[] args) { // 具体主题角色通常用具体自来来实现 ConcreteSubject subject = new ConcreteSubject(); subject.Attach(new ConcreteObserver(subject, "Observer A")); subject.Attach(new ConcreteObserver(subject, "Observer B")); subject.Attach(new ConcreteObserver(subject, "Observer C")); subject.SubjectState = "Ready"; subject.Notify(); Console.Read(); } }
q行l果
5. 模式ȝ
5.1 优点
5.1.1 观察者模式解除了主题和具体观察者的耦合Q让耦合的双斚w依赖于抽象,而不是依赖具体。从而得各自的变化都不会媄响另一边的变化?/p>
5.2 ~点
5.2.1 依赖关系q未完全解除Q抽象通知者依旧依赖抽象的观察者?/p>
5.3 适用场景
5.3.1 当一个对象的改变需要给变其它对象时Q而且它不知道具体有多个对象有待改变时?/p>
5.3.2 一个抽象某型有两个斚wQ当其中一个方面依赖于另一个方面,q时用观察者模式可以将q两者封装在独立的对象中使它们各自独立地改变和复用?/p>
6. 模式引申Q应用C#中的事g委托来彻底解除通知者和观察者之间的耦合?/p>
6.1 关于委托的定义:委托是一U引用方法的cd。一旦ؓ委托分配了方法,委托与该方法有相同的行为。委托方法可以像其它MҎ一Ph参数和返回倹{委托可以看作是对函敎ͼҎQ的的抽象,是函数的“c?#8221;Q委托的实例代表一个(或多个)具体的函敎ͼ它可以是多播的?/p>
6.2 关于事gQ事件基于委托,为委托提供了一U发?订阅机制。事件的订阅与取消与我们刚才讲的观察者模式中的订阅与取消cMQ只是表现Ş式有所不同。在观察者模式中Q订阅用方法AttachQ)来进行;在事件的订阅中?#8220;+=”。类似地Q取消订阅在观察者模式中用DettachQ)Q而事件的取消?#8220;-=”?/p>
7. 下面例子分别用观察者模式,事g机制来实?/p>
7.1 实例描述Q客h付了订单NQ这时胦务需要开具发,出纳需要记账,配送员需要配货?/p>
7.2 观察者模式的实现
7.2.1 cd
7.2.2 代码实现
/// <summary> /// 抽象观察? /// </summary> public interface ISubject { void Notify(); } /// <summary> /// 工作岗位Q作里的观察者的抽象 /// </summary> public abstract class JobStation { public abstract void Update(); } /// <summary> /// 具体主题Q这里是客户 /// </summary> public class Customer : ISubject { private string customerState; private IList<JobStation> observers = new List<JobStation>(); /// <summary> /// 增加观察? /// </summary> /// <param name="observer"></param> public void Attach(JobStation observer) { this.observers.Add(observer); } /// <summary> /// U除观察? /// </summary> /// <param name="observer"></param> public void Detach(JobStation observer) { this.observers.Remove(observer); } /// <summary> /// 客户状? /// </summary> public string CustomerState { get { return customerState; } set { customerState = value; } } public void Notify() { foreach (JobStation o in observers) { o.Update(); } } } /// <summary> /// 会计 /// </summary> public class Accountant : JobStation { private string accountantState; private Customer customer; public Accountant(Customer customer) { this.customer = customer; } /// <summary> /// 更新状? /// </summary> public override void Update() { if (customer.CustomerState == "已付?/span>") { Console.WriteLine("我是会计Q我来开具发?/span>"); accountantState = "已开发票"; } } } /// <summary> /// 出纳 /// </summary> public class Cashier : JobStation { private string cashierState; private Customer customer; public Cashier(Customer customer) { this.customer = customer; } public override void Update() { if (customer.CustomerState == "已付?/span>") { Console.WriteLine("我是出纳员,我给登记入̎?/span>"); cashierState = "已入?/span>"; } } } /// <summary> /// 配送员 /// </summary> public class Dilliveryman : JobStation { private string dillivierymanState; private Customer customer; public Dilliveryman(Customer customer) { this.customer = customer; } public override void Update() { if (customer.CustomerState == "已付?/span>") { Console.WriteLine("我是配送员Q我来发货?/span>"); dillivierymanState = "已发?/span>"; } } }
7.2.3 客户端代?/p>
class Program { static void Main(string[] args) { Customer subject = new Customer(); subject.Attach(new Accountant(subject)); subject.Attach(new Cashier(subject)); subject.Attach(new Dilliveryman(subject)); subject.CustomerState = "已付?/span>"; subject.Notify(); Console.Read(); } }
q行l果Q?/p>
我是会计Q我来开具发?/span>
我是出纳员,我给登记入̎?/span>
我是配送员Q我来发货?/span>
7.3 事g实现
7.3.1 cd
通过cd来看Q观察者和主题之间已经不存在Q何依赖关pM?/p>
7.3.2 代码实现
/// <summary> /// 抽象主题 /// </summary> public interface ISubject { void Notify(); } /// <summary> /// 声明委托 /// </summary> public delegate void CustomerEventHandler(); /// <summary> /// 具体主题 /// </summary> public class Customer : ISubject { private string customerState; // 声明一个委托事Ӟcd?CustomerEventHandler public event CustomerEventHandler Update; public void Notify() { if (Update != null) { // 使用事g来通知l订阅?/span> Update(); } } public string CustomerState { get { return customerState; } set { customerState = value; } } } /// <summary> /// 财务Q已l不需要实现抽象的观察者类Qƈ且不用引用具体的主题 /// </summary> public class Accountant { private string accountantState; public Accountant() { } /// <summary> /// 开发票 /// </summary> public void GiveInvoice() { Console.WriteLine("我是会计Q我来开具发?/span>"); accountantState = "已开发票"; } } /// <summary> /// 出纳Q已l不需要实现抽象的观察者类Qƈ且不用引用具体的主题 /// </summary> public class Cashier { private string cashierState; public void Recoded() { Console.WriteLine("我是出纳员,我给登记入̎?/span>"); cashierState = "已入?/span>"; } } /// <summary> /// 配送员Q已l不需要实现抽象的观察者类Qƈ且不用引用具体的主题 /// </summary> public class Dilliveryman { private string dillivierymanState; public void Dilliver() { Console.WriteLine("我是配送员Q我来发货?/span>"); dillivierymanState = "已发?/span>"; } }
7.3.3 客户端代?/p>
class Program { static void Main(string[] args) { Customer subject = new Customer(); Accountant accountant = new Accountant(); Cashier cashier = new Cashier(); Dilliveryman dilliveryman = new Dilliveryman(); // 注册事g subject.Update += accountant.GiveInvoice; subject.Update += cashier.Recoded; subject.Update += dilliveryman.Dilliver; /* * 以上写法也可以用下面代码来替? subject.Update += new CustomerEventHandler(accountant.GiveInvoice); subject.Update += new CustomerEventHandler(cashier.Recoded); subject.Update += new CustomerEventHandler(dilliveryman.Dilliver); */ subject.CustomerState = "已付?/span>"; subject.Notify(); Console.Read(); } }
q行l果
我是会计Q我来开具发?/span>
我是出纳员,我给登记入̎?/span>
我是配送员Q我来发货?/span>