精炼总结设计模式六大原则

1.单一职责原则(Single Responsibility Principle)

There should never be more than one reason for a class to change.
应该有且仅有一个原因引起类的变更。这里也包括接口、方法。

优点
  1. 降低类的复杂性。职责单一,定义明确,自然就变得简单。由此也陆续引出以下的优点。
  2. 提高可读性
  3. 提高可维护性。职责单一,将不会出现,修改职责A的逻辑会影响到不需要改变的职责B的逻辑。
  4. 提高扩展性
类的单一职责原则

比如,对象的属性和行为,就应该放到两个类里处理。比如对于用户,用户的属性:id、name、age就应该放到UserBO中。而对于用户行为的处理:添加权限、修改密码,就应该放到UserBiz类中处理。因为如果都放到同一个类中处理,则属性和行为的变更都会引起类的变更,便不符合有且只有一个原因引起变更。

方法的单一职责原则

原方法:changeUserInfo()。其中,修改用户信息,同时修改了用户的名称、密码、联系方式等等。应改为:

  • changeUserName()
  • changeUserPassWord()
  • changeUserPhone()

便于明确职责。

个人理解

如果职责划分过细,或者过于追求单一职责原则,会导致类和方法过多。因此,在贯彻单一职责原则的过程中,我们应该首先确认合适的职责范围,然后在按照单一职责原则进行设计。

2.里氏替换原则(Liskov Substitution Principle,LSP)

定义1:

如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换为o2,程序P的行为没有发生变化,那么类型S是类型T的子类型。

定义2:

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
所有引用基类的地方必须透明的使用其子类的对象。即:只要父类能出现的地方子类也可以出现,而且替换为子类不会产生任何错误或异常,但是反过来就不行,有子类出现的地方,父类未必就能适应

关于继承

实质上,里氏替换原则是对继承的使用做出了一定的规范。继承的优点在于,减少类创建的工作量、提高代码复用性、提高类的可扩展性等。但继承也有缺点,即:继承是侵入性的,只要继承,就必须拥有父类的所有方法和属性;降低了代码的灵活性,子类必须拥有父类的属性和方法,让子类有了一些约束;增加了耦合性,当父类的常量,变量和方法被修改了,需要考虑子类的修改,这种修改可能带来非常糟糕的结果,要重构大量的代码。

里氏替换原则的四个规范

简单来说,就是子类可以扩展父类的功能,但不能改变父类原有的功能。
对于继承会导致的问题,里氏替换原则规范了四个规范。

  1. 子类必须完全实现父类的方法。(即正向,引用基类的地方必须能透明地使用子类对象,且子类不能随意覆盖父类的方法,这样导致子类无法完全替换父类)
  2. 子类可以有自己的个性。(即反向,必须引用子类的地方不能以基类代替)
  3. 覆盖或者实现父类的方法时输入参数可以被放大。(一旦输入参数不相同,那么就不是重写而是重载,那么如果想实现里氏替换原则,就必须让父类的参数更小,因为这样才可以让替换子类的时候仍使用该方法。例如:父类方法入参为实现类(HashMap),子类方法入参可以为接口(Map),或者子类方法入参(HashMap)为父类方法入参(ConcurrentHashMap)的父类)
  4. 覆盖或者实现父类的方法时输出结果可以被缩小。
反例

java.sql.Time类,继承了java.util.Date类。但是他重写了getDate()、getDay()等方法,直接抛出了java.lang.IllegalArgumentException()异常,破坏了里氏替换原则。

3.依赖倒置原则(Dependence Inversion Principle ,DIP)

High level modules should not depend upon low level modules,Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstracts.

  • 高层模块不应该依赖低层模块,两者都应该依赖抽象
  • 抽象不应该依赖细节
  • 细节应该依赖抽象。

依赖倒置原则在java语言中,表现为:

  • 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。
  • 接口或抽象类不依赖实现类
  • 实现类依赖接口或抽象类

依赖倒置原则的核心思想是面向接口编程!

举个例子

比如,作为一个苦逼的码农(Coder类),每天下班回家(goHome)都要骑摩拜。

public class Mobike {
    public void drive(){
        System.out.print("骑摩拜");
    }
}

public class Coder {
    public void goHome(Mobike mobike){
        mobike.drive();
        System.out.println("回家!");
    }
}

public void offDuty() {
    Coder coder = new Coder();
    Mobike mobike = new Mobike();
    coder.goHome(mobike);
}

运行dip方法,则会输出“骑摩拜回家!”
然而有一台,公司上市了!苦逼码农翻身做主人,财务自由了!于是他买了辆车,以后每天开车回家,美滋滋。

