设计模式基础(第一部分)(设计原则)

设计模式

设计模式入门

  • 设计模式概念
  • 设计模式作用
  • 设计模式历史
  • 设计模式分类

设计模式概念

  • 一套被多数人知晓,经过分类编目的、反复使用的优秀代码设计经验的总结
  • 特定环境下特定问题的处理方法

设计模式作用

  • 重用设计和代码
    • 重用设计比重用代码更有意义,自动带来代码重用
  • 提高扩展性
    • 大量使用面向接口编程,预留扩展插槽,新的功能或特性很容易加入到系统中来
  • 提高灵活性
    • 通过组合提高灵活性,可以允许代码修改平稳发生,对一处的修改不会波及到很多其他模块
  • 提高开发效率
    • 正确使用设计模式,可以节省大量的时间

设计模式历史

  • 起源于建筑工程领域
  • 1995年GOF出版,第一次将设计模式提升到理论高度,并将之规范化,该书提出了23种基本设计模式
  • 时至今日,在可复用面向对象软件的发展过程中,新的设计模式仍然不断出现

设计模式分类

  • 面向对象设计原则
    • 面向对象设计的基石
    • 面向对象设计质量的依据和保障
    • 设计模式是面向对象设计原则的经典应用
  • 创建型设计模式
    • 解决类的实例化问题,提高创建对象效率
    • 关注对象的创建过程
  • 结构型设计模式
    • 解决类或对象的组成结构问题
    • 关注对象和类的组织
  • 行为型设计模式
    • 解决类或者对象之间的交互问题,职责分配问题
    • 关注系统中对象之间的相互交互,研究系统在运行时对象之间的相互通信和协作,进一步明确对象的职责。
  • 类设计模式
    • 用于处理类和子类之间的关系
    • 通过继承建立关系,是静态的,在编译时就已经确定
    • 因为从某种意义上来说,几乎所有模式都是使用继承机制,因此此处的“类模式“是指几种处理类间关系的模式,只有很少部分模式属于此类
  • 对象设计模式
    • 用于处理对象间的关系
    • 这些关系具有动态性,在运行期间是可以变化的

面向对象的设计原则

  • 单一职责原则
  • 开闭原则
  • 里氏替代原则
  • 依赖倒置原则
  • 接口分离原则
  • 迪米特原则
  • 组合/聚合复用原则

单一职责原则SRP

  • 应该有且仅有一个原因引起类的变更
  • 系统中的每一个类都应该只有一个职责,而所有类所关注的就是自身职责的完成
  • 多个职责耦合在一起,会影响复用性,可能只需要复用该类某一个职责,但该职责跟其他职责耦合在一起,很难分离出来
  • 单一职责原则的意思就是经常说的“高内聚、低耦合”
  • “变化的原因”,只有实际发生时才有意义。可能预测到会有多个原因引起这个类的变化,但这仅仅是预测,并没有真的发生,这个类仍可看做具有单一职责,不需要分离
  • JavaEE中的分层框架模式实际上体现了单一职责原则

开闭原则OCP

  • 软件实体应当对扩展开放,对修改关闭。更通俗翻译:软件系统中的各种组件,如模块(Modules)、类(Classes)以及功能(Functions)等,应该在不修改现有代码的基础上,引入新功能。
  • 实现开闭原则的关键是抽象
  • 定义一个抽象层,只规定功能而不提供实现,实现通过定义具体类来完成
  • 当需求变化时,不是通过修改抽象层来完成,而是通过定义抽象层的新实现(扩展)完成
  • 通过抽象类及接口,规定了具体类的特征作为抽象层,相对稳定,不需修改,从而满足“对修改关闭”;从抽象类导出的具体类可以改变系统的行为,从而满足“对扩展开放”
  • 通过扩展已有软件系统,可提供新的行为,以满足对软件的新需求,提高了软件系统的适应性和灵活性
  • 已有的软件模块,特别是最重要的抽象层模块不能再修改,提高了软件系统的一定的稳定性和延续性
  • 这样的设计同时也满足了可复用性与可维护性

