大话设计模式 学习笔记

主要学习书籍为大话设计模式。这个假期终于把这本拖了很久没看的书看完了,也第一次比较认真的做了读书笔记,感觉收获还是不少的。看完以后总结一下的话就是,这本书的确很有意思,而且讲述设计模式的方法是从不好的代码向更好的代码有一个过渡的过程,能让读者不仅知其然,更知其所以然。

这几天要把Effective C#看一下,知乎上已经有大佬做了很好的总结了,所以应该不会有这个的读书笔记了。然后就会进行对设计模式的第二部分学习,拜读一下游戏编程模式。这本书以前看着没什么印象,看完就忘了,现在有了一点设计模式的基础以后感觉会好很多。届时可能还会出新的读书笔记。

简单工厂模式

简单工厂模式是构建一个工厂类,在这个类里根据参数实例化对应的生产类,而各个生产类可以继承自同一个父类,通过多态性就可以将不同的生产类赋值给同一个父类类型的变量并返回。即工厂类返回一个操作类,在其他地方用这个返回的类进行操作。

工厂方法模式

工厂方法模式是定义一个用于创建对象的借口,让子类决定实例化那一个类。工厂方法中,可以理解为一个IFacotry为总工厂,接着派生了很多工厂类。如果要添加功能,只需要添加一个新的工厂类就可以了。工厂方法使一个类的实例化延迟到其子类。

简单工厂和工厂方法的对比

简单工厂中,如果要新添加一个功能,需要修改工厂重点判断条件(switch);而工厂方法中,总工厂是一个借口,只需要添加一个新的类即可。工厂方法把简单工厂的内部逻辑判断移动到了客户端代码进行。


抽象工厂模式

抽象工厂模式是指提供一个创建一系列相关或相互依赖对象的借口,而无需指定他们具体的类。其结构一般是:一个抽象工厂接口,由多个具体工厂实现。每个具体工厂可以生产不同种类的产品。

通常是在运行时先声明一个抽象工厂,然后再创建一个具体工厂类的实例,这个具体的工厂再创建具有特定实现的产品对象。

其好处是:

  • 易于交换产品系列,只需改变创建的具体工厂类,就可以改变接下来用该工厂类生成的所有的产品,使得改变一次就可以改变所有的产品配置。
  • 让具体的创建实例过程与客户端分离,客户端是通过他们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户端代码中。

缺点:

  • 如果需要增加产品种类,则需要增添其对应的所有具体工厂的类,还要修改抽象工厂和所有具体工厂的类,修改过多。
  • 每个使用抽象工厂的地方都需要有IFactory = new CurrentFactory()这样的代码,如果需要改变产品所属的具体工厂,每一处都要进行修改。

用简单工厂来改进抽象工厂

把工厂接口和具体工厂融合为一个简单工厂,在简单工厂中用switch来判断使用哪个具体工厂。但是这样增加一个工厂的时候就会在简单工厂中修改很多代码,为此使用反射进一步的改进。

使用反射+抽象工厂

反射的格式:Assembly.Load("程序集名称").CreateInstance("命名空间.类名")
这样就把实例化工厂的时间点从编译时改到了运行时。并且使用的参数是字符串类型,可以在运行时决定到底使用什么类型。这样再增加一个工厂时就会变的很简单,只需要修改类名即可。

使用反射+配置文件

在配置文件中存放使用的具体工厂类名,程序中直接读取配置文件。这样即使修改了具体工厂类名,也不需要重新编译。

一些个人理解

  • 抽象工厂,抽象的是什么,是每个产品由不同的具体工厂生产,但可以用同样的方法使用,所以是对产品的生产方的抽象。
  • 抽象工厂中,增加新的具体工厂简单,只需要添加类;而增加新的产品则麻烦,因为不仅需要新添加类,还得修改原有的工厂类。

装饰模式

装饰模式动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。一般的结构如下:

  • Component:定义一个对象接口,可以给这些对象动态地添加职责。
  • ConcreteComponent:定义了一个具体对象,也可以给这个对象添加一些职责。
  • Decorator:装饰抽象类,继承了Component类,扩展Component的功能,但对于Component来说,是无需知道Decorator的存在的。
  • ConcreteDecorator:具体装饰类,给Component添加职责。

