一、建造型
- 简单工厂:
工厂和产品 一对多 if else - 工厂方法:
把简单工厂变为一对一,提出if else - 抽象工厂:
创建一系列产品 - 建造者模式:
把一个产品拆分成多块,由导演类和抽象创建类关联 - 原型模式:
一个对象已经构建过了,想用它但是做一点点改变的话,就先clone然后再扩展 - 单例模式:
全局 自身对保护和生成
二、结构型
- 享元模式:
目的:共享一些东西,避免内存浪费
特点:通过复用来减少对象实例,把虚拟对象状态统一管理,共享状态处理细粒度对象
关键:区分内部状态和外部状态,不变的部分作为实例,变的部分作为参数传入 - 桥接模式:(主附关系)
多个抽象类之间的关联(组合/聚合) 。多变化不耦合,比如车(轿车、公交车、卡车)和公路(林间小路、高速路、土路) - 适配器模式:(三者关系)
例子:德国旅馆,它里面有德标的接口,用这个接口给手机充电
补偿模式,作为后期维护和补偿
增加了类的透明性和复用性
灵活性和扩展性好 - 装饰者模式:(画-框)
定义:动态给一个对象添加一些额外的职责,就象在墙上刷油漆.比继承更灵活。
设计初衷:通常可以使用继承来实现功能的拓展,如果这些需要拓展的功能的种类很繁多,那么势必生成很多子类,增加系统的复杂性,同时,使用继承实现功能拓展,我们必须可预见这些拓展功能,这些功能是编译时就确定了。
要点:装饰者与被装饰者拥有共同的超类,继承的目的是继承类型,而不是行为 - 外观模式:
为子系统中的一组接口提供一个一致界面 - 代理模式:
为其他对象提供一种代理以控制对这个对象的访问 - 组合模式:
三、行为型
-
解释器模式:
定义一个语言,然后用此模式定义出其文法,并同时提供解释器(正则、js本身、前端模版vue等、jq模拟css选择器、sql语句等特定领域语句)
终结符-》常量(叶子节点)
非终结符-》运算符(非叶子节点)
【优点】
1、易于实现语法
2、易于扩展新的语法
3、和享元模式、组合模式、迭代自模式组合使用
【缺点】
1、对于复杂的文法难以维护
2、执行效率较低
3、应用场景有限 -
责任链模式:
多个对象都有机会处理请求,从而避免请求的发送者喝接收者之间的耦合,单一职责。将这些对象连成一条链,按链传递、直到有一个对象处理它(dom事件模型、promise、js的proptype)
【缺点】
1、性能浪费2、兜底处理
-
策略模式
(根据环境或条件选择算法和策略)
定义一系列算法,把它们封装出来并可相互替换
对象本身(人)和算法(如何上班)是区分
(根据环境或条件选择算法和策略)
【优点】
1、算法和对象分离
2、方便替换扩展
3、 消除部分if、witch
【缺点】
1.必先预知策略
2.算法和对象通讯的开销 -
模版方法模式
定义一个算法骨架,具体操作延迟到子类(相同代码放在父类、不同代码放在子类)
【优点】
1、提前定义、子类实现
2、提取公用,代码复用
3、 通过钩子扩展
【缺点】
1、必须归纳出一个算法
2、每个实现都必须定义子类 -
状态模式
当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
主要解决当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同的一系列类当中,可以把复杂的逻辑判断简单化。 -
访问者模式
访问者模式即表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
访问者模式适用于数据结构相对稳定的系统
【优点】
1、使得新增新的访问操作变得更加简单。
2、能够使得用户在不修改现有类的层次结构下,定义该类层次结构的操作。
3、将有关元素对象的访问行为集中到一个访问者对象中,而不是分散搞一个个的元素类中。
【缺点】
1、增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,违背了“开闭原则”的要求。
2、破坏封装。当采用访问者模式的时候,就会打破组合类的封装。 -
观察者模式
发布/订阅模式
注册、注销、更新-》一个对象拥有更新方法则可以作为一个观察者
【优点】
解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。
【缺点】
在应用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。 -
迭代器模式
next() hasnext()
提供一种方法顺序一个聚合对象中各个元素,而又不暴露该对象内部表示。
【特点】
访问一个聚合对象的内容而无需暴露它的内部表示。
为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。
遍历的同时更改迭代器所在的集合结构可能会导致问题(比如C#的foreach里不允许修改item)。
【例子】
foreach
map
【优点】
简化遍历方式
六大原则
- 单一职责原则(Single Responsibility Principle)
单一职责原则是最简单的面向对象设计原则,它用于控制类的粒度大小。单一职责原则定义如下:
一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
单一职责原则告诉我们:一个类不能太“累”!在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。
单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关实践经验。多考虑何为单一,不要过度设计。 - 里氏替换原则(Liskov Substitution Principle)
所有引用基类(父类)的地方必须能透明地使用其子类的对象。
里氏替换原则是继承复用的基石。
只有当衍生类可以替换基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
里氏代换原则是对“开-闭”原则的补充。
实现“开闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。里氏替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
里氏代换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,因为我并不喜欢老鼠,虽然它也是动物。
在使用里氏代换原则时需要注意如下几个问题:
(1)子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。
(2) 我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。 - 依赖倒置原则(Dependence Inversion Principle)
抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程
在实现依赖倒转原则时,我们需要针对抽象层编程,而将具体类的对象通过依赖注入的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式有三种,分别是:构造注入,设值注入(Setter注入)和接口注入。构造注入是指通过构造函数来传入具体类的对象,设值注入是指通过Setter方法来传入具体类的对象,而接口注入是指通过在接口中声明的业务方法来传入具体类的对象。这些方法在定义时使用的是抽象类型,在运行时再传入具体类型的对象,由子类对象来覆盖父类对象。 - 接口隔离原则(Interface Segregation Principle)
- 迪米特法则(最少知道原则)(Demeter Principle)
一个类对自己依赖的类知道的越少越好。无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。 - 开闭原则(Open Closed Principle)
(1)对于扩展是开放的(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。
(2)对于修改是关闭的(Closed for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者.EXE文件,都无需改动。