总结:

  1. 实现开闭原则的关键是抽象
  2. 抽象层相对稳定,不需修改,需求变化后通过重新定义抽象层的新实现来完成
  3. 即使无法百分之百的做到开闭原则,但朝这个方向努力,可以显著改善一个系统的结构
  4. 对系统每个部分都肆意地进行抽象也不是一个好主意,应该仅仅对程序中需求频繁变化部分进行抽象。拒绝不成熟的抽象和抽象本身一样重要
  5. 开闭原则具有理想主义的色彩,它是面向对象设计的终极目标。其他设计原则都可以看作是开闭原则的实现手段或方法
  6. 软件系统的构建是一个需要不断重构的过程,100%满足开闭原则的软件系统是相当困难的,这就是开闭原则的相对性。但在设计过程中,通过对模块功能的抽象(接口定义),模块之间的关系的抽象(通过接口调用),抽象与实现的分离(面向接口的程序设计)等,可以尽量接近满足开闭原则

里氏替代原则LSP

所有引用基类的地方必须能透明地使用其子类的对象。

里氏替代原则包含了四层意思

  1. 子类必须完全实现父类的方法
  2. 子类可以有自己的个性
  3. 覆盖和实现父类的方法时,输入参数可以被放大,但不能被缩小
  4. 覆盖和实现父类的方法时,输出结果可以被缩小,但不能被放大

里氏替代原则对如何良好继承提出了衡量依据

采用开闭原则必然用到抽象和多态,而这离不开继承。而里氏代换原则对如何良好继承提出了衡量依据。里氏代换原则是使代码符合开闭原则的一个重要保证。

依赖倒置原则DIP

依赖倒置原则三层含义

  1. 高层模块不应该依赖低层模块,两者都应该依赖其抽象
  2. 抽象不应该依赖细节
  3. 细节应该依赖抽象

抽象:即抽象类或接口,两者是不能够实例化的

细节:即具体的实现类,实现接口或者继承抽象类的类,可通过关键字new直接被实例化

依赖正置就是类间的依赖是实实在在的实现类间的依赖,也就是面向实现编程

依赖倒置原则的本质其实就是通过抽象(抽象类或接口)使各个类或模块的实现彼此独立,不相互影响,实现模块间的松耦合

依赖倒置实现三种方式

  1. 通过构造函数传递依赖对象
  2. 通过setter方法传递依赖对象
  3. 接口声明实现依赖对象

依赖倒置原则的规则

  1. 每个类尽量都有接口和抽象类,或者抽象类和接口都有
  2. 变量的表面类型尽量是接口或者是抽象类
  3. 任何类都不应该从具体类派生。
  4. 尽量不要覆写基类已经实现好的方法

采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,减少并行开发引起的风险,提高代码的可读性和可维护性

依赖倒置原则核心就是要面向接口编程,理解了面向接口编程,也就理解了依赖倒置

如果没有实现依赖倒置原则,那么也就意味着开闭原则也无法实现

接口分离原则ISP

客户端不应该强行以来它不需要的接口

类间的依赖关系应该建立在最小的接口上

建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,要为各个类建立专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

接口隔离原则的规范

  1. 接口尽量小.保证一个接口只服务一个子模块或者业务逻辑
  2. 接口高内聚。接口高内聚是对内高度依赖,对外尽可能隔离
  3. 定制服务。为调用者提供且只提供他需要的方法
  4. 接口设计有限度。根据业务及经验,仔细思考筹划,合理适度隔离接口

接口隔离原则和单一职能原则辨析

  1. 单一职责原则注重的是职责;而接口隔离原则注重对接口依赖的隔离
  2. 单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构建

防止庞大、臃肿的接口,避免“接口污染”,提高灵活性和可维护性

注意控制接口的粒度,接口不能太小,如果太小会导致系统中接口泛滥,不利于维护

接口也不能太大,太大的接口将违背接口隔离原则,灵活性较差,使用起来很不方便

接口中仅包含为某一类用户定制的方法即可,不应该强迫客户依赖于那些它们不用的方法

迪米特原则LOD

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

迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系

应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度

尽量降低类与类之间的耦合

过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合

为了克服狭义迪米特法则的缺点,可以使用依赖倒转原则,引入一个抽象的类型引用"抽象陌生人"对象,使"某人"依赖于"抽象陌生人",换言之,就是将"抽象陌生人"变成朋友

外观模式和中介者模式都是迪米特法则的应用

合成/聚合复用原则CARP

优先使用对象组合,而不是类继承.(要尽量使用合成和聚合,尽量不要使用继承)

合成聚合复用原则是指在一个新对象中通过关联关系(组合和聚合关系)使用原来已经存在的一些对象,是之成为新对象的一部分,新的对象通过向这些原来已经具有的对象委派相应的动作或者命令达到复用已有功能的目的。

