六大原则

开闭原则

总原则

  • WHAT
    • 一个软件实体如类、模块或函数应该对扩展开放,对修改关闭。即扩展->修改原有❌ 扩展原有✔️
  • WHY

    • 对测试友好
    • 提高复用性和可维护性
    • 面向对象开发的要求

      单一职责原则

      Single Responsibility Principle,SRP

  • WHAT

    • 应有且仅有一个原因引起类的变更
  • 优点
    • 类复杂性降低
    • 可读性高
    • 易维护
    • 变更引起的风险降低
  • 难点
    • “职责”和“变化原因”都是不可度量的,因项目、环境而异
    • 过细的划分会引起类的剧增,人为的增加系统的复杂性
  • 建议

    • 接口的设计一定要做到单一原则,类的设计尽量做到只有一个原因引起变化
    • 职责的划分需要根据项目和经验来权衡,既要保证职责的单一性,又要尽量避免过细的划分

      里氏替换原则

      Liskov Substitution Principle,LSP

  • WHAT

    • 所有引用基类的地方都必须能透明地使用其子类的对象(继承)
  • 优点
    • 代码共享,提高代码的重用性
    • 提高代码的可扩展性
    • 提高产品或者项目的开放性
  • 缺点
    • 继承是侵入式的,只要继承,就拥有了父类的属性和方法。
    • 降低代码灵活性,子类拥有了父类的属性和方法,多了一些约束。
    • 增强了耦合性。父类的常量、变量或方法改动时,必须还要考虑子类的修改,可能会有大段代码需要重构。
  • 四层含义

    • 子类必须完全实现父类的方法,在类中调用其他类时务必使用父类或接口,如若不能,则说明类的设计已经违背LSP原则。如果子类不能完整的实现父类的方法,或者父类的方法在子类中发生畸变,建议断开父子继承关系,采用依赖、聚集、组合等方式代替继承
    • 子类可以有自己的特性:即子类出现的地方父类未必可以出现
    • 覆盖父类的方法时输入参数可以被放大:输入参数类型宽于父类的类型的覆盖范围,例如 hashmap -> map
    • 覆盖父类的方法时输出参数可以被缩小

      依赖倒置原则

      Dependence Inversion Principle,DIP

  • WHAT

    • 高层模块不应该依赖低层模块,两者都要依赖其抽象(模块间的依赖通过抽象产生,实现类不发生直接的依赖关系),抽象不应该依赖细节(接口或者抽象类不依赖实现类),细节可以依赖抽象(实现类依赖接口或者抽象类)
  • 建议
    • 每个类尽量都有接口或抽象类
    • 变量的表面类型尽量是接口或抽象类
    • 任何类都不应该从具体类派生(其实只要不是超过两层的继承都是可以忍受的)
    • 尽量不要复写基类已实现的方法
    • 结合里氏替换原则使用
  • 面向接口编程

    • 接口负责定义 public 属性和方法,并且声明与其它对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确实现业务逻辑,同时在适当的时候对父类进行细化。

      接口隔离原则

  • WHAT

    • 客户端不应该依赖他不需要的接口,类之间的依赖关系应该建立在最小的接口上
  • 四层含义
    • 接口尽量要小,不要出现臃肿的接口
    • 接口要高内聚
    • 只提供访问者需要的方法:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分
    • 接口设计是有限度的:接口设计粒度越小,系统越灵活。但是结构会越复杂、开发难度增加,可维护性降低
  • 建议

    • 一个接口只服务一个子模块或者业务逻辑。
    • 尽量压缩接口内的方法,保证方法都是有用的,避免臃肿。
    • 已经被污染的接口尽量去修改,若变更风险大,则采用适配器模式转化处理。
    • 深入了解业务逻辑,拒绝盲从。

      迪克特法则

      最少知道原则,Least Knowledge Principle,LKP

  • WHAT

    • 一个对象应该对其他对象有最少的了解(低耦合)
  • 三层含义
    • 一个类只与朋友交流,不和陌生类交流,方法尽量不引入类中不存在的对象
    • 尽量不要对外暴露过多的 public 方法和非静态的 public 变量,尽量内敛
    • 自己的就是自己的。如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中
  • 总结

    • 核心观念就是类间解耦,低耦合。其负面影响就是产生了大量的中转或者跳转类,导致系统复杂性提高,也为维护带来了难度。需要反复权衡,既做到结构清晰,又要高内聚低耦合
    • 如果一个类需要跳转两次以上才能访问到另一个类,就需要想办法重构了

      合成复用原则

      Composite/Aggregate Reuse Principle,CARP

  • WHAT

    • 是在一个新的对象里面使用一些已有的对象,使其成为新对象的一部分。新对象通过委派达到复用已有功能的效果
  • 优点
    • 使用对象的合成/聚合将有助于你保持每个类被封装,并被集中在单个任务上。这样类和集成层次会保持较小规模,并且不太可能增长为不可控制的庞然大物
  • 缺点
    • 通过这种方式复用建造的系统会有较多的对象需要管理
    • 为了能将多个不同的对象作为组合块来使用,必须仔细地对接口进行定义
  • 建议

    • 尽量首先使用合成/聚合的方式,而不是使用继承

      设计模式的分类

      创建型模式(5)

  • 单例模式

  • 工厂方法模式
  • 抽象工厂模式
  • 建造者模式
  • 原型模式

    结构型模式(7)

  • 适配器模式

  • 装饰者模式
  • 代理模式
  • 门面模式(外观模式)
  • 桥梁模式
  • 组合模式
  • 享元模式

    行为型模式(11)

  • 策略模式

  • 模版方法模式
  • 观察者模式
  • 迭代器模式
  • 责任链模式
  • 命令模式
  • 备忘录模式
  • 状态模式
  • 访问者模式
  • 中介者模式
  • 解释器模式

    扩展模式(5)

  • 规则模式

  • 对象池模式
  • 雇工模式
  • 黑板模式
  • 空对象模式

    GoF 23种设计模式

    1、单例(Singleton)模式
    某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
    2、原型(Prototype)模式
    将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
    3、工厂方法(Factory Method)模式
    定义一个用于创建产品的接口,由子类决定生产什么产品。
    4、抽象工厂(Abstract Factory)模式
    提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
    5、建造者(Builder)模式
    将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
    6、代理(Proxy)模式
    为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
    7、适配器(Adapter)模式
    将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
    8、桥接(Bridge)模式
    将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
    9、装饰(Decorator)模式
    动态的给对象增加一些职责,即增加其额外的功能。
    10、外观(Facade)模式
    为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
    11、享元(Flyweight)模式
    运用共享技术来有效地支持大量细粒度对象的复用。
    12、组合(Composite)模式
    将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
    13、模板方法(TemplateMethod)模式
    定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
    14、策略(Strategy)模式
    定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
    15、命令(Command)模式
    将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
    16、职责链(Chain of Responsibility)模式
    把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
    17、状态(State)模式
    允许一个对象在其内部状态发生改变时改变其行为能力。
    18、观察者(Observer)模式
    多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
    19、中介者(Mediator)模式
    定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
    20、迭代器(Iterator)模式
    提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
    21、访问者(Visitor)模式
    在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
    22、备忘录(Memento)模式
    在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
    23、解释器(Interpreter)模式
    提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。

    单例模式

    当前进程有且只有一个实例

