原则
单一职责原则
- 概念:一个类应该只有一个改变他的原因,职责就是指类变化的原因。
接口隔离原则
- 概念:客户端不依赖他不需要的接口,类之间的依赖关系应该建立在最小的接口上。
- 类接口:interface 定义的接口
- 实例接口:Class 定义的类也是一种接口
单一职责原则 和 接口隔离原则 的区别
- 单一职责原则是根据不同业务逻辑,将系统功能模块划分成不同种类
- 接口隔离原则就是指一个类对另一个类的依赖应该建立在最小的接口上
- 所以单一职责原则注重的职务的划分,而接口隔离原则注重的是类对接口的依赖的隔离
开闭原则
里氏替换原则
- 概念:所有使用父类的地方都必须要能够使用他的子类,即尽量避免使用方法覆写(重写),因为这样很有可能会使子类无法零影响的替换父类
- 规则:子类实现父类方法,方法输入参数要更加宽松或相等;返回值要比父类严格或相等
- 如果子类中某些方法已经发生”畸变“,建议不使用继承,而使用依赖、聚集、组合等关系代替
依赖倒置原则
- 概念:高层模块不应该依赖底层模块,都应该依赖抽象。抽象不应该依赖细节,细节依赖抽象。即模块间通过接口或抽象类使彼此独立,不相互影响,实现松耦合
- 实现方式:构造注入:构造方法声明依赖对象。setter 注入:类中通过 setter 方法声明依赖关系。接口注入:在接口中声明依赖对象
组合复用原则
- 概念:优先使用组合 contains a(聚合 has a),而不是继承 is a 来达到目的
- 原因:
- 继承会将实现细节暴露给子类,继承复用破坏了封装性,是白箱复用
- 使用继承时需要考虑里氏替换原则
- 优点:
- 新类对象存取成员对象只通过成员对象的接口,是黑箱复用,系统更灵活,降低耦合度
- 可以在运行时动态进行,新对象可动态引用与成员对象类型相同的对象
- 缺点:需要管理较多对象
迪米特法则
- 概念:一个对象应当对其他对象有尽可能少的了解,即不和陌生人说话
- “朋友圈”概念(以下情况是该对象朋友):
- this
- 该对象方法中的参数
- 实例变量直接引用的对象
- 实例变量如果是一个聚集(聚合对象),聚集中的元素
- 该对象方法中创建的变量
- 要求:
- 优先考虑将一个类设计成不变类
- 尽量降低一个类的访问权限
- 谨慎使用 Serializable(持久化,通过序列化一个对象,将其写入磁盘,以后程序调用时重新恢复该对象)
- 尽量降低成员的访问权限
- 优点:降低类之间的耦合
- 缺点:过多使用迪米特法则,会产生大量中介类,设计变复杂
创建型模式
简单工厂模式(静态工厂方法模式)
- 定义:定义一个工厂类,该类能够根据不同参数返回不同类的实例,被创建的实例具有公共的父类
- 结构:
- 抽象产品类:负责定义具体产品的公共接口
- 具体产品类:抽象产品的子类,所有创建的对象都是具体产品类的实例
- 静态工厂类:实现创建具体产品的内部逻辑,可以被外界直接调用
- 优点:
- 不需要记住具体类名,仅仅记住产品名即可,也不用关心具体创建对象的细节,减轻编程负担
- 创建对象由工厂进行,把类的定义和创建解耦,更加灵活
- 缺点:
- 不利于扩展,扩展要修改静态工厂类,违反开闭原则
工厂方法模式(虚拟构造器,多态工厂模式)
- 定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类,使一个类的实例化延迟到其子类
- 结构:
- 抽象产品类:定义所有具体产品的公共接口。
- 具体产品类:工厂方法模式中所创建的对象就是某个具体产品类的实例,具体产品和具体工厂需要一一对应。
- 抽象工厂类:一个接口(或抽象类),声明工厂方法返回一个抽象产品。是该模式的核心。
- 具体工厂类:抽象工厂的子类,实现了抽象工厂中定义的方法,返回一个具体产品类实例。
- 优点:
- 将客户类和实际创建具体产品代码分开,实现解耦
- 利用该模式,添加新产品时,仅仅需要添加一个具体工厂和对应的产品即可,可扩展性好,符合开闭原则
- 缺点:需要添加的类比较多
抽象工厂模式(Kit模式)
- 概念
- 产品等级结构: 产品的继承结构. 如: 抽象武器类, 抽象子弹类之间构成了一个产品等级结构
- 产品簇: 同一个工厂生产的, 位于不同产品等级结构中的一组产品 如: 步枪 和 步枪子弹; 手枪 和 手枪子弹 都是产品簇
- 抽象工厂模式: 提供一个一系列相关或相互依赖对象的接口, 而无需指定他们具体的类
- 区别
- 抽象工厂模式 与 工厂方法模式 工厂方法模式针对的是一个产品等级结构(如 武器), 而抽象工厂模式则需要针对多个产品等级结构(如 武器和子弹)
- 结构
- 抽象产品类:一般有多个, 每个抽象产品定义一个产品等级(产品簇)
- 具体产品类:工厂方法模式中所创建的对象就是某个具体产品类的实例,具体产品和具体工厂需要一一对应。
- 抽象工厂类:一个接口(或抽象类),与抽象产品类一一对应, 一个抽线工厂对应一个产品簇, 每一个工厂方法生产一个产品等级
- 具体工厂类:抽象工厂的子类,实现了抽象工厂中定义的方法,每个工厂方法返回一个产品等级。
- 优点
- 相比于工厂方法模式, 减少了许多类的设计, 实现高内聚低耦合
- 一个产品簇中的多个对象(不同等级)被设计到一起工作, 就保证客户端只使用同一个产品簇中的对象 对需要根据当前环境决定软件行为来说, 非常实用 (如: qq换肤等)
- 增加新的 产品簇 十分方便, 仅仅需要增加具体工厂和对应的产品等级即可, 无需修改源代码 符合开闭原则
- 缺点
- 增加产品簇相对容易, 但改变产品等级结构十分困难, 需要修改其他所有工厂的源代码, 违背开闭原则, 即:开闭原则具有倾斜性
- 应用场景
- 无需关系对象创建过程, 实现对象创建和使用解耦
- 每次使用 同一产品簇中的不同等级的产品 而不是使用每个产品簇中的同级产品
- 产品等级结构稳定, 不会增加或删除已有的等级结构
建造者模式(生成器模式)
- 定义: 将一个复杂对象的构建与他的表示分离, 使得同样的构建过程可以创建不同的表示 用户只需要指定对象的类型即可得到该对象, 而无需知道其内部的具体构造细节
- 结构
- 产品类(Product): 包含多个组成部件(属性), 是具体建造者要构造的复杂对象, 具体建造者定义他的装配过程
- 抽象建造者(Abstract Builder): 是一个接口(抽象类), 定义了创建一个产品对象各个部件的方法, 其中声明两类方法,
buildPartX()
用于创建复杂对象的各个组件, 另一类 getResult()
, 用于返回创建好的复杂对象 - 具体建造者(Concrete Builder): 抽象建造者的子类, 用于实现具体的建造方法. 根据需要可以创建多个具体建造者, 用于建造不同的复杂对象
- 指挥者(Director): 指挥者/导演类, 安排复杂产品和各个部件的建造顺序. 与 AbstractBuilder 是关联关系, 可以在 Director 的 build() 方法中调用建造者对象的方法, 完成对象的构建 在客户端确定 ConcreteBuilder 的类型, 并实例化, 然后通过 Director 中的 构造方法 / setter 方法, 将该对象传入 Director 中
- 优点
- 将本身与产品 创建过程解耦, 使得相同的创建过程可以创建不同的具体对象
- 每个 ConcreteBuilder 都是独立的, 可以很方便的添加或删除 Product
- 增加新类, 不需要修改源代码, 符合开闭原则
- 缺点
- 创建的所有产品组成类似, 若产品间差异过大, 则不适合使用该模式
- 应用场景
- 生成的产品对象的属性相互依赖, 具有复杂的内部结构并且需要指定其属性的生成顺序
- 对象的创建过程独立于创建该对象的类, 创建产品过程封装在 Director 中, 通过 Director 来创建对象
- 相同的创建过程可以创建不同的具体对象
- 实例
原型模式
- 概念:使用原型实例创建对象的类型,并且复制这个原型来创建新的对象。即, 使用 clone 方法来创建新对象,而不是 new
- 实现方式
- 浅克隆: 实现 clone 方法时, 只克隆普通变量, 而不会去克隆引用变量, 即 旧实例 与 新实例 中的引用变量引用的是同一个
- 序列化: 具体对象实现 Serializable(序列化) 接口, 通过 对象流 和 字节数组流 来完成新实例的创建
- 深克隆: 实现 clone 方法时, 所有变量都进行克隆, 即引用变量也需要重写 clone 方法
- 结构
- 抽象原型类: 可以是抽象类或者接口,声明克隆方法. 如: Object 类
- 具体原型类: 实现抽象原型中声明的 clone 方法, 该方法返回一个自己的 clone 对象
- 优点
- 简化对象的创建过程, 创建新实例的代价过大是可以考虑复制一个原有的实例来提高创建新实例的效率
- 动态的保存当前对象的状态, 运行时可以随时使用 clone 来保存一个当前实例
- 不需要创建继承结构
- 缺点
- 需要为每一个类的内部定义 clone 方法, 对类改造时, 需要修改源代码, 违反开闭原则
- 实现深克隆是需要编写复杂的代码, 对象之间有多重嵌套调用时, 实现比较复杂
- 应用
- 一个程序需要从一个对象出发, 得到若干个和其初始状态相同的对象时
- 一个对象被若干个对象访问, 各个调用者修改不同属性, 可以考虑使用原型对象复制多个对象供调用者使用, 即 采用采用保护性拷贝
- 实例
单例模式
- 概念: 确保一个类只有一个实例, 并 提供一个全局访问点来访问这个唯一的实例
- 特征:
- 只有一个实例
- 必须类自己创建这个实例
- 必须自行向全局提供这个实例
- 结构:
- 单例类
- 饿汉式: 直接类中加载单例对象, private 构造方法, 提供一个 public 的 getInstance() 方法
- 特点:
- 不需要考虑多个线程同时访问的问题
- 调用速度和时间要优于懒汉式(不需要加锁)
- 资源利用效率较低, 系统加载时间长
- 懒汉式: 类中先不加载单例对象, private 构造方法, 提供一个 public 加 synchronized 的 getInstance() 方法, 在其中先判断单例是否存在, 不存在则创建
- 特点:
- 实现延迟加载
- 必须处理多个线程同时访问的问题(需要加锁)
- 加锁检查等机制进行控制, 导致系统性能受到影响
- 静态内部类: 类中创建一个私有静态内部类, 内部类中创建静态单例对象, 然后 getInstance() 方法中就不需要加锁了
- 特点:
- 优点
- 开发团队可以共享对象
- 节约系统资源, 提高性能
- 对单例模式进行扩展, 可以获得指定个数的单例对象
- 缺点
- 单例模式没有抽象层, 扩建相对麻烦
- 违背单一职责原则, 既充当工厂又充当产品角色
- 共享连接池程序太多导致连接池溢出, Java 的垃圾自动回收机制, 共享的单例对象长时间不使用会被系统默认回收
- 应用
- 网站计数器
- 项目中读取配置文件的类
- 数据库连接池
- Spring 中的 bean 实例
- Windows 中的任务管理器, 回收站
模板方法模式
- 概念: 定义一个算法的框架, 即模板方法 , 将一些步骤延迟到子类中, 使得子类可以不改变一个算法的结构而重定义一个算法的某些步骤. 即, 子类修改抽象父类方法的内容, 从而实现多种不同的执行结果
- 结构:
- 抽象模板: 是一个抽象类, 其中定义了若干方法(抽象 和 非抽象方法), 还定义了一个模板方法用于定义一个算法的框架, 模板方法可以调用该类中的各种方法
- 包含: 具体方法 抽象方法 钩子方法 模板方法
- 钩子方法一般命名规则是 isXxx() 或 hasXxx(), 返回值为 boolean 类型
- 可以通过钩子方法来判断模板方法中的某一步骤是否执行
- 具体模板: 抽象模板的子类, 实现在父类中定义的抽象方法, 也可以覆盖父类中已经定义的非抽象方法
- 优点
- 形式化的定义一个算法执行的顺序, 细节交给子类处理
- 使用继承来实现代码复用
- 符合开闭原则 和 单一职责原则
- 缺点
- 如果模板方法中抽象方法比较多,需要 为每个可变的基本方法定义一个子类, 增加系统设计的复杂性
- 应用
- 若干个子类用同样的行为, 可以在父类中抽取出来, 减少代码重复
- 通过子类控制父类的方法 是否执行(钩子 方法)
行为型模式
命令模式
- 概念: 对请求进行封装, 一个请求对应一个命令, 把请求方和接收方分开, 请求方不需要知道执行细节
- 结构:
- 接收者(Receiver): 执行请求相关操作
- 抽象命令接口(AbstractCommand): 封装若干个请求方法 execute(), 封装接收者对象
- 具体命令(ConcreteCommand): 实现抽象命令接口的子类
- 请求者(Invoker): 负责调用具体命令, 封装抽象命令对象
- 优点:
- 降低耦合度. 请求者 不直接与 接收者 交互, 不互相包含引用
- 符合开闭原则, 增加新的具体命令和接收者, 不需要修改源代码, 只需要增加具体命令类即可
- 设计命令队列, 组合命令
- 为 撤销 和 恢复 操作提供一种方法
- 缺点:
- 具体命令类可能会比较多
策略模式
- 概念: 定义一系列算法, 将每个算法封装起来, 并让他们可以相互替换. 让算法独立于使用它的客户而适应变化
- 结构:
- 抽象策略类(AbstractStrategy): 一个接口或抽象类, 定义若干抽象方法, 运行时根据多态调用其子类的具体策略中的方法
- 具体策略类(ConcreteStrategy): 实现抽象策略类的方法, 运行时覆盖环境类中的抽象策略类对象, 实现具体算法
- 环境类(Context): 使用算法的角色, 引用且依赖 AbstractStrategy(setter 方法注入) . 定义一个方法来调用 ConcreteStrategy 所实现 AbstractStrategy 中的方法
- 优点:
- 支持开闭原则, 不修改原来算法, 添加新算法
- 提供了管理算法簇的一种方案, 公共代码移动到 AbstractStrategy 中
- 避免多重条件语句的硬编码
- 提供了一种算法复用的方案
- 缺点
- 客户端必须知道所有的策略类
- 会产生很多策略类
- 无法同时使用多个策略类,不支持使用一个策略类完成部分功能然后再使用另一个策略类完成剩下的功能
观察者模式(发布订阅模式)
- 概念:定义对象支架你的一对多关系, 使得每当一个对象发生改变时, 其相关依赖对象都得到通知并自动
- 结构:
- 目标/被观察者(Subject): 指被观察的对象, 可以是接口或者抽象类, 具体类. 拥有一个观察者集合, 并且提供增加, 删除观察者的方法, 同时也提供通知方法通知具体或所有观察者接收信息
- 具体目标(ConcreteSubject): 包含经常需要改变的数据, 如果不需要经常改变, 可以省略
- 抽象观察者(Observer): 定义对观察目标 Subject 的改变做出反应的 方法 update
- 具体观察者(ConcreteObserver): 维护一个指向具体目标的引用, 存储具体观察者的有关状态
- 优点:
- 实现表示层与数据层分离
- 被观察者只需要维护一个存放观察者的集合,使得这两者没有紧密耦合
- 简化一对多设计,即支持广播通信
- 增加新的观察者无需修改当前代码, 即符合开闭原则
- 缺点:
- 如果观察者过多,通知时间可以比较久
- 如果观察者和被观察者之间有依赖循环, 那么会产生死锁,导致系统崩溃
- 应用:
- 对象 A 的改变将导致一个或多个其他对象改变, 但 A 不知道要改变的对象以及个数
- 可以创建触发链, 即 A 对象影响 B 对象, B 对象影响 C 对象……
状态模式
- 概念:允许一个对象在内部状态改变时改变它的行为
- 结构
- 抽象状态(AbstractState): 接口或抽象类, 定义与环境相关的若干个方法
- 具体状态类(Concrete State): 实现具体的状态业务
- 环境类(Context): 含有抽象状态类的引用, 可以引用具体状态类实例
- 优点:
- 将所有与状态相关的行为封装在一个类中, 只需要注入一个不同的状态对象即可是环境对象有不同的行为
- 将状态具体为类, 使得状态在环境类中更容易被改变
- 缺点:
- 状态较多时会增加很多类
- 不支持开闭原则, 增加新状态需要修改环境类代码
- 应用:
- 对象的行为依赖于它的状态(属性), 状态的改变将导致行为的变化
责任链模式
- 概念: 将对象连成一条链, 并沿着这条链传递请求, 直到一个对象处理他为止
- 结构:
- 抽象处理者(Handler): 定义抽象请求处理者类型的对象和抽象处理请求的方法, 用于引用后继处理者
- 具体处理者(ConcreteHandler): 处理具体请求, 如果处理不了就交个一下个处理
- 形式
- 纯责任链模式:
- 一个具体请求者对象要么承担全部责任, 要么给后继请求者, 不允许出现某一个具体处理者承担一部分职责再向后传递
- 请求必须被一个处理者给接收
- 不纯责任链模式:
- 允许一个处理者处理部分职责后传递下一个, 或处理完之后可以继续向下传递
- 一个请求可以最终不被任何处理者接收
- 优点:
- 客户端不需要创建链, 只需要组织链, 降低耦合度
- 只需要指向下一个后继者, 简化处理对象的连接
- 链式结构增删非常灵活
- 符合开闭原则
- 缺点:
- 较长的责任链会导致性能收到影响, 不方便调试
- 应用:
- 不确定指定接收者时, 可以向多个接收者发送请求
结构型模式
装饰器模式
- 概念: 动态的为一个对象增加一些额外的职责, 就扩展功能而言, 装饰模式提供了一种比使用子类更加灵活的方案 使用对象之间的关联关系取代类之间的继承关系
- 结构:
- 抽象构件(Abstract Component): 是具体构件和抽象装饰类的共同父类, 声明在具体构建中实现的方法.
- 具体构件(Concrete Component): 抽象构件的子类, 用于定义具体的构件对象, 可以通过具体装饰器给他增加额外的方法.
- 抽象装饰类(Abstract Decorator): 抽象构件的子类, 用于给具体构件添加方法, 但是具体方法在子类中实现
- 具体装饰类(Concrete Decorator): 抽象装饰类的子类, 负责向具体构件添加职责
- 优点:
- 使用关联比继承更加灵活
- 可以对一个组件进行多次装饰
- 符合开闭原则
- 缺点:
- 会产生许多对象
- 应用
- 在不影响其他对象的情况下, 动态透明的给单个对象添加职责
- 在不能才能继承的时候
适配器模式
- 概念:让一个类的接口转换为客户希望的另一个接口,解决接口不兼容的问题。
- 结构:
- 目标抽象类(Target): 定义客户需要的接口, 可以是一个抽象类或者接口
- 适配器类(Adapter): 适配器类可以调用 Target 接口, 作为一个转换器, 对 Adaptee 进行适配, 是核心
- 对象适配器: Adaptee 和 Adapter 是关联关系
- 类适配器: Adaptee 和 Adapter 是继承关系
- 适配者类(Adaptee): 被适配的类, 可以是接口或者一个具体类, 包含客户需要使用的方法
- 优点:
- 符合开闭原则
- 适配的过程对外隐藏
- 缺点:
- java 、c# 中 Target 类只能是接口,不能为类、
- 属于是一种补救措施,在设计类前尽量不考虑使用该模式解决问题