“要尽量使用合成和聚合,尽量不要使用继承”的原因

  1. 继承复用破坏包装,它把超类的实现细节直接暴露给子类,这违背了信息隐藏的原则
  2. 如果超类发生了改变,那么子类也要发生相应的改变,这就直接导致了类与类之间的高耦合,不利于类的扩展、复用、维护等,也带来了系统僵硬和脆弱的设计
  3. 从超类继承而来的实现是静态的,不可能再运行时间内发生改变,因此没有足够的灵活性

用合成和聚合的时候新对象和已有对象的交互往往是通过接口或者抽象类进行的,就可以很好的避免上面的不足,而且这也可以让每一个新的类专注于实现自己的任务,符合单一职责原则。

非常有利于构建可维护、可复用、可扩展和灵活性好的软件系统

组合与继承都是重要的复用方法。组合称为黑箱复用,继承称为白箱复用

优先使用组合可以获得复用性与简单性更佳的设计

优先使用对象组合,而不是类继承。使用继承必须符合里氏替代原则

具体的设计模式

简单工厂模式:Simple Factory

由一个工厂类根据传入的参数(一般是字符串参数),动态决定应该创建哪一个产品子类(这些产品子类继承自同一个父类或接口)的实例,并以父类形式返回。

该模式中包含的角色及其职责

工厂(Creator)角色

简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类提供静态方法,可以被外界直接调用,创建所需的产品对象。

抽象产品(Product)角色

简单工厂模式所创建的所有对象的父类,描述所有实例共有的接口。 可以是抽象类或接口。

具体产品(Concrete Product)角色

是简单工厂模式的创建目标。

优点:

  1. 客户端不负责对象的创建,而是由专门的工厂类完成;
  2. 客户端只负责对象的调用,实现了创建和调用的分离,降低了客户端代码的难度

缺点:

  1. 如果增加和减少产品子类,需要修改简单工厂类,违背了开闭原则
  2. 如果产品子类过多,会导致工厂类非常的庞大,违反了高内聚原则,不利于后期维护(在工厂方法模式中得到了一定的克服)

适用情况:

  1. 所有的产品子类都有同一个父类(或接口),属于同一个产品系列
  2. 产品子类比较少的、创建操作比较简单

简单工厂模式又叫做静态工厂方法(Static Factory Method)模式,并不是23中设计模式之一

工厂方法模式: Factory Method

工厂方法模式的对简单工厂模式进行了抽象。有一个抽象的Factory类(可以是抽象类和接口),这个类将不在负责具体的产品生产,而是只制定一些规范,将实际创建工作推迟到子类去完成。

在这个模式中,工厂类和产品类往往可以依次对应。即一个抽象工厂对应一个抽象产品,一个具体工厂对应一个具体产品,这个具体的工厂就负责生产对应的产品。

该模式中包含的角色及其职责

抽象工厂(Creator)角色

工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。

具体工厂(Concrete Creator)角色

这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建产品对象。

抽象产品(Product)角色

工厂方法模式所创建的对象的父类型,也就是产品对象的共同父类或共同拥有的接口。

具体产品(Concrete Product)角色

这个角色实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往一一对应。

优点:

  1. 客户端不负责对象的创建,而是由专门的工厂类完成;
  2. 客户端只负责对象的调用,实现了创建和调用的分离,降低了客户端代码的难度;
  3. 若增加和减少产品子类,不需修改工厂类,只需增加产品子类和工厂子类,符合开闭原则。
  4. 即使产品子类过多,不会导致工厂类的庞大,利于后期维护

缺点:

需要额外的编写代码,增加了工作量

适用情况:

  1. 所有的产品子类都有同一个父类(或接口),属于同一个产品系列
  2. 产品子类比较多的、创建操作比较复杂

单例模式: Singleton

含义:一个类只能有一个对象

单例模式只能有一个实例

单例类必须自己创建自己的唯一实例

单例类必须给所有其他对象提供这一实例

实现:

  • 饿汉单例模式:
    • 第一次加载类的时候就创建对象
    • 优点:安全,保证单例
    • 缺点:创建对象时机早了,只要加载该类,即使没有获取对象,也会创建对象
  • 懒汉式单例模式
    • 只有第一次获取对象时,才会创建对象
    • 优点:不会有闲置不使用的单例对象
    • 缺点:不安全(可采取措施)

私有的构造方法

private Singleton(){}

避免外部创建对象

公共静态方法来返回对象

