Pattern 4

《C#设计模式》
备忘录模式:可以通过特权访问要保存对象的状态,采用某些语言解决这一问题。其他对象对该对象值具有比较受限的访问权限,从而保持了他们的封装特性。
对象通常不应该用公有方法暴露态多的内部状态,但是因为以后可能要恢复对象,还是希望能保存它的整个状态。在某些情况下,可以从公有接口(例如,图形对象的绘图位置)获得足够的信息来保存和恢复数据,而在另外一些情况下,需要保存颜色、明暗、角度以及其他对象的连接关系,这些信息不容易获得。这种需要保存和恢复的信息在支持Undo命令的系统中是很常见的。
如果公有变量里可以得到一个对象的所有描述信息,那么将这些信息以某种形式存储起来并不困难。然而,使这些数据公有化会使整个系统容易受外部代码的改变而破坏,而通常希望对象内部的数据是私有的,与外界相隔离。
 
备忘录模式为对象定义了三个角色:
1、发起人(Originator)是一个对象,我们要保存它的状态。
2、备忘录(Memento)是另外一个对象,它保存了发起人的状态。
3、负责人(Caretaker)管理状态保存的时机,保存备忘录,并且如果需要的话,使用备忘录恢复发起人的状态。
 
 
《C#设计模式》
观察者模式:在新奇而复杂的窗口世界里,经常希望能够同时以多种形式显示数据,并且所有的现实都能反映出数据的变化。例如,用图形、表格或列表框表示股票价格的变化,每次价格发生变化时,所有的表示形式应该能同时改变,而不需要做任何操作。我们可以通过观察者模式实现这种功能。
观察者模式假定,包含数据的对象和显示数据的对象相隔离,这些显示数据要“观察”数据的变化。
我们实现观察者模式时,通常把数据称为目标(subject),把每种显示称为一个观察者(observer)。每个观察者通过调用目标中的公有方法注册它所感兴趣的数据。每个观察者中都有一个一致的接口,目标在数据发生变化时会调用它。我们可以将目标中的公有方法提出放在接口中:
   public interface ISubject { void RegisterInterest(IObserver obs);}
而IObserver接口定义为:
   public interface IObserver { void SendNotify(string message);}
    观察者模式的效果:Observer促进了目标的抽象耦合,目标不知道任何一个观察者的消息内容。但这也具由潜在缺点。当目标中的数据发生了一些列的递增变化时,要持续或反复地更新观察者。如果更新的代价很高,就必须改进管理更新的策略。当一个客户(观察者)对底层数据做了修改,你要决定由哪一个对象去触发发送给其他观察者的更新通知。如果是由目标在它被更改后去通知所有的观察者,每个客户就不需要记住去触发通知。但另一方面,这会导致多个连续的更新被触发。如果由客户告诉目标何时去通知其它的客户,会避免这种连续的通知。但是客户增加了告诉目标何时发送通知的责任。如果一个客户“忘记”了,程序将不能正常运行。
最后,依据所发生的变化的类型或范围,为观察者定义多个接受通知的更新方法,依此指出要选择发送的通知类型,在有些情况下,客户能由此忽略掉一些更新通知。
在比较复杂的系统里,观察者可能要求各种不同类型的数据。我们不是让各个观察者把消息转换成正确的数据类型,而是用一个中间的Adapter类型完成这种转换工作。
观察者需要处理的另一个问题是目标类的数据能以几种方式改变的情况。例如,可以从一个数据列表中删除若干个接点,编辑它们的数据或改变我们观测的数据的范围,在这些情况下,或者把各种不同的改变消息都发送个观察者,或只发送一个消息,然后让观察者询问发生的是哪一种改变。
    // “目标”用一数组记录对该目标感兴趣的所有观察者
    // 当目标更新时,调用目标对象的DataChanges方法通知所有的观察者
    //DataChanges 是通过遍历观察者数组并调用观察者类对象的SendNotify()方法实现的
    // 另外我们还可以通过其它方式实现更新,如通过职责链让观察者传播目标的更新
    public class MySubject : ISubject
    {
        ArrayList observers = new ArrayList();
        public void RegisterInterest(IObserver obs) { observers.Add(obs); }
        void DataChanges()
        {
            IEnumerator e = observers.GetEnumerator();
            while (e.MoveNext())
            { (IObserver)e.current.SendNotify(args); }
        }
    }
    // 观察者通过SendNotify方法响应目标的更新
    public class MyObserver : IObserver
    {
        public void SendNotify(params object[] args) { }
}
 