为什么要用:节约系统内存、资源new gc、有些类不能创建多个、资源复用

  • 饿汉式

    1. public class Singleton {
    2. private statis Singleton instance = new Singleton();
    3. private Singleton (){}
    4. public static Singleton getInstance() {
    5. return instance;
    6. }
    7. }
  • 懒汉式(线程不安全)

    1. public class Singleton {
    2. private static Singleton instance;
    3. private Singleton(){}
    4. public static Singleton getInstance() {
    5. if (instance == null) {
    6. instance = new Singleton();
    7. }
    8. return instance;
    9. }
    10. }
  • 懒汉式(线程安全——效率低)

    1. public class Singleton() {
    2. private static Singleton instance;
    3. private Singleton(){}
    4. public static synchronized Singleton getInstance() {
    5. if (instance == null) {
    6. instance = new Singleton();
    7. }
    8. return instance;
    9. }
    10. }
  • 懒汉式(线程安全——双重校验)

    1. public class Singleton() {
    2. private volatile static Singleton instance = null;
    3. private Singleton(){
    4. if (instance == null) {
    5. }
    6. }
    7. public static Singleton getInstance() {
    8. if (instance == null) {
    9. synchronized(Singleton.class) {
    10. if (instance == null) {
    11. instance = new Singleton();
    12. }
    13. }
    14. }
    15. return instance;
    16. }
    17. }
  • 静态内部类(私有静态内部类持有私有静态内部变量)

    1. public class Singleton() {
    2. private static class SingletonFactory {
    3. private static Singleton instance = new Singleton();
    4. }
    5. public static Singleton getInstance() {
    6. return SingletonFactory.instance;
    7. }
    8. public Object readResolve() {
    9. return getInstance();
    10. }
    11. }
  • 枚举

    1. public enum Singleton {
    2. INSTANCE;
    3. public void doSomething() {
    4. System.out.println("doSomething");
    5. }
    6. }