接口隔离原则定义如下:
接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
一个类对另一个类的依赖应该建立在最小的接口上。
根绝接口隔离原则,当一个接口太大时,我们需要将它分割成一些细小的接口,使用该接口的客户端只需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色,不干不该干的事情,干该干的事请。这里的"接口"往往有两种不同的定义:一种是指一个类型所具有的方法特征的集合,仅仅是一种逻辑上的抽象;另外一种是某种语言上具体的"接口"定义,比如Java语言的interface。
接口隔离原则优点
接口隔离原则的实现方法
以下假设一个场景并用代码演示(项目代码名称为ISP):
创建类Client.java,代码如下:
public class Client {public static void main(String[] args) {A a = new A();a.b1(new B()); // A 类通过接口去依赖 B 类a.b2(new B());a.b3(new B());System.out.println("-----------------------");C c = new C();c.d1(new D()); // C 类通过接口去依赖(使用)D 类c.d2(new D());c.d3(new D());}
}//A 类通过接口 Interface1 依赖(使用) B 类,但是只会用到 1,2,3 方法class A {public void b1(Interface1 i) {i.run1();}public void b2(Interface1 i) {i.run2();}public void b3(Interface1 i) {i.run3();}}//C 类通过接口 Interface1 依赖(使用) D 类,但是只会用到 1,4,5 方法class C {public void d1(Interface1 i) {i.run1();}public void d2(Interface1 i) {i.run4();}public void d3(Interface1 i) {i.run5();}}//接口interface Interface1 {void run1();void run2();void run3();void run4();void run5();}class B implements Interface1 {public void run1() {System.out.println("B 实现了 run1");}public void run2() {System.out.println("B 实现了 run2");}public void run3() {System.out.println("B 实现了 run3");}public void run4() {System.out.println("B 实现了 run4");}public void run5() {System.out.println("B 实现了 run5");}}class D implements Interface1 {public void run1() {System.out.println("D 实现了 run1");}public void run2() {System.out.println("D 实现了 run2");}public void run3() {System.out.println("D 实现了 run3");}public void run4() {System.out.println("D 实现了 run4");}public void run5() {System.out.println("D 实现了 run5");}
}
运行结果如下所示:
B 实现了 run1
B 实现了 run2
B 实现了 run3
-----------------------
D 实现了 run1
D 实现了 run4
D 实现了 run5
修改代码,改为用ISP原则:
创建代码ClientISP.java,代码如下:
public class ClientISP {public static void main(String[] args) {A a = new A();a.b1(new B()); // A 类通过接口去依赖 B 类a.b2(new B());a.b3(new B());System.out.println("-----------------------");C c = new C();c.d1(new D()); // C 类通过接口去依赖(使用)D 类c.d2(new D());c.d3(new D());}
}//A 类通过接口 Interface1 依赖(使用) B 类,但是只会用到 1,2,3 方法
class A {public void b1(Interface1 i) {i.run1();}public void b2(Interface2 i) {i.run2();}public void b3(Interface2 i) {i.run3();}
}//C 类通过接口 Interface1 依赖(使用) D 类,但是只会用到 1,4,5 方法
class C {public void d1(Interface1 i) {i.run1();}public void d2(Interface3 i) {i.run4();}public void d3(Interface3 i) {i.run5();}
}//接口
interface Interface1 {void run1();
}interface Interface2 {void run2();void run3();
}interface Interface3 {void run4();void run5();
}class B implements Interface1, Interface2 {public void run1() {System.out.println("B 实现了 run1");}public void run2() {System.out.println("B 实现了 run2");}public void run3() {System.out.println("B 实现了 run3");}public void run4() {System.out.println("B 实现了 run4");}public void run5() {System.out.println("B 实现了 run5");}
}class D implements Interface1, Interface3 {public void run1() {System.out.println("D 实现了 run1");}public void run2() {System.out.println("D 实现了 run2");}public void run3() {System.out.println("D 实现了 run3");}public void run4() {System.out.println("D 实现了 run4");}public void run5() {System.out.println("D 实现了 run5");}
}
运行结果如下所示:
B 实现了 run1
B 实现了 run2
B 实现了 run3
-----------------------
D 实现了 run1
D 实现了 run4
D 实现了 run5
重构之后每个接口都承担自己的职责,灵活性很高,使用起来很方便,每个接口中都只包含和自己业务相关的方法,不会存在和自己无关的方法,达到了高内聚、松耦合的效果。
迪米特法则定义如下:
迪米特法则(Law of Demeter, LOD)也称为最少知识原则,它们的意思为:一个对象应该对其他对象有最少的了解。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的 public 方法,不对外泄露任何信息
迪米特法则的定义是:只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则还有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。
迪米特法则的优点
迪米特法则要求限制软件实体之间通信的宽度和深度,正确使用迪米特法则将有以下两个优点。
迪米特法则的实现方法
从迪米特法则的定义和特点可知,它强调以下两点:
所以,在运用迪米特法则时要注意以下:
如果一个系统满足迪米特法则,那么当其中一个软件实体发生变化时,就会尽量少的影响其他软件实体,扩展会相对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可以降低系统的耦合度,使类与类之间保持松耦合状态。
以下假设一个场景并用代码演示(项目代码名称为LOD):
创建类School.java,代码如下:
import java.util.ArrayList;
import java.util.List;public class School {public static void main(String[] args) {Teacher teacher = new Teacher();//输出学院的教师id和学生idteacher.printPeople(new Student());}
}//学校教师类
class EmployeeDTO {private String id;public String getId() {return id;}public void setId(String id) {this.id = id;}
}//学院的学生类
class StudentDTO {private String id;public String getId() {return id;}public void setId(String id) {this.id = id;}
}//学院学生管理类
class Student {//返回学院的所有学生public List getAllEmployee() {List list = new ArrayList<>();//增加 10 个员工到 listfor (int i = 0; i < 10; i++) {StudentDTO stu = new StudentDTO();stu.setId("学院学生id= " + i);list.add(stu);}return list;}
}//教师管理类
class Teacher {//返回学校教师public List getAllEmployee() {List list = new ArrayList();//增加 5 个员工到 listfor (int i = 0; i < 5; i++) {EmployeeDTO emp = new EmployeeDTO();emp.setId("学校教师id= " + i);list.add(emp);}return list;}//该方法完成输出学校总部和学院员工信息(id)void printPeople(Student sub) {/*问题分析:1. 这里的 StudentDTO 不是 EmployeeDTO 的直接朋友2. StudentDTO 是以局部变量方式出现在 EmployeeDTO3. 违反了 迪米特法则*///获取到学院学生数据List list1 = sub.getAllEmployee();System.out.println("------------学院学生------------");for (StudentDTO e : list1) {System.out.println(e.getId());}//获取到学校总部员工List list2 = this.getAllEmployee();System.out.println("------------学校教师------------");for (EmployeeDTO e : list2) {System.out.println(e.getId());}}
}
运行结果如下所示:
------------学院学生------------
学院学生id= 0
学院学生id= 1
学院学生id= 2
学院学生id= 3
学院学生id= 4
学院学生id= 5
学院学生id= 6
学院学生id= 7
学院学生id= 8
学院学生id= 9
------------学校教师------------
学校教师id= 0
学校教师id= 1
学校教师id= 2
学校教师id= 3
学校教师id= 4
修改代码,改为用LOD原则:
创建代码SchoolLOD.java,代码如下:
import java.util.ArrayList;
import java.util.List;public class SchoolLOD {public static void main(String[] args) {Teacher teacher = new Teacher();//输出学院的教师id和学生idteacher.printPeople(new Student());}
}//学校教师类
class EmployeeDTO {private String id;public String getId() {return id;}public void setId(String id) {this.id = id;}
}//学院的学生类
class StudentDTO {private String id;public String getId() {return id;}public void setId(String id) {this.id = id;}
}//学院学生管理类
class Student {//返回学院的所有学生public List getAllEmployee() {List list = new ArrayList<>();//增加 10 个员工到 listfor (int i = 0; i < 10; i++) {StudentDTO stu = new StudentDTO();stu.setId("学院学生id= " + i);list.add(stu);}return list;}public void printStudent(){//获取到学院员工List list1 = getAllEmployee();System.out.println("------------学院学生------------");for (StudentDTO e : list1) {System.out.println(e.getId());}}
}//教师管理类
class Teacher {//返回学校教师public List getAllEmployee() {List list = new ArrayList();//增加 5 个员工到 listfor (int i = 0; i < 5; i++) {EmployeeDTO emp = new EmployeeDTO();emp.setId("学校教师id= " + i);list.add(emp);}return list;}//该方法完成输出学校总部和学院员工信息(id)void printPeople(Student sub) {//将输出学院学生方法,封装到 Studentsub.printStudent();//获取到学校总部员工List list2 = this.getAllEmployee();System.out.println("------------学校教师------------");for (EmployeeDTO e : list2) {System.out.println(e.getId());}}
}
运行结果如下所示:
------------学院学生------------
学院学生id= 0
学院学生id= 1
学院学生id= 2
学院学生id= 3
学院学生id= 4
学院学生id= 5
学院学生id= 6
学院学生id= 7
学院学生id= 8
学院学生id= 9
------------学校教师------------
学校教师id= 0
学校教师id= 1
学校教师id= 2
学校教师id= 3
学校教师id= 4
迪米特法则注意事项和细节
迪米特法则的核心是降低类之间的耦合
注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类之间或对象之间的耦合关系,并不是要求完全没有依赖关系
组合/聚合复用原则定义如下:
合成复用原则(Composite Reuse Principle,CRP)又叫组合/聚合复用原则(Composition/Aggregate Reuse Principle,CARP)。它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
如果要使用继承关系,则必须严格遵循里氏替换原则。合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范。
合成/聚合复用原则是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的。
组合/聚合复用原则的优点
由于合成或聚合可以将已有对象纳入到新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能。这样做的好处有
组合/聚合复用原则的实现方法
由于组合或聚合关系可以将已有的对象(也可称为成员对象)纳入到新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能,这样做可以使得成员对象的内部实现细节对于新对象不可见,所以这种复用又称为“黑箱”复用,相对继承关系而言,其耦合度相对较低,成员对象的变化对新对象的影响不大,可以在新对象中根据实际需要有选择性地调用成员对象的操作;合成复用可以在运行时动态进行,新对象可以动态地引用与成员对象类型相同的其他对象。
以下假设一个场景并用代码演示(项目代码名称为CRP):
创建类Shop.java,代码如下:
public class Shop {public String getShopping() {return "线上商店销售";}
}class Fruit extends Shop {public void addFruit() {String fruit = super.getShopping();System.out.println(String.format("%s苹果", fruit));}
}class test {public static void main(String[] args) {Fruit fruit = new Fruit();fruit.addFruit();}
}
运行结果如下所示:
线上商店销售苹果
假设现在需要添加线下销售苹果的方式,
而目前Shop 中只会返回线上销售方法。
其实也简单,我们在 Shop中再新增一个方法用于获取 线下销售方法。功能实现上其实是没什么问题的,但是这样会违反开闭原则。
那如何利用合成复用原则对示例代码进行重构?代码如下:
创建类Shop.java,代码如下:
public abstract class ShopCRP {public abstract String getShopping();
}class Online extends ShopCRP {@Overridepublic String getShopping() {return "线上商店销售";}
}class Offline extends ShopCRP {@Overridepublic String getShopping() {return "线下商店销售";}
}class Fruit {private ShopCRP shopCRP;public void setShopCRP(ShopCRP shopCRP) {this.shopCRP = shopCRP;}public void addFruit() {System.out.println(String.format("%s苹果", shopCRP.getShopping()));}
}class test {public static void main(String[] args) {Fruit fruit = new Fruit();fruit.setShopCRP(new Online());fruit.addFruit();fruit.setShopCRP(new Offline());fruit.addFruit();}
}
运行结果如下所示:
线上商店销售苹果
线下商店销售苹果
如果还有扩展,则只要写与 Online 平级的类继承 ShopCRP ,而具体的选择则交由应用层。
优点
以上代码下载请点击该链接:https://github.com/Yarrow052/Java-package.git