《Thinking in Pattern
和其它形式的回调函数(callback)类似,Observer模式允许你通过挂钩程序(hook point)改变代码。不同之处在于,从本质上说,Observer模式是完全动态的。它经常被用于需要根据其它对象的状态变化来改变自身(状态)的场合,而且它还经常是事件管理系统(event management)的基本组成部分。无论什么时候,当你需要用完全动态的方式分离呼叫源和被呼叫代码的时候,Observer模式都是你的首选。
Observer模式解决的是一个相当常见的问题:当某个对象改变状态的时候,另外一组(与之相关的)对象如何更新它们自己。比如说,Smalltalk里的“model-view”结构,(它是MVC(model-view-controller)结构的一部分),再比如基本与之相当的“文档-视图(document-view)”结构。假设说你有一些数据(也就是“文档”)和多于一个的视图,比如说是一个图表(plot)和一个文本视图。当你改变数据的时候,这两个视图必须知道进而(根据需要)更新它们自己,这也就是Observer模式所要帮你解决的问题。
实现observer模式需要用到两种类型的对象,Observable类负责记住发生变化时需要通知哪些类,而不论“状态”改变与否。当被观察对象说“OK,你们(指观察者)可以根据需要更新你们自己了,”Observable类通过调用notifyObservers( )方法通知列表上的每个观察者,进而完成这个任务。notifyObservers( )是基类Observable的一个方法。 实际上,Observer模式真正变化的有两样东西:观察者(observing objects)的数量和它们如何更新自己。也就是说,observer模式使得你在不必改动其它代码的情况下只针对这两种变化更改代码。
Observer实际上是只有一个成员函数的接口类,这个成员函数就是update( )。当被观察者决定更新所有的观察者的时候,它就调用update( )函数。是否需要传递参数是可选的;即使是没有参数的Update( )函数也同样符合observer模式;但是,更通常的做法是让被观察者(通过update( )函数)把引起更新的对象(也就是它自己 )和其它有用的信息传递给观察者, 因为一个观察者可能会注册到多于一个的被观察者。这样,观察者对象就不用再费劲查找是哪个被观察者引起的更新,并且它所需要的信息也已经传递过来。 决定何时以及如何发起更新(updating)的那个“被观察者对象”被命名为Observable。
Observable类用一个标志(flag)来指示它自己是否改变。对于比较简单的设计来说,不用flag也是可以的;如果有变化,就通知所有的观察者。如果用flag的话,你可以使通知的时间延迟,并且由你来决定只在合适的时候通知观察者。但是,请注意,控制flag状态的方法是受保护的(protected),也就是说,只有(Observable类的)派生类可以决定哪些东西可以构成一个变化,而不是由Observer派生类的最终用户来决定。 大多数工作是在notifyObservers( )这个方法里完成的。如果没有将flag置为“已改变”,那notifyObservers( )什么也不做;否则,它先清除flag的“已改变”状态,从而避免重复调用notifyObservers( )的时候浪费时间。这些要在通知观察者之前完成,为了避免对于update( )的调用有可能引起被观察对象的一个反向的改变。然后notifyObservers()方法就遍历它所保存的观察者序列,并且调用每个观察者的update( )成员函数。
初看起来,似乎可以用一个普通的Observable对象来管理更新。但是实际上办不到;为了达到这个效果,你必须继承Observable类,并且在派生类的代码里调用setChanged( )方法。它就是用来将flag置为“已改变”的那个成员函数,这么一来,当你调用notifyObservers( )的时候所有的观察者都会被准确无误的通知道。在什么地方调用setChanged( ),这取决于你程序的逻辑结构。
总结:如果Observable(被观察者)对象改变了,则遍历调用所有观察者的NotifyObservers,进而调用Observer对象的Update。这里,我们也可以在NotifyObservers中实现Update方法。
Observable(或称其为目标)并不一定是一个类,它可以是方法、事件。当一事件被激活,就可以认为状态改变,进而通知观察者。
 