public static Singleton getInstance(){
    return instance;
}

私有的静态属性(当前类的使用)

private static Singleton instance;

外观模式:Facade

外观模式为子系统中的一组接口提供了一个一致的界面,此模式定义了一个高层接口,这个接口使子系统更加容易使用

通过外观对象来组织细粒度的服务的调用,外观对象提供给外部应用程序以使用的服务,而具体的调用细粒度的过程则被外观对象给封装起来,当然这个过程就是封装变化的部分,而将变化的部分与应用程序进行隔离,无疑对程序的易用性和可维护性都是很大的提高。

该模式中包含的角色及其职责

  1. 门面角色( facade ):这是门面模式的核心。它被客户角色调用,因此它熟悉子系统的功能。它内部根据客户角色已有的需求预定了几种功能组合。
  2. 子系统角色:实现了子系统的功能。对它而言, façade 角色就和客户角色一样是未知的,它没有任何 façade 角色的信息和链接。对于子系统而言,门面仅仅是另外一个客户端而已。
  3. 客户角色:调用 façade 角色来完成要得到的功能

外观模式的最大的特点将细粒度的对象包装成粗粒度的对象,应用程序通过访问这个外观对象,来完成细粒度对象的调用,这样无疑会降低应用程序的复杂度,并且提高了程序的可维护性

优点:

  • 松散耦合
    • 门面模式送伞了客户端与子系统耦合关系,子系统内部模块能更容易扩展和维护
  • 简单易用
    • 门面模式让子系统更加易用,客户端不再需要了解子系统内部的实现,也不需要跟众多子系统内部的模块进行交互,只需要跟门面类交互就可以了
  • 更好的划分访问层次
    • 通过合理使用Facade,可以帮助我们更好地划分访问的层次。有些方法是对系统外的,有些方法是系统内部使用的。把需要暴露给外部的功能集中到门面中,这样既方便客户端使用,也很好地隐藏了内部的细节

缺点:

外观模式对外部系统提供的接口是有限的,从这个角度上讲,限制了外部系统对子系统调用的灵活性

适用情况:

  • 为一个复杂的子系统提供一个简单的接口
  • 提高子系统的独立性和可移植性
  • 在层次化结构中,可以使用Facade模式定义系统中每一层的入口

外观模式是在复杂系统中为子系统提供方便一致的对外接口,但这并不妨碍外部系统和子系统直接交互,外部系统可穿越外观而直接与子系统交互。

装饰模式:Decorator

装饰模式实在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能,它通过创建一个包装对象,也就是装饰来包裹真实对象,提供了比继承更具弹性的代替方案

装饰模式一般涉及到的角色

  • 抽象构建角色
    • 给出一个抽象的接口,以规范准备接受附加责任的对象
  • 具体的构建角色
    • 定义一个将要接受附加责任的类
  • 抽象的装饰角色
    • 持有一个抽象构建角色的引用,并定义一个与抽象构建一致的接口
  • 具体的装饰角色
    • 负责给构建对象“贴上”附加的责任

优点:

  • Decorator模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性
  • 把类中的装饰功能从类中搬移出去,这样可以简化原有的类,有效的把类的核心功能和装饰功能区分开
  • 通过使用不同的具体装饰类以及这些装饰类的排列组合,库创造出很多不同行为的组合

缺点:

  • 比继承更加灵活机动的特性,也同时意味着更多的复杂性
  • 装饰模式会导致设计中出现许多小类,如果过度使用,会使成都变得很复杂

符合的设计原则:

  • 多用组合,少用继承,利用继承设计子类的行为实在编译时静态决定的,且所有的子类都会继承到相同的行为,如能利用组合扩展对象的行为,就可在运行时动态进行扩展
  • 类应设计的对扩展开放,对修改关闭

适用情况:

  • 需要扩展一个类的功能,或给一个类添加附加职责
  • 需要动态的给一个对象添加功能,这些功能可以再动态的撤销
  • 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实
  • 当不能采用生成子类的方法进行扩充时,一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长,另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类

Decorator模式采用对象组合而非继承的手法,实现了在运行时动态的扩展对象功能的能力,而且可以根据需要扩展多个功能,避免了单独使用继承带来的“灵活性差”和“多子类衍生问题”。同时它很好地符合面向对象设计原则中“优先使用对象组合而非继承”和“开放-封闭”原则。

责任链模式COR

原型模式prototype

通过cloneable对类进行标记

重写Object的clone()方法,返回Object对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值