单一职责原则,里氏替换原则

导语:

本篇文章是最近看了设计模式方面的知识,记录下自己的阅读内容以及笔记,以备以后翻录!

前言

  • 设计模式分为设计与模式两块,通常来说分为 6 大设计原则和 23 种模式,但随着后续研究者的发掘与探究,将来也会有一些新的设计模式出现!
  • 在遇到开发难题时,可以借鉴这些设计模式!
  • 在学习这些设计模式时,能结合自己以前做过的项目更好,可以自己一边思考以前自己写的代码不足之处,一边理解这些设计模式的规则要求!

一、单一职责原则

定义

单一职责原则( Single Responsibility Principle ):指有且仅有一个原因引起类的变更

理解:尽可能的将对象划分成单一性的类别,使类的复杂性降低,实现什么职责都有清晰明确的定义。

实例

要求:设计一个用户信息管理的类!

接口 IUserBO ( Business Object 用户信息属性放在一个接口里)

1
2
3
4
5
6
7
8
public interface IUserBO {
void setUserID(String userID);
String getUserID();
void setPassword(String password);
String getPassword();
void setUserName(String name);
String getUserName();
}

接口 IUserBL ( Business Logic 用户信息逻辑处理放在一个接口里)

1
2
3
4
5
6
7
public interface IUserBL {
boolean changePassword(String oldPassword);
boolean deleteUser();
void mapUser();
boolean addOrg(int orgID);
boolean addRole(int roleID);
}

接口 IUserInfo ( 用户信息接口将上面两类接口融合为用户信息接口 )

1
2
public interface IUserInfo extends IUserBL,IUserBO{
}

UserInfo ( 用户信息管理类,实现上面的接口 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class UserInfo implements IUserInfo{
private String userID;
private String password;
private String userName;
@Override
public boolean changePassword(String oldPassword) {
System.out.println("改变密码,"+oldPassword);
return false;
}
@Override
public boolean deleteUser() {
System.out.println("删除密码,"+false);
return false;
}
@Override
public void mapUser() {
System.out.println("用户map");
}
@Override
public boolean addOrg(int orgID) {
System.out.println("添加机构"+orgID);
return false;
}
@Override
public boolean addRole(int roleID) {
System.out.println("添加角色,"+roleID);
return false;
}
@Override
public void setUserID(String userID) {
this.userID=userID;
}
@Override
public String getUserID() {
return userID;
}
@Override
public void setPassword(String password) {
this.password = password;
}
@Override
public String getPassword() {
return password;
}
@Override
public void setUserName(String name) {
this.userName = name;
}
@Override
public String getUserName() {
return userName;
}
}

在以上实例中,我们定义了三个接口和一个类,接口 IUserInfo 继承了接口 IUserBOIUserBL,这样设计将 IUserInfo 的接口细化了,将属性操作和逻辑操作分开,这也就是印证了单一职责原则,IUserBO只负责用户属性操作,IUserBL只负责用户信息的逻辑操作!这样使得整个代码清晰易读!便于扩展!

结论

在开发过程中设计类或接口时,尽量要将 这些类和接口的职责唯一、明确。这样在进行合作开发时,不仅自己能够快速明确自己的目的,其他合作开发的程序员也能够明白你的意思。这样同样也可以降低代码的耦合性,有利于后期代码的维护。

二、里氏替换原则

定义

里氏替换原则 ( Liskov Substitution Principle, LSP ):所有引用基类的地方必须能够透明地使用其子类的对象

理解:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法,也尽量不要重载父类的方法

实例

我们想实现两数字的相减的功能,用 A 类来实现,如下:

1
2
3
4
5
6
7
8
9
10
11
12
class A{
public int func1(int a, int b){
return a-b;
}
}
public class Client{
public static void main(String[] args){
A a = new A();
System.out.println("100-50="+a.func1(100, 50));
System.out.println("100-80="+a.func1(100, 80));
}
}

运行结果:

1
2
100-50=50
100-80=20

后来,我们需要增加一个新的功能:完成两数相加,然后再与 100 求和,由类 B 来负责。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class B extends A{
public int func1(int a, int b){
return a+b;
}
public int func2(int a, int b){
return func1(a,b)+100;
}
}
public class Client{
public static void main(String[] args){
B b = new B();
System.out.println("100-50="+b.func1(100, 50));
System.out.println("100-80="+b.func1(100, 80));
System.out.println("100+20+100="+b.func2(100, 20));
}
}

运行结果:

1
2
3
100-50=150
100-80=180
100+20+100=220

我们发现原本运行正常的相减功能发生了错误。原因就是 类 B 在给方法起名时无意中重写了父类的方法,造成所有运行相减功能的代码全部调用了类B重写后的方法,造成原本运行正常的功能出现了错误。

在本例中,引用基类 A 完成的功能,换成子类 B 之后,发生了异常。在实际编程中,我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大。如果非要重写父类的方法,比较通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替。

结论

里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

看上去很不可思议,因为我们会发现在自己编程中常常会违反里氏替换原则,程序照样跑的好好的。所以大家都会产生这样的疑问,假如我非要不遵循里氏替换原则会有什么后果?后果就是:你写的代码出问题的几率将会大大增加。

赞赏一下
0%