《C#设计模式》
当想用一个对象表示应用程序的状态,并通过转换对象来转换程序的状态时,可以使用状态模式。例如,让一个包装类在多个相关的内部类之间转换,并将方法调用传递给当前的内部类。“Design Patterns”指出,状态模式在内部类之间转换,使得包装对象看起来似乎是修改了它的类。许多程序员都有这样的经验,即创建一个类,让它根据传进来的参数执行不同的计算或显示不同的信息。通常在类内部使用一些select-case或if-else语句来决定执行哪一个行为,这正是状态模式要力图取代的粗糙之处。
状态模式的效果:
1、状态模式为应用程序所具有的每一个状态创建了一个基类state的子类,并在应用程序改变状态时在这些状态之间转换。
2、没有与状态相关联的一长串的if或switch条件语句,因为每个状态都封装在一个类中。
3、由于在别处没有指明程序所处状态的变量,减少了因程序员忘记检查状态变量而引起的错误。
4、可以在几部分应用程序之间共享状态对象,比如单独的窗口之间,只要没有一个状态对象具有专门的实例变量。
5、这种方法会产生很多小的类对象,但其处理过程会使程序简单清楚。
6、C#中,所有的State都必须实现一个公共接口,因而必然有共同的方法,尽管某些方法可能失控的。
状态转换:状态之间的转换可以内部指定或外部指定。可以让中介者(Mediator)告诉状态管理器(StateManager)何时在状态之间转换,也也可以让每个状态自动决定其后续状态是什么。
《Thinking in Pattern
为了使同一个方法调用可以产生不同的行为,State模式在代理(surrogate)的生命周期内切换它所对应的实现(implementation)。当你发现,在决定如何实现任何一个方法之前都必须作很多测试的情况下,这是一种优化实现代码的方法。
当你自己实现State模式的时候就会碰到很多细节的问题,你必须根据自己的需要选择合适的实现方法,比如用到的状态(State)是否要暴露给调用的客户,以及如何使状态发生变化。有些情况下,客户端可以直接传对象进来;而有时,状态对于客户端来说是不可见的。此外,用于改变状态的机制可能很简单也可能很复杂-比如用状态机(State Machine)实现一系列的状态以及改变状态的不同机制。
Proxy模式和State模式的区别在于它们所解决的问题不同。《设计模式》里是这么描述Proxy模式的一般应用的:
 1. 远程代理(Remote Proxy)为一个对象在不同的地址空间提供局部代理。
 2. 虚代理(Virtual proxy),根据需要,在创建复杂对象时使用 “延迟初始化(lazy initialization)”
 3. 保护代理(protection proxy) 用于你不希望客户端程序员完全控制被代理对象(proxied object)的情况下。
 4. 智能引用(smart reference). 当访问被代理对象时提供额外的动作。例如,它可以用来对特定对象的引用进行计数,从而实现写时复制(copy-on-write),进而避免对象别名(object aliasing). 更简单的一个例子是用来记录一个特定方法被调用的次数。
State 模式的基本结构:
    // 所有的具体状态必须实现该接口,而ServiceProvider所需要的所有服务均从该接口中获取。
// 保证不同状态下的ServiceProvider对象除了其构造函数中传入的参数不同外,不存在其它的任何异同。
    interface State
    { void operation1(); void operation2(); void operation3(); }
    //ServiceProvider 存在不同的状态
    // 当然不同的状态必须实现state接口
    // 否则我们就不能完全的将状态从ServiceProvider中分离出来
    //state 模式将不同的处理方式,或说不同的行为,完全从ServiceProvider中分离出来。
    // 也就是ServiceProvider提供了唯一的接口
    class ServiceProvider
    {
        private State state;
        public ServiceProvider(State state)
        { this.state = state; }
        public void changeState(State newState)
        { state = newState; }
        public void service1()
        { state.operation1(); state.operation3(); }
        public void service2()
        { state.operation1(); state.operation2(); }
        public void service3()
        { state.operation3(); state.operation2(); }
    }
    class Implementation1 : State
    {
        public void operation1()
        { Console.WriteLine("Implementation1.operation1()"); }
        public void operation2()
        { Console.WriteLine("Implementation1.operation2()"); }
        public void operation3()
        { Console.WriteLine("Implementation1.operation3()"); }
    }
    class Implementation2 : State
    {
        public void operation1()
        { Console.WriteLine("Implementation2.operation1()"); }
        public void operation2()
        { Console.WriteLine("Implementation2.operation2()"); }
        public void operation3()
        { Console.WriteLine("Implementation2.operation3()"); }
}
// 客户端代码
// 在客户端代码中,我们除了传入的参数不同外,没有其它任何不同的代码
    public class StateDemoClient
    {
        static void run(ServiceProvider sp)
        {
            sp.service1(); sp.service2(); sp.service3();
        }
        static void Main(string[] args)
        {
            ServiceProvider sp = new ServiceProvider(new Implementation1());
            run(sp);
            sp.changeState(new Implementation2());
            run(sp);
        }
 }
将State接口声明为不同状态实行的几个操作,倒不如将其声明如下:
        interface State{ void OneStateBegin(); }
不管状态如何实现,客户需要知道的最为明显的就是某个状态将运行,而客户提供的也就是运行某个状态的具体参数。为此,将该接口声明为只包含一个启动状态的方法似乎更为合适。
 
