依赖倒转原则
依赖倒转原则(DependenceInversionPrinciple)是指:
1)高层模块不应该依赖低层模块,二者都应该依赖其抽象
2)抽象不应该依赖细节,细节应该依赖抽象
3)依赖倒转(倒置)的中心思想是面向接口编程
4)依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在Java中,抽象指的是接口或抽象类,细节就是具体的实现类。
5)使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
示例一、实现person接收消息方式1
package org.yunan.design;
public class DependecyInversion {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}
class Email {
public String getInfo() {
return "电子邮件信息: hello world ";
}
}
class Person {
public void receive(Email email) {
System.out.println(email.getInfo());
}
}
分析:
1.简单,比较容易想到
2.如果我们获取的对象是微信,短信等,则新增类,同时Perons也要增加相应的接收方法
3.Person 类接受消息,将Email类作为参数产生了依赖* 如果参数发生变化,即接受的是微信或短信整个方法需要改动
解决:定义接受的接口调用时传入不同的实现类实现不同的接受,引入一个抽象的接口IReceiver,表示接收者,这样Person类与接口IReceiver发生依赖因为Emai1,WeiXin等属于接收的范围,它们各自实现IReceiver接口,这样就符合依赖倒转原则。
package org.yunan.design;
public class DependecyInversion1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Person person = new Person();
person.receive(new Email());// 传入不同实现类实现不同的接受
person.receive(new WenXin());
person.receive(new QQ());
}
}
/*定义接受的接口*/
interface IReciver {
public String getInfo();
}
//实现接口
class Email implements IReciver {
public String getInfo() {
return "电子邮件信息: hello world ";
}
}
class WenXin implements IReciver {
public String getInfo() {
return "微信信息: hello WenXin";
}
}
class QQ implements IReciver {
public String getInfo() {
return "QQ信息: hello QQ ";
}
}
/* Person 类接受消息将IReciver接口作为参数产生了依赖*/
class Person {
public void receive(IReciver reciver) {
System.out.println(reciver.getInfo());
}
}
PS:
1)低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好。
2)变量的声明类型尽量是抽象类或接口,这样变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化。B obj=new A(); //B是抽象类,A继承B,如果要扩展A,则只要扩充B3)继承时遵循里氏替换原则。
合成/聚合复用原则
合成/聚合复用原则是:尽量使用合成/聚合的方式,而不是使用继承
合成/聚合复用原则来源:在面向对象的设计中,
1.如果直接继承基类,会破坏封装,因为继承将基类的实现细节暴露给子类;
2.如果基类的实现发生改变,则子类的实现也发生改变;
3.从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性。
4.于是就提出了合成/聚合复用原则:尽量使用合成/聚合,不要使用类继承达到复用的目的。
设计原则核心思想:
- 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
- 针对接口编程,而不是针对实现编程。
- 为了交互对象之间的松耦合设计
接口隔离原则
基本介绍:不应该依赖不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。
示例一:
类B实现接口Interface1 ,类A通过接口Interface1依赖(使用)类B,但是只会用到1,2,3.
类D实现接口Interface1 ,类C通过接口Interface1依赖(使用)类D,但是只会用到1,4,5方法
package org.yunan.design;
public class segregation {
public static void main(String[] args) {
// TODO Auto-generated method stub
A a=new A();
C c=new C();
a.depend1(new B());
a.depend2(new B());
a.depend3(new B());
c.depend1(new D());
c.depend4(new D());
c.depend5(new D());
}
}
interface Interface1 {
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
//类B实现接口Interface1 class
class B implements Interface1 {
public void operation1() {
System.out.println("B实现了operation1");
}
public void operation2() {
System.out.println("B实现了operation2");
}
public void operation3() {
System.out.println("B实现了operation3");
}
public void operation4() {
System.out.println("B实现了operation4");
}
public void operation5() {
System.out.println("B实现了operation5");
}
}
class D implements Interface1 {
public void operation1() {
System.out.println("D实现了operation1");
}
public void operation2() {
System.out.println("D实现了operation2");
}
public void operation3() {
System.out.println("D实现了operation3");
}
public void operation4() {
System.out.println("D实现了operation4");
}
public void operation5() {
System.out.println("D实现了operation5");
}
}
class C {
public void depend1(Interface1 i) {
i.operation1();
}
public void depend4(Interface1 i) {
i.operation4();
}
public void depend5(Interface1 i) {
i.operation5();
}
}
class A {
public void depend1(Interface1 i) {
i.operation1();
}
public void depend2(Interface1 i) {
i.operation2();
}
public void depend3(Interface1 i) {
i.operation3();
}
}
分析:类B、D中有大量多余的方法没有利用,资源浪费严重
类A通过接口Interface1依赖类B,
1.类C通过接口Interface1依赖类D,
2.如果接口Interface1对于类A和类C来说不是最小接口,那么类B和类D必须去实现它们不需要的方法。
3.如上代码违背了接口隔离原则(不应该依赖不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上)类A不需要用到类B4,5方法类C不需要用到类D2,3方法
解决:
将接口Interface1拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
package org.yunan.design;
public class segregation1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
A a=new A();
C c=new C();
a.depend1(new B());
a.depend2(new B());
a.depend3(new B());
c.depend1(new D());
c.depend4(new D());
c.depend5(new D());
}
}
interface Interface1 {
void operation1();
}
interface Interface2 {
void operation2();
void operation3();
}
interface Interface3 {
void operation4();
void operation5();
}
class B implements Interface1, Interface2 {
public void operation1() {
System.out.println("B 实现operation1");
}
public void operation2() {
System.out.println("B 实现operation2");
}
public void operation3() {
System.out.println("B 实现operation3");
}
}
class A {
public void depend1(Interface1 i) {
i.operation1();
}
public void depend2(Interface2 i) {
i.operation2();
}
public void depend3(Interface2 i) {
i.operation3();
}
}
class D implements Interface1, Interface3 {
public void operation1() {
System.out.println("D 实现operation1");
}
public void operation4() {
System.out.println("D 实现operation4");
}
public void operation5() {
System.out.println("D 实现operation5");
}
}
class C {
public void depend1(Interface1 i) {
i.operation1();
}
public void depend4(Interface3 i) {
i.operation4();
}
public void depend5(Interface3 i) {
i.operation5();
}
}
接口隔离原则:
使用多个专门的接口比使用单一的总接口要好。换而言之,接口尽量细化,接口中的方法尽量少。
1.过于臃肿的接口是对接口的污染。不应该强迫依赖于它们不用的方法:
2.类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类C来说不是最小接口,而类B和类D必须去实现它们不需要的方法。
迪米特法则
基本介绍:
1)一个对象应该对其他对象保持最少的了解
2)类与类关系越密切,耦合度越大
3)迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不泄露任何信息。
4)迪米特法则还有个更简单的定义:只与直接的朋友通信
5)直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
class A{
B objb; //成员变量中的类
Bmethoda(C para);//方法参数中的类
CD methoda(); //方法返回值中的类D
}//类B,C,D是类A的直接朋友
class C{
methodc(){E para =new E;}//类E出现在局部变量中
} //类E不是类C的直接朋友
实例1:
有一个学校,下属有各个学院和总部,现要求打印出学校总部员工ID和学院员工的id。编程实现上面的功能。
package org.yunan.design;
import java.util.ArrayList;
import java.util.List;
public class Demeter {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建了一个SchoolManager对象
SchoolManager schoolManager = new SchoolManager();
//输出学院的员工id 和 学校总部的员工信息
schoolManager.printAllEmployee(new CollegeManager());
}
}
//学校总部员工类
class Employee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
//学院的员工类
class CollegeEmployee{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//管理学院员工的管理类
class CollegeManager{
public List<CollegeEmployee> getAllEmployee(){
List<CollegeEmployee> list = new ArrayList();
for(int i = 0; i < 10; i++) {
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工 id = " + i);
list.add(emp);
}
return list;
}
}
//学校管理类
//分析SchoolMangager 类的直接朋友有哪些Employee,CollegeManager
//CollegeEmployee 不是直接朋友而是一个陌生类,这样违背了迪米特法则
class SchoolManager{
public List<Employee> getAllEmployee(){
List<Employee> list = new ArrayList();
for(int i = 0; i < 5; i++) {
Employee emp = new Employee();
emp.setId("学校总部的员工id = " + i);
list.add(emp);
}
return list;
}
//该方法完成输出学校总部和学院员工信息(id)
void printAllEmployee(CollegeManager sub){
//分析问题
//1. 这里的CollegeEmployee不是 SchoolManager的直接朋友
//2. CollegeEmployee 是以局部变量方式出现在SchoolManager
//3. 违反了迪米特法则
//获取到学院员工
List<CollegeEmployee> list1 = sub.getAllEmployee();
System.out.println("-------学院员工---------");
for (CollegeEmployee employee : list1) {
System.out.println(employee.getId());
}
//获取到学校总部的员工
List<Employee> list2 = this.getAllEmployee();
System.out.println("-------学校总部员工---------");
for (Employee employee : list2) {
System.out.println(employee.getId());
}
}
}
改进:
package org.yunan.design;
import java.util.ArrayList;
import java.util.List;
public class Demeter1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建了一个SchoolManager对象
SchoolManager schoolManager = new SchoolManager();
//输出学院的员工id 和 学校总部的员工信息
schoolManager.printAllEmployee(new CollegeManager());
}
}
//学校总部员工类
class Employee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
//学院的员工类
class CollegeEmployee{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//管理学院员工的管理类
class CollegeManager{
public List<CollegeEmployee> getAllEmployee(){
List<CollegeEmployee> list = new ArrayList();
for(int i = 0; i < 5; i++) {
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工 id = " + i);
list.add(emp);
}
return list;
}
void printEmployee(){
List<CollegeEmployee> list1 = getAllEmployee();
System.out.println("-------学院员工---------");
for (CollegeEmployee employee : list1) {
System.out.println(employee.getId());
}
}
}
//学校管理类
//分析SchoolMangager 类的直接朋友有哪些Employee,CollegeManager
//CollegeEmployee 不是直接朋友而是一个陌生类,这样违背了迪米特法则
class SchoolManager{
public List<Employee> getAllEmployee(){
List<Employee> list = new ArrayList();
for(int i = 0; i < 5; i++) {
Employee emp = new Employee();
emp.setId("学校总部的员工id = " + i);
list.add(emp);
}
return list;
}
//该方法完成输出学校总部和学院员工信息(id)
void printAllEmployee(CollegeManager sub){
//分析问题
//1. 将输出学院的员工方法,封装到CollegeManager
sub.printEmployee();
//获取到学校总部的员工
List<Employee> list2 = this.getAllEmployee();
System.out.println("-------学校总部员工---------");
for (Employee employee : list2) {
System.out.println(employee.getId());
}
}
}
迪米特法则(Law of Demeter,简写LoD)又叫做最少知识原则(Least Knowledge Principle或简写为LKP)
1.如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
2.也就是说,一个对象应当对其它对象有尽可能少的了解。
其它表述
3.一个模块设计的好坏的一个重要标志就是该模块在多大程度上将自己的内部数据与实现的有关细节隐藏起来。
4.一个软件实体应当尽可能少的与其他实体发生相互作用。
5.每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
PS:
迪米特法则的目的在于降低类与类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,使得相互间存在尽可能少的依赖关系。信息的隐藏促进了软件的复用。
但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系。
以上部分摘取自朱红梅老师2020年5月的课件。