Bootstrap

C#23种设计模式简单记录

一:简单工厂模式

建立流程:

1:一系列继承于某一接口或抽象类的类;

2:工厂类:依据传进来的参数不同来创建不同类实例并返回

优点:

1:可以对创建的对象进行“加工”,对客户端隐藏相关细节。只关注结果,无需了解过程。

缺点:

1:若创建逻辑复杂或创建对象过多,则会造成代码臃肿

2:新增、删除子类均会违反开闭原则

适用场景:

1.需要创建的对象比较少。

2.客户端不关心对象的创建过程。

二:工厂模式

建立流程:

1:建立一个抽象工厂,抽象类以及具体类;

2:建立一个具体工厂,实现一个具体类,关系为一对一;

优点:

1:可以对创建的对象进行“加工”,对客户端隐藏相关细节。只关注结果,无需了解过程。

2:弥补了简单工厂模式的缺点(违反开闭原则);

缺点:

1:当需要创建的对象有许多个时,会增加许多工厂类。

适用场景:

1:当一个类不知道它所必须创建的对象的类的时候:

2:当一个类希望由它的子类来指定它所创建的对象的时候

3:当类将创建对象的职责委托给多个帮忙子类的中的某一个,并且你希望将哪一个帮助子类是代理者者一信息局部化的时候

三:抽象工厂模式

建立流程:

1:不同类型的一系列类(不同品牌的不同服装);

2:工厂接口:需要实例化的类的抽象方法(一个品牌,一件服装)

3:具体工厂实现需要的组合

优点:

1、抽象工厂模式隔离了具体类的生产,使得客户并不需要知道什么被创建。

2、当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

3、增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。

缺点:

增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性。

适用场景:

当需要创建的对象是一系列相互关联或相互依赖的产品族时,便可以使用抽象工厂模式。说的更明白一点,就是一个继承体系中,如果存在着多个等级结构(即存在着多个抽象类),并且分属各个等级结构中的实现类之间存在着一定的关联或者约束,就可以使用抽象工厂模式。假如各个等级结构中的实现类之间不存在关联或约束,则使用多个独立的工厂来对产品进行创建,则更合适一点。

四:单例模式:

建立流程:

1:私有化无参构造函数(类SingleInstance)

2:创建私有静态SingleInstance,用于储存该实例

3:一个方法返回SingleInstance。

优点:

1:以保证内存里只有一个实例,减少了内存的开销;

2:避免对资源的多重占用;

3:设置全局访问点,可以优化和共享资源的访问。

缺点

1:不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。

2:由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。

3:单例类的职责过重,在一定程度上违背了“单一职责原则”。

4:滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

适用场景:

 1:系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
2:客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

五:生成器模式

建立流程:

1:一个复杂的类(有许多需要创建的对象组成的类);

2:抽象生成器(包含对类分布生成的抽象方法以及一个将几个方法组合在一起生成复杂类的抽象方法)

3:主管类,含有一个获取生成器创建产品的方法

优点:

1:你可以分步创建对象, 暂缓创建步骤或递归运行创建步骤。

2:生成不同形式的产品时, 你可以复用相同的制造代码。

3:单一职责原则。 你可以将复杂构造代码从产品的业务逻辑中分离出来。

缺点

 由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加。

适用场景:

1:只有当产品较为复杂且需要详细的配置时,使用生成器模式才有意义。

2:使用生成器模式可避免 “重叠构造函数 (telescoping constructor)” 的出现。

(假设你的构造函数中有十个可选参数, 那么调用该函数会非常不方便; 因此, 你需要重载这个构造函数, 新建几个只有较少参数的简化版。 但这些构造函数仍需调用主构造函数, 传递一些默认数值来替代省略掉的参数,生成器模式让你可以分步骤生成对象, 而且允许你仅使用必须的步骤。 应用该模式后, 你再也不需要将几十个参数塞进构造函数里了)

3:当你希望使用代码创建不同形式的产品 (例如石头或木头房屋) 时, 可使用生成器模式。

4: 使用生成器构造组合树或其他复杂对象。