《C#设计模式》
策略模式在外形上与状态模式很相似,但在意图上有些不同。它是由多个封装在一个称为Context的驱动器类里的相关算法组成。客户程序可以从这些不同的算法中选择一个,或在某些情况下,由Context替你选择一个最好的算法。策略模式的意图是使这些算法可交换,并提供一种方法来选择最合适的算法。状态模式和策略模式之间的区别在于:用户通常选择一种策略使用,在Context类中只能有一个策略实例化和运行;而所有不同的状态可以同时都是活动的,在它们之间经常进行转换。另外策略模式封装的算法所作的工作或多或少是相同的,而状态模式封装的相关类所作的事情多少由点不同。最后一点,在不同状态之间转换的概念在策略模式中已经完全没有了。
策略模式的动机:如果一个程序需要一种特定的服务或功能,而且该程序由多种实现该功能的方式,此时适合于使用策略模式。程序可根据运算效率或用户选项在这些算法之间选择。程序中可以由任意数量的策略,可以添加策略,也可以在任何时修改策略。如:以不同的格式保存文件、用不同的算法压缩文件、用不同的压缩机制捕获视频数据、用不同的行分割策略显示文本数据、以不同的形式对同样的数据绘图:线状图或条形图或饼图。对每种情形,都让客户程序告诉驱动器模块(Context)使用哪一个策略,然后让策略完成相应的操作。
策略模式的基本思想:将不同的策略封装在一个模块中,并提供一个简单的接口在这些策略之间进行选择。尽管这些策略不必都是同一类层次结构中的成员,但每个策略都应该有相同的程序设计接口。
   public abstract class StrategyExample
    { public abstract void DoSomething(); }
    // 具体策略的实现:
    public class Strategy_A : StrategyExample { public void DoSomething() { } }
    public class Strategy_B : StrategyExample { public void DoSomething() { } }
    //Context 类,该类本身上可以算是个适配器。
    // 它将抽象类的接口转换成其本身提供的接口
    public class Context
    {
        StrategyExample exam;
        public void DoSomething() { exam.DoSomething(); }
        public void Set() { exam = new Strategy_A(); }
}
// 客户调用代码:选择A策略:
// 该例中,客户端通过set方法设置不同的策略。
            Context ctxt;
            ctxt.Set ();
    ctxt.DoSomething();
      
策略模式允许从几个算法中自动选择一个算法。这些算法可以是在一个继承层次结构中相互关联的,也可以是毫无关系但实现了一个共同的接口。因为Context能根据请求在各种策略间转换,所以它比调用相关的派生类有更大的灵活性。这种方式也避免了使用条件语句。但另一方面,策略模式每有把每件事都隐藏起来。客户代码通常都会知道有多少个可替换的算法,还要拥有一些选择算法的准则。这把算法的选择转移到客户端程序员或用户身上了。
因为有多个不同的参数要传递给不同的算法,所以需要开发一个Context接口和若干策略方法,这些方法的范围要足够达,允许传金来的参数是某个算法用不到的。
《Smalltalk Companion》中列举出了如下集中策略模式的使用情形:
1、以不同的格式保存文件;
2、用不同的算法压缩文件;
3、用不同的压缩机制捕获视频数据;
4、用不同的行分割策略显示文本数据;
5、以不同的形式对同样的数据绘图:线状图、条形图或饼图。
对于每种情形,都让客户程序告诉驱动器模块(Context)使用哪一种策略,然后让具体策略完成相应的操作。
 
《C#设计模式》
模板方法模式是一种非常简单又经常使用的模式。先创建一个父类,把其中的一个或多个方法留给派生类实现,这实际上就是在使用模板方法模式。模板方法模式形式化了下列想法:在类中定义一个算法,但将算法的某些细节留到子类中实现。换句话说,如果基类是一个抽象类,那么你就是在使用一种简单形式的模板方法模式。
模板方法模式有四种方法可用在派生类中:
1、                完整的方法,它完成了所有子类都要用到的一些基本功能。这些方法被称为具体的方法。
2、                方法中根本没有内容,必须在派生类中实现的方法。C#中声明Virtural的方法。
3、                方法里包含了某些操作的一种默认实现,派生类里可以重新定义这些方法。这些方法称为钩子方法。
4、                一个Template类可以包含这样一些方法:他们自己调用抽象方法、钩子方法和具体方法的各种组合。这些方法不仅有待重定义,还描述了一个没有实现细节的算法。“Design Patterns”称这些方法为模板方法
 
