《Design Patterns》学习总结

在时间比较空余的时候,又找到一本一直想看的书,就是这本名为《Design Patterns》(Gang of Four)的著作。本书通过类似Window Doc的程序,来揭开设计模式学习序幕,通过分析设计程序时遇到的困难,引出可以解决问题的设计模式,从而引导你更全面的掌握设计模式。案例程序的设计引出8个设计模式,案例程序设计完成后就是单独的23个经典设计模式,可以单个查阅,单个阅读。

  1. Abstract Factory模式

    这个模式似乎离我比较远,因为本人还从未开发过在此模式讲解过程中介绍到的设计问题,如支持不同风格控件库。做游戏一般就一套控件库。不过这个模式告诉我们,可以将对象创建单独分离出来,用一个类单独去管理对象创建,如果想添加不同的创建,我们可以构建一个创建类框架。抽象出一个Factory基类,然后派生不同样式的创建类。

  2. Builder模式

    这个设计模式感觉更直觉化,也就是说,当你遇到类似问题的时候,可能会不知不觉的设计出类似Builder的设计模式。这个设计模式的特点在于,通过一个创建类,慢慢的创建每个“小零件”,等整个创建完成,我们将获得一个相当规模的“机器”。

  3. Factory Method模式

    FactoryMethod想必很多人没看过设计模式,在学习多态的时候就或多或少用到过。FactoryMethod是给在一个类层次框架中,添加一个创建对象的虚函数,以实现不同派生类创建不同对象目的。既然是虚函数,所以必须要通过对象调用,所以对象就已经存在了。所以FactoryMethod更多用于创建其他类对象(如迭代器),而非本类。但是如果用于创建自己,此模式将演变成了Prototype模式。

  4. Prototype模式

    这个模式,我认为是FactoryMethod模式的改进版。在一个类层次框架中添加一个接口专门用于拷贝自己。通过这种形式,我们可以实现在运行时创建对象,什么叫运行时创建对象呢?我这样认为的,不是通过硬编码到具体的类去创建某些对象,而是通过一个基类指针,然后调用虚函数来实现对象创建。

    所以这个模式要求我们事先提供几个原型对象。以后就用这些原型对象来创建新对象。这样做到底有什么好处呢?不好一下子讲出来,慢慢讲。假如有一个函数专门用于创建一个类层次框架中的类对象,并绘画此对象。那么我们为了不修改这个函数而在这个类层次框架中添加新类,就需要这个模式了。如果我们不采用这个模式,我们用AbstractFactory,我们需要为不同的类调用不同的创建函数。

    其实,如果我们不想把创建对象这个操作封装起来,我们完全不用依赖这个模式,我们直接在函数外自己声明新的类对象,然后传递给函数使用就可以了,就像一般的多态函数的用法一样。所以一些模式,在一些简单的程序中还是很少被用到的。

  5. Singleton模式

    此模式估计用过一些游戏引擎,或者框架的开发者都见过。很巧妙而简单的实现了单一对象的目标。通过将类的所有构造函数声明而protected或者private,禁止外界创建对象。再通过在类中声明一个静态成员函数,通过一些逻辑,来实现唯一的创建某一对象。

    这里值得注意的是,原文中说一般用new而不用函数内的局部变量的原因有些不正确(个人认为)。

    It isn't enough to define the singleton as a global or static object and then rely on automatic initialization. There are three reasons for this:
    1. We can't guarantee that only one instance of a static object will ever be declared.
    2. We might not have enough information to instantiate every singleton at static initialization time. A singleton might require values that are computed later in the program's execution.
    3. C++ doesn't define the order in which constructors for global objects are called across translation units [ES90]. This means that no dependencies can exist between singletons; if any do, then errors are inevitable.

    这少这看起来不是说局部静态变量的,而是说全局静态变量的。所以局部静态变量也是可以的,当你不在乎对象销毁次序时。

  6. Adapter模式

    这个模式在看《STL源码》的时候就已经听说过了,那是是将的queue和set,map等,这几类都是适配器容器,为什么呢?因为他们的类并没有做太多实质性的事情,只不过是转接了一下底层类的接口,并且限制了某些底层类的接口(因为不希望用户不按规则办事)。所以Adapter模式,是一个比较容易理解而且使用的模式,它有连各两种形式,一种的继承Adapter,另一种是包含(持有)Adapter。特点就跟一般的继承和包含的特点一样,继承比较静态,但能改写函数。包含比较动态灵活,但不能改写底层类的实现。

    另外一种情况让Adapter更实用,那就是让其他框架内的类,经过适配之后,兼容到当前工作的类框架。

  7. Bridge模式

    这个模式从书中讲解来看似乎很难被我们用到,但我觉得,这些类设计模式都是在共同的诠释一点,那就是降低模块间依赖,将变化的部分封装到一处。这样即使变化的部门不断的变化,也只能影响到一处,而不是所有。

    Bridge模式可以讲框架的抽象和实现分开。比如我们不同的系统API,我们可以将同样的系统API继承自一个抽象类A。这样我们用这个抽象类A的指针就能操作不同系统了。但是为了进一步分离函数接口,我们在使用包含模式来封装这个抽象类A得到抽象类B。这样你是抽象类A增加,删除某些接口只能影响到抽象类B的部分实现代码。而不是影响到使用抽象类B的客户端代码。

    所以Bridge带来的结果是,两个类层次框架高度分离,自由发展。最大程度了减少了由实现更改而带来的抽象更改。

  8. Composite模式

    这个模式是最令我感到知识真的学到手了。因为这个模式可谓是实用模式,它提供的一个常见的架构问题解决方案。那就是如何让简单对象,组合成无限复杂对象,而且最好能让处理复杂对象像处理简单对象那样简单?这就是Composite模式的最大用处——构建一个递归包含,且接口一致的类层次框架。

    我们通过提供抽象基类,并让基类持有基类指针,以方便指向派生类。这种形式就是典型的递归形式,自己可以指向自己。派生类通过派生基类,使其具有指向其他派生类的能力,这样就形成了一个递归包含的结构。

    这种结构,在UI框架设计方面几乎是唯一解决方法。

  9. Decorator模式

    此模式很像我们一般使用的方法——自定义系统类(系统类是指非用户创建的类)。当我们想给系统类添加或改变一些功能的时候,我们经常做的就是派生系统类。这样我们就可以访问系统类的非公有数据和接口,并提供更多功能,或者重写某些接口。

    但是Decorator模式在此基础上,稍微添加一点,那就是Decorator类需要更多灵活性,所以其采用包含而不是派生某个部件(或者组件)。并且为了让Decorator继续融入原有的类框架,所以Decorator要派生自抽象基类。如何实现抽象基类的接口呢?一般就是将操作转接到被包含的部件。在转接的时候,我们可以添加自己想要的功能。这样就是先了Decorator的初衷。

  10. Facade模式

    Facade模式估计很多编程比较多的学生都自己创建过,当我们写了很多单独功能的类之后,我们在逻辑层需要一个更为简单的类,来提供一些常用的功能时,我们常用Facade模式。那就是单独写一个类,让这个类提供一些接口来完成一个高层次的逻辑需求。这些接口会负责与那些杂乱的类打教导。

    殊不知,这样就已经完成了Facade模式,此模式可以让客户端和某些子系统(或者称之为模块)依赖更小。为什么?当哪些单独的,杂类的类改变时,客户端不会有任何察觉,因为它只与Facade打交道。

  11. Flyweight模式

    Flyweight模式也是那种比较启发式,探索式的模式(也就是 heuristic模式,但其实所有的模式都是前人慢慢探索出来的,都可以称为heuristic模式了)。其关键在于,将一些不变的因素(成分)分离出来,不要让他们重复出现,如同一个字“的”在文本中出现1W遍,那么我们需要存“的”1W遍吗?虽然以现代计算机的内存来说,完全可以,但是从内存最优化来说,这样做不是最好的解决方法。然后,分析他们重复出现的原因,这些原因一般就是外在因素,我们将不变因素(内在因素)分离出来,让所有重复的地方都去指向同一个它,引用它,而不是创建新的对象,这样就可以大幅度减少内存使用。同时,由于外在因素没有存放起来,所以必须实时计算出来。这就是典型的用内存和效率的权衡。

    当然我们不能让实时计算大幅影响效率,所以采用Flyweight模式的一个前提是,外在因素能有一个比较好的解决方案,比较高效的实时计算出来。其实这个模式,在游戏编程中最经常出现了,那就是Texture,我们绝对不会因为需要在屏幕上画两次图标,而在内存中创建两份图标的像素内存(也就是Texture)。我们一般只创建一个图标像素内存,而创建多个Sprite(指向同一个内存,但包含不同的图片坐标和其他一些状态信息)。

  12. Proxy模式

    这个模式我觉得可以用伪造模式来称呼它。因为其主要目的是为了将自己去伪造某个类,以实现一些功能修改。如当我们需要加载文档时,如果有些很多图片,在打开是看不见,那么我们应该让这图片先不加载以提高文档打开速度。如何不修改原有的代码,而实现这个功能呢?我们可以添加一个代理,让代理完成最简单的功能,如返回图片尺寸。但当真的需要图片时,代理再加载图片。所以代理需要适配其所包含的对象接口。

    所以Proxy有点像Adapter。但Adapter更能适配一个类,使其融入另一个类框架,和Proxy模式仅仅是改变其所包含的对象的部分行为,而并不试图使其兼容到其他类层次框架。而且Proxy作用更简单一点,属于轻量级适配,而Adapter需要做的事情更多,属于重量级适配。

    同时Proxy还能控制访问权限。如派生某类,重写接口并声明为保护访问权限。

  13. Chain of Responsibility模式

    这个模式很像windows的消息传递模式,但也不完全一样。至少这种这种把想要处理某些事件的对象串起来的想法很相似。Chain of Responsibility模式,通过将想要处理某个事件的对象串联起来,然后挨个遍历访问,让每个对象都有机会处理。其中有一点值得注意,为了实现这种消息传递,我们可以建立一个基类,其负责消息传递。当然,如果被串起来对象之间已经建立了某种连接关系,我们可以试着复用,以减少内存开销。

  14. Command模式

    Command模式是一个不错的模式,这种将处理过程封装成一个对象的做法有很多好处。首先通过构建抽象基类,我们能随时添加不同的命令对象。其次,通过对象我们可以记录一些状态或者其他想要的信息。经常接触的Undo操作原来是被Command模式解决了。同时也让命令请求者和被执行者之间更加独立。

  15. Interpreter模式

    一个很头疼的模式,看起来比较无语。但是书中用其处理正规表达式的方式确实让我豁然明朗。仅从框架结构来说,Interpret模式可以说所有的框架都用到过,因为它就是在类层次框架中的每个类中声明同一个虚函数就完了。所以你的类层次框架有个虚函数,你就厚着脸皮说这是Interpreter模式。但是专门提到Interpreter模式,我们更多的是用于处理语法分析,简单语言构建的一种解析方法。

  16. Iterator模式

    用STL的应该没有不知道,为了掩藏容器类内部的实现机制和数据结构,我们将遍历过程抽象出来,使得用户不用关心容器内部结构,就可以遍历所有不同种类的容器。而且通过Iterator中间层,容器类可以随时改变内部结构而不影响客户端代码。

  17. Mediator模式

    一个将各种物体之间的联系集中到一点的模式,通过这个模式,我们减轻和各类中间的关联。Mediator和Observer模式很像,但给我的感觉是,Mediator模式更像干杂活的,而Observer更像一个白领。Mediator模式更适合简单的将对多个对象的操作集中到一点。和Observer更适合建立一种消息通知机制,当某事发生时,通知对这个事件感兴趣的所有对象。

  18. Memento模式

    这个模式,目前看来似乎专门给Undo操作用的,其目的是,将一个对象A的内部状态存贮起来,以方便以后恢复。而且为了不让除对象A以外的类访问,其几乎不定义任何接口,而只是将A声明为其友元类。此模式需要注意,就是某些对象的内部状态非常大(内存),所以简单的保存和恢复状态可能涉及很大的开销。而这些开销外界是不清楚的。

  19. Observer模式

    这个模式,在Cocos2d游戏引擎中就有,那就是CCNotifyCenter吧(如何类名没有记错的话),我们可以为一个对象添加多个Observer,当这个对象发布通知,或者接受到某些事件,需要通知其观察者时,其只需要遍历Observer列表,通知相应对象即可。这和Chain of Responsibility很像。Observer模式,看书中描述似乎多指一个主对象,多个观察者模式。其实多个主对象,多个观察者模式我觉得更通用。

  20. State模式

    这个模式让我稍微开了眼界。因为在写游戏的时候,经常写一个些switch语句,而且一个整形状态变量,可以处理很多状态之间的切换。但是这个模式将其中状态决定行为的方式极致化。那就是封装,通过抽象基类,派生出每个状态类。然后申明一个基类指针,在不同的状态时,付给其不同的状态类对象,就形成状态之间的过度。这种方法更清晰,更有调理。我打算尽量试一试。

  21. Strategy模式

    这个模式主要是将一些可能变化的算法,如排版,计算某些信息等,封装起来。通过抽象类框架,我们可以随时添加各种算法,而不改变被实施算法的类。

  22. TemplateMethod模式

    这个模式自己曾经就用过,只是自己不知道这还是一个模式。我们在基类中,我们在某个通用函数中,埋下一些“钩子”(某些基类函数),以方便派生类重写这些函数,使其可以在基类布置的大框架中,添加自己的一些操作和行为。

  23. Visitor模式

    这个模式将一些与类层次框架中的不是很相关的接口抽离出来,单独组成一个类,或者一个新的类层次框架。例如,假如我们要在一个图形方面相关的类层次框架中添加一个新接口CheckType(),这个接口声明在类层次框架中有点突兀,违背了图形类层次框架的主旨,那么我们可以将CheckType抽离成一个类。类中的接口就是针对每个类层次框架中的类的CheckType操作。如下代码:
        class CheckTypeOperator
        {
        public:
            bool CheckTypeButton( Button* pBt );
            bool CheckTypeEditBox( EditBox* pBt );
            bool CheckTypeScrollBar( ScrollBar* pBt );
        };

    如果要从类层次框架中抽离多个接口,那么我们可以声明一个抽象基类,然后每个派生类重写对象类的操作函数即可。这个模式只在类层级框架稳定时采用,因为如果要往类层次框架中添加多个新类,那么Visitor类层次框架就需要添加很多接口和代码,比较费时费力。所以当类层次框架不稳定时,最好还是在原有的类层次框架中添加接口,以后再改。

你可能感兴趣的:(《Design Patterns》学习总结)