其中常见的添加职责的方法就是,在具体装饰类中,先写好自己的附加功能,然后在合适的位置插入base.Operation()。


策略模式

策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户。

策略模式是一种定义了一系列算法的方法,从概念上来看,所有这些算法都完成的是相同的工作,只是实现不同,他可以用相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。并且Strategy层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法总的公共功能。并且简化了单元测试。


原型模式

原型模式就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节。其好处:

  • 提高效率,减少构造函数的执行之间。
  • 隐藏了对象创建的细节

注意深拷贝和浅拷贝


代理模式

代理模式是为其他对象提供一种代理以控制对这个对象的访问。一般的结构如下:

  • Subject:定义了真实实体和代理者共同实现的接口
  • RealSubject:定义了代理所代表的真实实体
  • Proxy:代理,保存一个引用使得代理可以访问实体,病提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体。

一般的实现是:在真实实体类和代理类中都实现接口所声明的方法,其中实体是确实的时下你,而代理类是直接调用实体的方法。


模板方法模式

当我们要完成在某一细节层次一致的一个过程或一系列步骤,但其个别步骤在更详细的层次上的实现可能不同时,我们通常考虑用模板方法模式来处理。

定义:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

其中一种实现方法是:在顶层的抽象父类中实现一个具体的非抽象方法,在该方法中调用需要子类重写的抽象方法,各个派生类再分别重写自己的抽象方法。


迪米特法则(最少知识原则)

如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个雷达某一个方法的话,可以通过第三者转发这个调用。

其核心思想史强调了类之间的松耦合。类之间的耦合越弱,越有利于复用。


外观模式

外观模式是为子系统中的一组借口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。说白了,就是为底层接口定义一个高层接口,使得用户不需要跟底层的复杂机制打交道,只需使用高层Facede接口就好。适用于底层的交互开始复杂时,减轻继续增量或维护时与底层交互的工作量。


建造者模式

讲一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程具有不同的内部表象的产品对象。它主要用于创建一些复杂的对象,这些对象内部构建间的建造顺序通常是稳定的,但对象内部的构建通常面临着复杂的变化。其好处是是的建造代码与表示代码分离。

实际上的一种实现就是构建一个抽象类或者接口,定义必须实现的步骤,然后派生出具体的建造类。同时需要一个指挥者类,该类控制对每个具体的建造类进行建造。建造过程在指挥类中完成,而显示过程则在每个具体建造类中实现。


观察者模式

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

其中应该有Subject父类和Observer父类,分别表示通知者和观察者的基类。每个Subject的派生类都可以对应多个观察者,即Observer的派生类。每个Observer类定义一个Update更新方法,在Subject通知自己时调用。每个具体的Observer初始化时都应该有一个对应的具体Subject类绑定,注意这里的绑定时引用,所以更改Subject中的标记内容,就可以改变通知时的不同内容了。

那么什么时候应该使用观察者模式呢:

  • 当一个对象的改变需要同时改变多个不知具体有多少对象时
  • 当一个抽象模型有两个方便,其中一方面依赖于另一方面。可以将这两者封装在独立的对象中使他们各自独立地改变和复用。让双方都依赖于抽象而不是具体。

不足:

  • 抽象通知者还是依赖抽象观察者,通知者必须有观察者的Update这样的接口才能通知。
  • 并且对于多个观察者,有时可能需要多个

在C#中,事件和委托就是观察者模式的实现,并且使用非常方便。(使用事件和委托时不需要抽象观察类,但还是需要抽象的Subject类,即通知类)

  • 委托时一种引用方法的类型,可以将委托看作是函数的抽象,每一个委托可以代表一个同样类型的函数,可以使得委托对象所搭载的方法并不需要属于同一个类。

  • 事件则可以理解为管理多个委托,可以用+=或-=来增减事件所控制的委托。

  • 事件声明时需要委托类型。

  • 而调用事件和调用函数的方法是相同的,调用事件相当于同时调用了事件管理的所有委托方法。


状态模式

状态模式是当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。状态模式主要解决的就是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。