六:原型模式

建立流程:

抽象接口含有Clone();需要复制的类继承接口实现该方法;

优点:

1.隐藏了创建实例的繁琐过程,只需通过Clone方法就可以获取实例对象

2.使用浅拷贝替代new,减少资源消耗

缺点:

1.需要添加一个Clone方法,C#中一般使用MemberwishClone方法来获取实例的浅拷贝副本。深拷贝需要序列化

适用场景:

(1)类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗。

(2)通过new一个对象需要非常繁琐的数据准备或访问权限,可以使用原型模式。

(3)一个对象需要提供给其他对象访问,而且各个调用者可能需要修改其值,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝

七:适配器模式

建立流程:

类适配器:一个接口,一个现有类,适配器类继承接口和现有类,实现接口中方法(方法中实现现有类方法)

对象适配器:适配器类继承一个接口,内部实例化现有类;

优点:

1:可以在不修改原有代码的基础上来复用现有类,很好地符合 “开闭原则”

:2:可以重新定义Adaptee(被适配的类)的部分行为,因为在类适配器模式中,Adapter是Adaptee的子类

3:仅仅引入一个对象,并不需要额外的字段来引用Adaptee实例(这个即是优点也是缺点)。

缺点:

1:用一个具体的Adapter类对Adaptee和Target进行匹配,当如果想要匹配一个类以及所有它的子类时,类的适配器模式就不能胜任了。因为类的适配器模式中没有引入Adaptee的实例,光调用this.SpecificRequest方法并不能去调用它对应子类的SpecificRequest方法。
2:采用了 “多继承”的实现方式,带来了不良的高耦合。

适用场景:

 当你希望使用某个类, 但是其接口与其他代码不兼容时, 可以使用适配器类。

如果您需要复用这样一些类, 他们处于同一个继承体系, 并且他们又有了额外的一些共同的方法, 但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。

八:桥接模式

建立流程:

子类对接口或抽象类的实现交由其他类实现,在子类中调用其他类的方法来实现接口

优点:

1.实现抽象和具体的分离,降低了各个分类角度间的耦合;

2:扩展性好,解决了多角度分类使用继承可能出现的子类爆炸问题。

缺点:

桥接模式的引进需要通过聚合关联关系建立抽象层,增加了理解和设计系统的难度。

适用场景:

当系统实现有多个角度分类,每种分类都可能变化时使用。近几年提出的微服务概念采用了桥接模式的思想,通过各种服务的组合来实现一个大的系统。

九:组合模式

建立流程:

树枝和叶子结点都继承同一个接口并实现,但是叶子结点对不需要实现的方法进行相应处理,以达到树枝和叶子结点在操作上没有区别(组合模式允许你将对象组合成树形结构来表现”部分-整体“的层次结构,使得客户以一致的方式处理单个对象以及对象的组合。)

优点:

1:组合模式使得客户端代码可以一致地处理对象和对象容器,无需关系处理的单个对象,还是组合的对象容器。

2:将”客户代码与复杂的对象容器结构“解耦。

3:可以更容易地往组合对象中加入新的构件。

缺点:

使得设计更加复杂。客户端需要花更多时间理清类之间的层次关系。(这个是几乎所有设计模式所面临的问题)。

适用场景:

当发现需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑使用组合模式了

十:装饰者模式

建立流程:

1:抽象装饰类继承类,具体装饰类实现,具体装饰类可以调用另一个具体装饰类以不断增加功能

优点:

1:装饰这模式和继承的目的都是扩展对象的功能,但装饰者模式比继承更灵活

2:通过使用不同的具体装饰类以及这些类的排列组合,设计师可以创造出很多不同行为的组合

3:装饰者模式有很好地可扩展性:

缺点:

装饰者模式会导致设计中出现许多小对象,如果过度使用,会让程序变的更复杂。并且更多的对象会是的差错变得困难,特别是这些对象看上去都很像。

适用场景:

1:需要扩展一个类的功能或给一个类增加附加责任。

2:需要动态地给一个对象增加功能,这些功能可以再动态地撤销。