《C#设计模式》
访问者模式:能将面向对象模型的劣势转化为优势,它可以创建一个外部类来操作其它类中的数据。当一个多形态的操作由于某种原因不能放在类层次中时(例如,因为在层次设计时没考虑到该操作,或者因为没有必要弄乱类之间的接口),使用访问者模式是很有帮助的。
在访问者模式中,访问每个类的含义是调用一个为此而存在的方法,即Accept方法。Accept方法有一个参数是访问者实例,被访问的实例可以根据该参数回过来在调用访问者的Visit方法,并在调用时将自身作为一个参数传递过去:
    public virtual void Accept(visitor v){ v.Visit( this);}
使用这种方式,访问者对象可以收到每个被访问实例的一个引用,接下来就可以调用它们的公有方法获取数据、执行计算、生成报表,或在屏幕中绘制对象。如果某个类没有Accept方法,可以子类化它并添加上该方法。
何时使用访问者模式?
当对数据执行一个操作,而该数据包含在不同接口的多个对象中,应该考虑使用访问者模式。如果要对这些类执行很多不相关的操作时,用访问者模式也是有帮助的。在因为某种技术或策略的原因而无法获得源代码或不能更改源代码时,若想要向类库或框架中添加功能,访问者模式是一种有用的方法。在后面的这些情形中,只需子类化框架中的类,并向每个子类添加Accept方法。另一方面,若不希望向程序添加很多新类,访问者模式是一个很好的选择。
双分派:
为了让Visitor能够运行,有一个方法实际上分派了两次。某个Visitor调用了一个给定对象的多态Accept方法,该方法回头调用Visitor的多态Visit方法。这种双向调用允许向任何一个具有Accept方法的类中添加更多的操作。因为我们编写的每个新的Visitor类都能使用这些类中的可用数据执行任何想要的操作。使用这种模式下的回调方法,可以更具实际类的类型,调用Visitor中的不同的访问方法,并不必要求被访问的对象使相同的或相关的类型。
将类实例传递给Visitor的调用程序必须知道所有已存在的要被访问的类实例,应该将它们保存在一个简单的结构里,比如数据或集合。另外一种方法是创建这些类的一个迭代器,并将它传递给Visitor。最后一种方法是,Visitor自己保存一个它要访问的对象列表。
思考题:一个投资公司的客户记录包括每个投资人拥有的每支股票或其他投资手段所对应的一个对象。对象包括了一个股票的买入、卖处和股息分配的历史记录。设计一个访问者模式,生成一年中关于股票买卖的年终纯收益或损失的一份报告。    
《Thinking in Pattern
假设说你手头有一组早先的类体系(class hierachy),这些类都是固定不能改变的;可能它们是从第三方买来的,所以你没法改变这个类体系。但是,你可能想给这个类体系添加新的多态方法,通常情况下这必须得向基类接口添加新的东西。所以问题就来了:你既需要给基类添加新的方法,而你又不能动基类。那到底该怎么办呢? 解决这类问题的设计模式叫做“访问者(visitor)”(《设计模式》里最后讲到的那个),它是建立在上一节的双重分派机制之上的。
Visitor模式使得你可以通过创建另外一个独立的visitor类型的类体系来扩展原始类型的接口,从而仿真(virtualize)实现原本针对原始类型的操作。原始类型的对象只是简单的“接受”访问者,然后调用访问者动态绑定的成员函数。
    // 被访问者Accept方法接受一个Visitor接口类型参数
    // 所有访问者都实现该接口
    interface Visitor
    { void visit(Gladiolus g); void visit(Runuculus r); }
    // 被访问者实现该接口已提供Accept方法
    interface Flower
    {   void accept(Visitor v); }
    // 被访问者
    //Accept 方法允许传入一个实现了visitor接口的类对象
    // 通过Visitor实现多态调用
    class Gladiolus : Flower
    { public void accept(Visitor v) { v.visit(this);   } }
    class Runuculus : Flower
    { public void accept(Visitor v) {   v.visit(this); } }
    // 访问Flower具体实现并返回具体花名
    class StringVal : Visitor
    {
        String s;
        public String toString() {   return s; }
        public void visit(Gladiolus g) {    s = "Gladiolus";   }
        public void visit(Runuculus r) {    s = "Runuculus";    }
    }
    //"Bee" 类型对象访问Flower时将放回字符串。。。
    class Bee :Visitor
    {
        public void visit(Gladiolus g)
        { Console.WriteLine("Bee and Gladiolus"); }
        public void visit(Runuculus r)
        { Console.WriteLine("Bee and Runuculus"); }
 
}
            // 客户代码
            // 向Gladiolus类对象添加输出该类名字符串的功能
            // 我们通过调用访问者StringVal来实现该功能
            Gladiolus flower = new Gladiolus();
    flower.accept(new StringVal());
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值