当一个对象的行为取决于他的状态,并且它必须在运行时刻根据状态改变他的行为时,就可以考虑使用状态模式了。

状态模式的好处是将与特定状态相关的行为局部化,并且将不同状态的行为分隔开来。也就是将特定的状态相关的行为都放入一个对象中,由于所有与状态相关的代码都存在于某个ConcreteState中,所以通过定义新的子类可以很容易的增加新的状态和转换。

状态模式通过把各种状态转移逻辑分布到State的子类之间,来减少相互间的依赖。每一个状态即一个子类,从抽象的总State类中派生。


适配器模式

适配器模式将一个类的借口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。想当于封装出一个不适配目标类的新的类,能够完成要求的功能并且接口还适配。

考虑在双方都不太容易修改的时候再使用适配器模式,否则直接修改代码就好了。(或者使用第三方开发组件时)。DataAdapter就是适配器模式的.NET应用。


备忘录模式

备忘录模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态。

备忘录模式的结构通常分为三部分:

  • Originator(发起人):负责创建一个备忘录,用来记录内部的状态,并且可以通过备忘录恢复到当时的状态。同样,也是在这里确定应该备份哪些内容。
  • Memento(备忘录):负责存储Originator对象的内部状态,起到封装的作用。
  • Caretaker(管理者):负责保存好备忘录Memento,不能访问具体内部情况。

在使用备忘录模式时,通常的顺序是:创建一个Caretaker对象,在需要备份时,通过发起人返回一个保存当时窗台的Memento。需要回档时,发起人使用复位函数,复位Caretaker保存的备忘录。


组合模式

组合模式将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。当需求中体现部分与整体层次的结构时,以及希望用户可以忽略组合对象与单个对象的不同,统一的使用组合结构中的所有对象时,就应该改考虑用组合模式了。

实际上就是把一个整体中的每个部分用相同的基类派生表示,使其具有相同或类似的结构,这样在进行操作时就会方便很多。

组合模式的好处:

  • 组合模式定义了包含基本对象和组合对象的类层次结构。基本对象可以被组合成更复杂的组合对象。而这个组合对象又可以被组合,这样不断地递归下去,客户代码中,任何用到基本对象的地方都可以使用组合对象。
  • 组合模式让用户不需要关心到底是处理一个叶子节点还是组合节点,省却了判断的工夫。也就是说,让客户可以一致的使用组合解耦股和单个对象。

迭代器模式

迭代器模式指提供一种方法顺序访问一个聚合对象中各种元素,而又不暴露该对象的内部表示。当需要访问一个聚集对象,而且不管这些对象是什么都需要遍历的时候,就应该考虑用迭代器模式。实际上foreach就是迭代器模式的一种实现。


单例模式

单例模式是保证一个类仅有一个实例,并提供一个访问它的全局访问点。将构造方法设为private,防止其他类new该单例类;提供一个全局访问点,往往是一个静态函数。

注意多线程时的单例,要上锁。

  • 直接上锁:每次检查实例是否为空前上锁。
  • 双重锁定:在上锁前首先检查实例是否为空。注意上锁之后还要再检查一次实例是否为空,因为多线程有可能出现冲突。但是这样会增加效率,不用每次都上锁。

之前说的是懒汉式单例类,即到第一次使用时才会初始化单例类。C#中存在静态初始化方法,可以不需要考虑线程安全问题,这种初始化时在类一加载时就会实例化单例类对象,所以要提前占用系统资源。其中饿汉式初始化单例类的语句为:private static readonly Singleton instance = new Singleton();其中readonly意味着只能在静态初始化期间或在类构造函数中非配变量,即饿汉式的单例类。


桥接模式

合成/聚合复用原则

尽量使用合成/聚合,而不是使用类继承。继承一定要在时’is a’的关系时才考虑使用。

桥接模式

桥接模式是将抽象部分与他的实现部分分离,使他们都可以独立地变化。这里的抽象与实现分离,指的并不是让抽象类与其派生类分离,因为这没有任何意义。实现指的是抽象类和它的派生类用来实现自己的对象。对于手机的例子来说,就是手机既可以按照品牌来分类,也可以按照功能来分类。