3:需要增加由一些基本功能的排列组合而产生的非常大量的功能

十一:外观模式

建立流程:

外观类对不同类之间的方法执行顺序的重组;

优点:

1:外观模式对客户屏蔽了子系统组件,从而简化了接口,减少了客户处理的对象数目并使子系统的使用更加简单。

2:外观模式实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件是紧耦合的。松耦合使得子系统的组件变化不会影响到它的客户。

缺点:

如果增加新的子系统可能需要修改外观类或客户端的源代码,这样就违背了”开——闭原则“(不过这点也是不可避免)。

适用场景:

1:外一个复杂的子系统提供一个简单的接口

2:提供子系统的独立性

3:在层次化结构中,可以使用外观模式定义系统中每一层的入口。其中三层架构就是这样的一个例子。

十一:享元模式

建立流程:

1:一个享元对象,具有内部状态(一些几乎不会改变的成员,可以有少部分需要传入的参数,例如一个name)和外部状态(创建实例时很大程度会改变的成员由外部传入)

2:一个管理享元对象的工厂,储存不同对象,若存有需要的对象则直接取出;

Ps:如果一个类每次创建实例时经常需要变化的数据和不经常变化的数据混在一起,则会创建大量对象,如果分开,则可以只创建一个对象,经常变化的参数在需要时作为参数传入;

优点:

降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

缺点:

1:为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑更复杂,使系统复杂化。

2:享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。

适用场景:

1:一个系统中有大量的对象;

2:这些对象耗费大量的内存;

3:这些对象中的状态大部分都可以被外部化;

4:这些对象可以按照内部状态分成很多的组,当把外部对象从对象中剔除时,每一个组都可以仅用一个对象代替;

5:软件系统不依赖这些对象的身份

十二:代理模式

建立流程:

  代理类继承与类相同接口,代理类通过重写方法(该方法调用类同名方法),来间接输出类方法。

优点:

  1. 、远程代理为位于两个不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高系统的整体运行效率。
  2. 虚拟代理通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。
  3. 缓冲代理为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间。
  4. 保护代理可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。

缺点:

(1)由于在客户端和真实主题之间增加了一个代理对象,所以会造成请求的处理速度变慢

(2)实现代理类也需要额外的工作,从而增加了系统的实现复杂度。

适用场景:

 代理模式的类型较多,不同类型的代理模式有不同的优缺点,它们应用于不同的场合:

(1) 当客户端对象需要访问远程主机中的对象时可以使用远程代理。
(2)当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理,例如一个对象需要很长时间才能完成加载时。
(3)当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理。通过使用缓冲代理,系统无须在客户端每一次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。
(4) 当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用保护代理。
(5)当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能引用代理。

十三:责任链模式

建立流程:

 一个类处理传进来的参数,若不满足该方法处理条件,则该方法将调用另一个类的处理方法。

优点:

1:降低了请求的发送者和接收者之间的耦合。

2:把多个条件判定分散到各个处理类中,使得代码更加清晰,责任更加明确。

缺点:

1:在找到正确的处理对象之前,所有的条件判定都要执行一遍,当责任链过长时,可能会引起性能的问题

2:可能导致某个请求不被处理。

适用场景:

1:一个系统的审批需要多个对象才能完成处理的情况下,例如请假系统等。

2:代码中存在多个if-else语句的情况下,此时可以考虑使用责任链模式来对代码进行重构。

十四:命令模式

建立流程:

1:请求发送者(调用命令的execute);

2:抽象命令,具体命令的execute方法对接受者方法的调用;

3:接受者

优点:

1:通过引入中间件(抽象接口)降低系统的耦合度。

2:扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。

3:可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。

4:方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。

5:可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。

缺点:

1:可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。

2:命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量,代码抽离肯定比代码聚合更加难理解。

适用场景:

1:将用户界面和行为分离,使两者的开发可以并行不悖。

2:在需要指定、排列和执行一系列请求的情况下,适用命令模式。

3:支持修改日志。

十五:解释器模式

建立流程:

利用终结符类和非终结符类构造出一系列的规则;

优点:

1:扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。

2:容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。

缺点:

1:执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。

2:会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。

3:可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。

适用场景:

1:当读者需要一个命令解释器分析用户命令时;

2:当程序需要分析一串代数时;

3:当程序需要生成各种形式的输出时;

十六:迭代器模式

建立流程:

1:聚合类:包括所需参数,存储关键数据集合以及索引器,数量,创建迭代器方法;

2:迭代器接口(包含MoveNext()),具体索引器(Current以及MoveNext()迭代方法);

优点:

1:单一职责原则。 通过将体积庞大的遍历算法代码抽取为独立的类, 你可对客户端代码和集合进行整理。

2: 开闭原则。 你可实现新型的集合和迭代器并将其传递给现有代码, 无需修改现有代码。

3:你可以并行遍历同一集合, 因为每个迭代器对象都包含其自身的遍历状态。

4:相似的, 你可以暂停遍历并在需要时继续。

缺点:

1:如果你的程序只与简单的集合进行交互, 应用该模式可能会矫枉过正。

2:对于某些特殊集合, 使用迭代器可能比直接遍历的效率低。

适用场景:

1:当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时 (出于使用便利性或安全性的考虑), 可以使用迭代器模式

2:使用该模式可以减少程序中重复的遍历代码

3:如果你希望代码能够遍历不同的甚至是无法预知的数据结构, 可以使用迭代器模式。

十七:中介者模式

建立流程:

1:一些具有互相联系的类,以及抽象中介类,具体中介类(具有所有类)

2:需要实现的方法交由中介类实现或者调用类里的方法

优点:

1:类之间各司其职,符合迪米特法则。

2:降低了对象之间的耦合性,使得对象易于独立地被复用。

3:将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。

缺点:

中介者模式将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。

适用场景:

1:当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用时。

2:当想创建一个运行于多个类之间的对象,又不想生成新的子类时。

十八:备忘录模式

建立流程:

1:原发器(含有需要备忘的属性,创建备忘录和恢复的数据的方法)

2:备忘录(与原发器同名的私有属性)

3:负责人(储存备忘录)

优点:

1:你可以在不破坏对象封装情况的前提下创建对象状态快照。

2:你可以通过让负责人维护原发器状态历史记录来简化原发器代码。

缺点:

1:如果客户端过于频繁地创建备忘录, 程序将消耗大量内存。

2:负责人必须完整跟踪原发器的生命周期, 这样才能销毁弃用的备忘录。

3:绝大部分动态编程语言 (例如 PHP、 Python 和 JavaScript) 不能确保备忘录中的状态不被修改。

适用场景:

1:当你需要创建对象状态快照来恢复其之前的状态时, 可以使用备忘录模式。

2:当直接访问对象的成员变量、 获取器或设置器将导致封装被突破时, 可以使用该模式。

十九:观察者模式

建立流程:

1:主题类(包含对观察者的引用,调用观察者响应主题类的方法)

2:抽象观察者,具体观察者,包含响应主题类的方法

优点:

1:观察者模式实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层,即观察者。

2:观察者模式在被观察者和观察者之间建立了一个抽象的耦合,被观察者并不知道任何一个具体的观察者,只是保存着抽象观察者的列表,每个具体观察者都符合一个抽象观察者的接口。

3:观察者模式支持广播通信。被观察者会向所有的注册过的观察者发出通知。

缺点:

1:如果一个被观察者有很多直接和间接的观察者时,将所有的观察者都通知到会花费很多时间。

2:虽然观察者模式可以随时使观察者知道所观察的对象发送了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎样发生变化的。

3:如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃,在使用观察者模式应特别注意这点。

适用场景:

1:当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两者封装在独立的对象中以使它们可以各自独立地改变和复用的情况下。

2:当对一个对象的改变需要同时改变其他对象,而又不知道具体有多少对象有待改变的情况下。

3:当一个对象必须通知其他对象,而又不能假定其他对象是谁的情况下。

二十:状态模式

建立流程:

1:状态模式建议为对象的所有可能状态新建一个类, 然后将所有状态的对应行为抽取到这些类中。

2:上下文类:原始对象被称为上下文 (context), 它并不会自行实现所有行为, 而是会保存一个指向表示当前状态的状态对象的引用, 且将所有与状态相关的工作委派给该对象。

优点:

1:单一职责原则。

2:开闭原则无需修改已有状态类和上下文就能引入新状态。

3:通过消除臃肿的状态机条件语句简化上下文代码。

缺点:

 如果状态机只有很少的几个状态, 或者很少发生改变, 那么应用该模式可能会显得小题大作。

适用场景:

1: 如果对象需要根据自身当前状态进行不同行为, 同时状态的数量非常多且与状态相关的代码会频繁变更的话, 可使用状态模式.

2:如果某个类需要根据成员变量的当前值改变自身行为, 从而需要使用大量的条件语句时, 可使用该模式。

3:当相似状态和基于条件的状态机转换中存在许多重复代码时, 可使用状态模式。

二十一:策略模式

建立流程:

1:一个抽象类(有一个需要更具不同情况实现的抽象方法),具体类;

2:一个实际使用该方法的类;

3:由客户端决定使用哪种类;

优点:

1:策略模式提供了对开闭原则的完美支持,用户可以在不修改原有系统的基础上选择算法或者行为,也可以灵活的添加新的算法和行为。

2:使用策略模式可以避免多重条件选择语句。

3:策略模式可以让被选择的算法可以得到复用。

缺点:

1:客户端必须知道所有的策略类,并自行决定使用哪一个策略类,可以通过引入策略代理来解决这个问题。

2:策略模式将造成系统产生很多具体策略类。

适用场景:

1. 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。

2. 一个系统需要动态地在几种算法中选择一种。那么这些算法可以包装到一个个的具体算法类里面,而这些具体算法类都是一个抽象算法类的子类。换言之,这些具体算法类均有统一的接口,由于多态性原则,客户端可以选择使用任何一个具体算法类,并只持有一个数据类型是抽象算法类的对象。

3. 一个系统的算法使用的数据不可以让客户端知道。策略模式可以避免让客户端涉及到不必要接触到的复杂的和只与算法有关的数据。

4. 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句,并体现面向对象设计的概念。

二十二:模板方法模式

建立流程:

抽象类中定义一个抽象方法,具体交由具体类实现

优点:

1:实现了代码复用

2:能够灵活应对子步骤的变化,符合开放-封闭原则

缺点:

因为引入了一个抽象类,如果具体实现过多的话,需要用户或开发人员需要花更多的时间去理清类之间的关系。

适用场景:

模板方法模式在抽象类中定义执行的方法和步骤,子类按需重写各个步骤的方法,从而满足具体的需求。某些行为可以分步执行且执行的步骤固定时可以考虑使用模板方法模式。

二十三:访问者模式

建立流程:

1:一个类A(具有属性和一个以外界访问者作为参数的方法)

2:访问者B(以A作为参数或属性,实现与A相关的方法);

本质:将类的属性和方法分离解耦,以达到在经常需要改变方法时只需添加新访问者,无需改变原有类;

优点:

1:开闭原则。

2:单一职责原则。 可将同一行为的不同版本移到同一个类中。

3:访问者对象可以在与各种对象交互时收集一些有用的信息。

缺点:

1:每次在元素层次结构中添加或移除一个类时, 你都要更新所有的访问者。

2:在访问者同某个元素进行交互时, 它们可能没有访问元素私有成员变量和方法的必要权限。

适用场景:

1:如果你需要对一个复杂对象结构 (例如对象树) 中的所有元素执行某些操作, 可使用访问者模式。

2:可使用访问者模式来清理辅助行为的业务逻辑。

3:当某个行为仅在类层次结构中的一些类中有意义, 而在其他类中没有意义时, 可使用该模式。

可能常用的模式:单例模式,装饰者模式,享元模式,命令模式(MVVM常用),模板方法模式,策略模式来代替(ifelse);

;