View on GitHub

学习仓库

设计原则

核心思想

  1. 找出应用中的变化之处,把它们独立出来,不要和固定的代码混在一起。
  2. 针对接口编程,而不是针对实现编程。
  3. 为了交互对象之间的松耦合设计而努力。

七大设计原则

1. 单一职责原则

定义:每个方法、每个类、每个框架只负责一项职责。

目的:

  1. 降低复杂度,降低变更引起的风险。
  2. 提高可读性、可维护性。

2. 接口隔离原则

定义:客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小接口上。

反例

不满足接口隔离原则

正例

满足接口隔离原则

3. 开闭原则(Open Closed Principle,OCP)

定义:

  1. 开闭原则是最基础、最重要的设计原则
  2. 软件中的实体(类,模块,函数等等)对扩展是开放的(对提供方),但对修改是封闭的(对使用方)。
  3. 当软件有需求变化时,是通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
  4. 编程中遵循其他原则,以及使用设计模式的目的,就是为了遵循开闭原则。

举例:原本有一个汽车类,现在需求是要给汽车的价格打8折。

public class Car{
    private BigDecimal price;
    public BigDecimal getPrice(){
        return price;
    }
}

反例:直接修改源代码。

public class Car{
    private BigDecimal price;
    public BigDecimal getPrice(){
        return price * 0.8;
    }
}

正例:扩展一个子类实现需求。

public class DiscountCar extends Car{
    @Override
    public BigDecimal getPrice(){
        return price * 0.8;
    }
}

4. 依赖倒转(置)原则

定义:

  1. 高层模块不应该依赖低层模块,二者都应该依赖其抽象。
  2. 抽象不应该依赖细节,细节应该依赖抽象。
  3. 依赖倒转的中心思想是面向接口编程。

反例

不满足依赖倒转原则

正例

满足依赖倒转原则

5. 里氏替换原则(Liskov Substitution Principle)

定义:

  1. 子类可以替换父类;任何父类可以出现的地方,子类一定可以出现。
  2. 里氏替换原则是继承复用的基石:只有当子类可以替换父类且软件单位的功能不受到影响时,基类才能真正被复用。

注意:

  1. 能否使用继承依据:是否有"is a"关系?子类替换父类后,业务逻辑是否变化?如果业务逻辑发生变化则不能使用继承(经典问题:正方形非长方形)。
  2. 继承带来便利也带来了弊端。如:使用继承给程序带来侵入性,程序可移植性降低,增加对象间的耦合性。修改一个父类,必须考虑所有子类,修改父类可能会导致子类功能产生故障。
  3. 优先使用组合或者聚合来解决问题。

6. 迪米特法则(Law of Demeter)

定义:

  1. 又叫“最少知道原则”:即一个类对自己依赖的类知道的越少越好。
  2. 更简单的定义:只与朋友通信。(朋友:出现在成员变量的类,方法参数的类,方法返回值中的类,自己new出来的类;非朋友:出现在局部变量的类。)

反例

不满足迪米特法则

正例

满足迪米特法则

注意:

  1. 迪米特法则的核心是降低类之间的耦合;只是要求降低类间耦合,并不是要求完全没有依赖关系(这是不可能的)。
  2. 缺点:要完全符合迪米特法则,会在系统里造出大量的小方法,这些方法仅仅是传递间接的调用,与系统的业务逻辑无关。
  3. 结论:可以适当违反。

7. 组合优于继承

定义:在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

JDK中的反例:JDK中的Stack,继承了Vector,仅仅为了复用一些方法,搞得Stack拥有不属于栈特性的方法;只能说Stack像一个栈,而不是一个纯粹的栈。

注意:

  1. 如果父类作者与子类作者不是同一个人,就别用继承;因为父类不知道未来子类会重写自己的哪个方法;子类也不知道未来父类会新增哪个方法。
  2. 如果父类作者与子类作者是同一个人,就放心使用继承;因为作者同时控制父类和子类。
  3. 如果仅仅为了复用代码而继承别人的类,难免会出现“沟通”上的问题。