更通俗的说,实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这多角度分离出来让他们独立变化,减少他们之间的耦合。


命令模式

命令模式是将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

命令模式的结构可以分为以下几部分:

  • Command:命令类,其中命令类包括抽象命令类和派生出的具体命令类,命令类中包括Receiver,和每个命令的功能,或者说操作。
  • Invoker:这个类的功能有点类似于餐厅中的服务员,即对命令起到一个管理的作用。包括对命令的Add方法和执行方法。
  • Receiver:接受类,知道如何实施一个与请求相关的操作。是执行命令的主体。

一般的流程如下:首先创建一个Receiver,用这个Receiver来初始化具体的Command类。然后通过Invoker来设置命令,执行命令。在整个过程中,已经设置的命令都是保存在Invoker中的,可以用一个List来保存所有的命令。实际上,Invoker起到的是一个管理命令的作用;Receiver是执行者;Command就是要执行的命令,每个具体命令从抽象命令中派生,对应执行Receiver中具体的一个方法。所以实际上命令的具体实现还是写在Receiver中的。

命令模式的好处:

  • 能较容易的设计一个命令队列
  • 在需要的情况下,可以较容易的将命令计入日志
  • 允许接受请求的一方决定是否要否决请求
  • 可以容易的实现对请求的撤销和重做。
  • 命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。

职责链模式

使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连城一条链,并沿着这条链传递该请求,直到有一个对象处理它为之。

定义一个处理请示的抽象类接口Manager,从Manager派生出各个具体的能够处理请示的类。每个类中都应包含一个Manager类型的后继者变量,用于存储链的下一跳(链尾不算)。每个具体的处理请示的类中,根据请求的属性进行判断:如果自己能处理,就处理;否则将请求传递给自己的后继者进行处理,以此类推,直到链的尾部。往往客户端的请求都是从最低级的开始的,请求会沿链自动传递。

职责链的好处是请求者不用管哪个对象来处理。接受者和发送者都没有对方的明确信息,且联众的对象自己也不知道链的结构。结果是职责链可简化对象的相互连接,他们仅需保持一个指向其后继者的引用,降低耦合度。也可以随时的增加或修改一个请求的结构,增强了灵活性。


中介者模式

用一个中介对象来封装一系列的对象交互。中介者使个对象不需要显示的相互引用,从而使其耦合松散,而且可以独立的改变他们之间的交互。

通常的结构包括:

  • 抽象中介者:定义了同事对象到中介者对象的接口。
  • 具体中介者:实现抽象类的方法,它需要知道所有具体同事类,并从具体同事接受消息,向具体同事对象发出命令。
  • 抽象同事类:定义了同事对象的基本属性和共同的操作
  • 具体同时雷:每个具体同事只知道自己的行为,不了解其他同事类的情况,但每个具体同事类都知道中介者对象。

中介者模式的优缺点;

  • 优点:
    • 中介者的出现减少了各个同事类之间的耦合,使得可以独立地改变和复用各个同事类。
    • 由于把对象如何协作进行了抽象,将中介作为一个独立的概念并将其封装在一个对象中,这样关注的对象就从对象各自本身的行为转移到他们之间的交互上来,也就是站在一个更宏观的角度去看待系统。
  • 缺点; 由于中介者控制了集中化,把整个系统的交互复杂性变味了中介者的复杂性,会使得中介者过于复杂,并且该中介者承担了过多的责任,如果中介者损坏,则整个系统都会瘫痪。

中介者模式一般用于一组对象以定义良好但是复杂的方式进行通信的场合,比如计算器,以及想定制一个分布在多个类中的行为,而又不想生成太多的子类的场合。


享元模式

享元模式是运用共享技术有效地支持大量细粒度的对象。享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,可以把这些参数移到实例的外面,在方法调用时将他们传递进来,就可以通过共享大幅度的减少单个实例的数目。

享元模式的结构如下:

  • Flyweight类:该类是所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。
  • 具体Flyweight类:该类是继承与Flyweight的类,并为内部增加存储空间。在具体享元类中的Use函数,可以添加参数,这些参数就是从实例内分离出去的外部状态。可以实现相似的类的不同处的区别。
  • 非共享具体Flyweight类:该类是指不需要共享的Flyweight的子类。
  • FlyweightFactory:享元工厂类:用来创建并管理Flyweight对象。他主要是用来确保合理的共享Flyweight,当用户请求一个Flyweight时,工厂提供一个已经创建的实例或者创建故意而。