public class Car {
    public void drive(){
        System.out.print("开车");
    }
}

但是goHome方法的入参只能接受Mobike类型的交通工具,Coder和Mobike的耦合性过高,产生了强依赖,所以买了车也没法马上开。
如果我们的Coder一开始拥有这样的驾驶能力,小汽车和摩拜也这样设计呢?

public interface Vehicle {
    void drive();
}
public class Mobike implements Vehicle{
    public void drive(){
        System.out.print("骑摩拜");
    }
}
public class Car implements Vehicle{
    public void drive() {
        System.out.print("开车");
    }
}
public class Coder {
    public void goHome(Vehicle vehicle){
        vehicle.drive();
        System.out.println("回家!");
    }
}
public void offDuty() {
    Coder coder = new Coder();
    Car mobike = new Car();
    coder.goHome(mobike);
}

这样的话,我们的高端码农就可以肆意妄为的想骑车就骑车想开车就开车嘞。

依赖倒置原则的优点
  • 降低类之间的耦合性,提高系统的稳定性,降低修改程序造成的风险
  • 降低并行开发引起的风险与难度
依赖倒置原则的经验
  • 每个类尽量都有接口或者抽象类,或者抽象类和接口两都具备。在使用时尽量依赖接口或抽象类使用。
  • 变量的表面类型尽量是接口或者抽象类
  • 任何类都不应该从具体类派生(视情况而定)
  • 尽量不要覆写基类的方法 。覆盖基类的方法会影响依赖的稳定性。
  • 结合里氏替换原则使用

4.接口隔离原则

  • 定义1:Clients should not be forced to depend upon interfaces that they
    don’t use.
    客户端不应该依赖它不需要的接口。
    那依赖什么呢?依赖它需要的接口,客户端需要什么接口就提供什么接口,把不需要的接口剔除,那就需要对接口进行细化,保证其纯洁性。

  • 定义2:The dependency of one class to another one should depend on the
    smallest possible interface.
    类间的依赖关系应该建立在最小的接口上。
    它要求是最小的接口,也是要求接口细化,接口纯洁。

这里的接口指的是:

  • 实例接口(Object Interface) :在 Java 中声明一个类,然后用 new 关键字产生一个实例,它是对一类事物的描述,可以看成是一个接口
  • 类接口(Class Interface):Java中常使用的interface关键字定义的接口
    即:建立单一接口,不要建立臃肿庞大的接口。再通俗的说就是接口尽量细化,同时接口中的方法尽量少。我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

接口隔离即注意控制接口粒度大小,是为了防止我们封装过度,但是同时设计也是有限度的,我们在实现接口隔离原则时,一定要先满足单一职责原则

5.迪米特法则(Law of Demeter,LoD)

一个对象应该对其他对象有最少的了解。也叫最少知识原则(Low knowledge Principle,LKP),即一个类对自己需要耦合或调用的类知道的越少越好。或者用另一个解释:Only talk to your immediate friends(只与直接朋友通信)。
迪米特法则是对对象之间的耦合度进行限制,尽量降低类与类之间的耦合。

什么是朋友

直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友。
而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。

注意

既然要避免与非直接的朋友通信,那就势必要通过直接的朋友作为中介来间接的与非直接的朋友通信,这样会导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。

6.开闭原则

Software entities like classes,modules and functions should be open for extension but closed for modifications.
一个软件实体如类,模块和函数应该对扩展开放,对修改关闭。
即:一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化的

为什么使用开闭原则
  1. 开闭原则是最基础的设计原则,其它的五个设计原则都是开闭原则的具体形态。
    也就是说其它的五个设计原则是指导设计的工具和方法,而开闭原则才是其精神领袖。依照java语言的称谓,开闭原则是抽象类,而其它的五个原则是具体的实现类。
  2. 开闭原则可以提高复用性
    在面向对象的设计中,所有的逻辑都是从原子逻辑组合而来,不是在一个类中独立实现一个业务逻辑。只有这样的代码才可以复用,粒度越小,被复用的可能性越大。
  3. 开闭原则可以提高维护性
    扩展一个类比修改一个类要好的多
  4. 面向对象开发的要求

总结

  • 单一职责原则告诉我们实现类要职责单一
  • 里氏替换原则告诉我们不要破坏继承体系
  • 依赖倒置原则告诉我们要面向接口编程
  • 接口隔离原则告诉我们在设计接口的时候要精简单一
  • 迪米特法则告诉我们要降低耦合
  • 开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭

参考文档

专栏:6大设计原则详解
以及一些内部资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白码上飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值