如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么就可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。


解释器模式

解释器模式是指给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。解释器模式需要解决的是:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语句中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。正则表达式就是其中的一个应用。

解释器模式的结构如下:

  • AbstratExpression:抽象表达式类:声明一个抽象的解释(Interpret)操作,这个接口为抽象语法树中所有的节点共享。
  • TerminalExpression:终结符表达式,实现与文法中的终结符相关联的解释操作。实现抽象表达式中所要求的借口,Interpret方法。文法中每一个终结符都有一个具体终结表达式类与之对应。
  • NonterminalExpression:非终结符表达式,为文法中的非终结符实现解释操作。对文法中每一条规则R1,R2…Rn都需要一个具体的非终结符表达式类。解释操作以递归方式调用上面所提到的R1…Rn中各个符号的实例变量。
  • Context:包含解释器之外的一些全局信息。例如输入,输出等。是Interpret方法的参数。

解释器模式的好处:

  • 当一个语言需要解释执行,并且可以将该语言中的句子表达为一个抽象语法树时,可使用解释器模式。
  • 可以容易地改变和扩展文法,因为该模式使用类来表示文法规则,可以使用继承来改变或扩展该文法。也比较容易实现文法,因为定义抽象语法树中各节点的类的实现大体类似,这些类都易于直接编写。

缺点:

  • 解释器模式为文法中的每一条规则至少定义了一个类,因此包含许多规则的文法可能难以管理和维护。建议当文法非常复杂时,使用其他的技术如语法分析程序或编译器生成器来处理。

书中的例子是要处理一个字符串,字符串中的每组都是用空格分开的。相当于将每组分开的方法应该是相同的,但是不同类型的文法组处理方式不同。所以先创造一个抽象父类,里面有分割的方法,并且声明了一个解释的抽象方法。从该抽象父类派生出不同的解释文法类,都继承了这个分割方法,并且解释的方法各不相同。然后递归对字符串进行解析即可。


访问者模式

访问者模式表示一个作用于某对象结构中的各元素的操作。它使你可以再不改变个元素的类的前提下定义作用于这些元素的新操作。

访问者模式的结构如下:

  • Visitor:抽象访问者类,其中为该对象结构中每个具体的Element(成分)类声明一个visit操作。需要注意点是,每个visit方法的参数都是其对应的具体的Element类。
  • ConcreteVisitor1,2…:具体访问者类,实现每个Visitor类中声明的操作。每个操作实现算法的一部分,而该算法片段乃是对应于结构中对象的类。
  • Element:抽象Element类,定义了一个Accept操作,以一个访问者为参数。
  • ConcreteElement:具体Element类,实现Accept操作,Accept操作以一个访问者为参数。
  • ObjectStructure:对象结构类,在该类中可以有存放所有的具体Element,并且可以对Element进行增删,并且可以遍历所以的Element进行接受:e.Accept(visitor)

这里Element类中的Accept方法:visitor.VisitConcreteElementA(this),实际上这是一个双分派操作。首先在客户端中,visitor作为参数传递给Element类,然后该Element以自己this为参数调用visitor的具体访问操作。也就是说执行的操作取决于请求的种类和接受者的类型。

访问者模式适用于数据结构相对稳定的系统,对于上面的结构来说,此时添加一个新的操作,即添加一个新的访问者类时很方便的。但是如果要添加成员,即Element,那么就非常麻烦了,所以访问者模式适用于这种数据结构稳定的情况。例如人类只有男人和女人,即Element只有两种,那么此时使用访问者模式添加Visitor,即操作就很方便了。

访问者模式把数据结构和作用于结构上的操作之间耦合解脱开,使得操作集合可以相对自由的变化。访问者模式的目的是要把处理从数据结构分离出来。如果系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式就是比较合适的,因为访问者模式使得算法操作的层架更容易。(增加一个操作就相当于增加一个新的访问者)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值