柏柏羊的第三篇博客——常见面向对象的设计原则2020.03.17

封装变化——能将变化所带来的影响减为最小
抽象约束——可以随需求的变化而变化
各司其职

编写程序的要求
1、高内聚(块内联系),低耦合(块间联系)
2、可维护性
3、可扩展性
4、重用性:(通用性)
5、灵活性:可使用多种方式来调用来部分的代码
6、可读性
7、可靠性
8、可移植性:代码经过修改可以在两个环境以上使用(兼容性)

设计模式的七大原则:
单一职责原则(SRP)
开放-封闭原则
里氏代换原则(LSP) )
依赖倒转原则(DIP)
合成/ 聚合复用原则(CARP)
接口隔离原则(ISP) )
迪米特法则(LoD)

class Vehicle2 {
public void run(String vehicle) { // 不变
System .out. println(vehicle +" 在公路上跑");
}
public void runAir(String vehicle) { // 增加
System.out. println(vehicle + " 在天空中飞");
}
public void runWater(String vehicle) { // 增加
System.out. println(vehicle + " 在水里游");
}
}
public class singleResponsibility {
public static void main(String[] args) {
Vehicle2 Vehicle = new Vehicle2();
Vehicle.run(" 摩托车");
Vehicle.run(" 汽车");
Vehicle.runAir(" 飞机");
Vehicle.runWater(" 轮船");
}
}
1.这种修改方法没有对原来的类做大的修改,只是增加 方法;
2.增加的部分不影响 增加的部分不影响 原有部分, 降低了变更 引起的风险;
3.这里在 这里在 类 这个级别上没有遵守单一职责原则,但是在 方法级别上,仍然是遵守单一 职责。
重新认识面向对象
封装变化(笼子):为满足甲方爸爸各种要求,给自己留余地,事先要考虑到变化,封装变化。面向对象的构建方式本身为了软件重用性和可扩展性,它更能适应软件的变化,将变化所带来的的影响减为最小。
封装变化包含两层含义:
(1)将相同的变化封装到一个接口或抽象类中
(2)将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中
封装变化,就是受保护的变化,找出预计有变化或不稳定的点,为这些变化点创建稳定的接口。
总之,就是将目光放长远,预计可能会有变化的地方。

抽象约束(约束笼子内的某一东西的活动范围):抽象是对一组事物的通用描述,没有具体的实现,就表示它可以有非常多的可能性,可以随需求的变化而变化。
通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放,其包含三层含义:
(1)通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法。

实际上,我们定义接口或抽象类的时候,可定义一些抽象方法,由他的实现类 子类来具体实现。子类里只能实现接口或抽象类中已经定义好的那些允许变化的地方 ;实现类里还可以定义自己的一些方法 ,但是在接口或抽象类里定义的方法在子类或实现类里定义的时候必须严格按照接口或抽象类里的定义 比如返回值的类型是什么、名字叫什么、参数几个、参数是什么类型……

(2)参数类型,引用对象尽量使用接口或抽象类,而不是实现类,这主要是实现里氏代换原则的一个要求。
(3)抽象层尽量保持稳定,一旦确定就不要修改。

各司其职
(1)从微观层面来看,面向对象的方式更强调各个类的“责任”
(2)由于需求变化导致的新增类型不应该影响原来类型的实现—各负其责。
增加新的类来承担责任,而不是修改原有的。
耦合性和内聚性
程序设计要求较高的模块独立性:每个模块只完成系统要求的独立子功能,并且与其他模块的联系最少且接口简单。(由外界直接调用方法,调用时使用的参数尽量的简单,尽量少。当设计的程序较为复杂,参数不得不多,这时就需要将模块进行分解,分解成有独立功能的模块)模块独立性的度量标准——耦合性和内聚性。
耦合性也称块间联系,指软件系统结构中各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差,也就是说当我们改动一个模块时,有更大的概率也需要去改动其他的多个模块。
内聚性又称块内联系。指模块的功能强度的度量,即一个模块内部各个元素彼此结合的紧密程度的度量。若一个模块内各元素(语名之间、程序段之间)联系的越紧密,则它的内聚性就越高。
设计模式的目的(软件设计的要求)
编写软件过程中,程序员面临着来自耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性等多方面的挑战:
(1)使程序呈现高内聚,低耦合的特性
(2)可维护性:代码完成之后,当需要修改程序的某个模块时,对其他模块的影响和修改的代价。——需要修改的地方很少,就是容易维护。
(3)可扩展性:代码完成之后,当需要为程序添加新的功能时,对其他模块的影响和添加的代价。——只需要添加添加该功能的代码,不需要修改原来的代码(对之前的代码没有影响),这就是可扩展。恰当地使用设计模式可以提高代码的可维护性和可扩展性。
(4)重用性:代码完成之后,以后开发中可以复用部分代码,提高效率,就是复用性强。(并不是简单的完全复制粘贴emmm,应是调用)
(5)灵活性:代码完成之后,使用的地方可以通过多种方式来调用该部分的代码,这就是灵活性好。
(6)可读性:编程规范性,便于其他程序员的阅读和理解(命名规范——见名知意、代码排版——排版一致、关键注释)。
(7)可靠性(稳定性):软件在一定的边缘条件(线性表删除的时候是不是空的 ……)下的出错机率、性能劣化趋势等,又称稳定性。要求系统在发生硬件故障,软件故障,或人为错误时,仍然可以正常工作。(例如双十一时、疫情期间网上教学等一些平台流量)
(8)可移植性:代码完成之后,稍微修改一下就可以在另外一个环境中使用, 也就是说可以在两个环境以上使用, 就具备可移植性。

重用性与可移植性的区别:
重用: 强调被使用的次数, 也就是通用性;
移植: 强调支持的环境的能力, 多个系统环境都可以正常使用, 也就是兼容性。

设计模式七大原则:
( 为了实现前面所述的软件设计的要求 而规定的原则 )

单一职责原则(SRP)
开放-封闭原则(OCP)——面向对象设计的终极目标 最常用
里氏代换原则(LSP)
依赖倒转原则(DIP)
合成/聚合复用原则(CARP)
接口隔离原则(ISP)
迪米特法则(LoD)
设计原则是思想上的指导 设计模式是实现上的手段
设计模式是设计原则指导下的具体体现 ~~但不需要完完全全遵守
设计是一种危险的平衡艺术 害 我们在搞艺术

1.单一职责原则(Single Responsibility Principle)
就一个类而言,应该仅有一个引起它变化的原因(职责)。
职责就是对象能够承担的责任,并以某种行为方式来执行。可以将数据库的增删改查理解成职责,即对数据的基本操作。
单一职责原则对类来说,即一个类应该只负责一项职责。
如类A负责两个不同职责:职责1,职责2,当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2。 (分解为两个类)
主要根据不同的角度划分职责,比如,
1、从类的组成,划分为属性操作和行为操作两种。
2、从数据库操作的不同作用,划分为数据库的连接操作和增删改查基本操作。
方式1 :


package sjms;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Vehicle vehicle =new Vehicle();
		vehicle.run("摩托车");
		vehicle.run("汽车");
		vehicle.run("飞机");
	}
}
	class Vehicle{
		public void run(String vehicle)
		{
			System.out.println(vehicle +"在公路上跑");
		}
	}

运行结果如下:
在这里插入图片描述
分析:
1.在方式1的run方法中,违反了单一职责原则
2.解决方案:根据交通工具运行方法不同,分解成不同类
方式二:

package sjms;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		RoadVehicle roadVehicle =new RoadVehicle();
		roadVehicle.run("摩托车");
		roadVehicle.run("汽车");
		AirVehicle airVehicle=new AirVehicle();
		airVehicle.run("飞机");
		WaterVehicle waterVehicle=new WaterVehicle();
		waterVehicle.run("轮船");
	}
}
	class RoadVehicle{
		public void run(String vehicle)
		{
			System.out.println(vehicle +"在公路上跑");
		}
	}
	class AirVehicle{
		public void run(String vehicle)
		{
			System.out.println(vehicle +"在天空中飞");
		}
	}
	class WaterVehicle{
		public void run(String vehicle)
		{
			System.out.println(vehicle +"在水里游");
		}
	}

运行结果如下:
柏柏羊的第三篇博客——常见面向对象的设计原则2020.03.17_第1张图片
分析:
1.遵守单一职责原则
2.但是改动很大,即要将类分解,又要修改客户端
3.改进:保留并修改Vehicle类,改动的代码会比较少——方式3
方式三:

package sjms;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Vehicle2 Vehicle =new Vehicle2();
		Vehicle.run("摩托车");
		Vehicle.run("汽车");
		Vehicle.runAir("飞机");
		Vehicle.runWater("轮船");
	}
}
	class Vehicle2{
		public void run(String vehicle)
		{
			System.out.println(vehicle +"在公路上跑");
		}
		public void runAir(String vehicle)
		{
			System.out.println(vehicle +"在天空中飞");
		}
		public void runWater(String vehicle)
		{
			System.out.println(vehicle +"在水里游");
		}
	}

运行结果如下:
柏柏羊的第三篇博客——常见面向对象的设计原则2020.03.17_第2张图片
分析:
1.这种修改方法没有对原来的类做大的修改,只是增加方法;
2.增加的部分不影响原有部分,降低了变更引起的风险;
3.这里在类这个级别上没有遵守单一职责原则,但是在方法级别上,仍然是遵守单一职责。
单一职责原则注意事项和细节:
1)降低类的复杂度,一个类只负责一项职责。
2)提高类的可读性,可维护性
3)降低变更引起的风险
4)通常情况下,应当遵守单一职责原则。
只有逻辑足够简单,才可以在代码级违反单一职责原则:
只有类中方法数量足够少,可以在方法级别保持单一职责原则。

2.开放-封闭原则(Open-Closed Principle)
又称开闭原则,是编程中最基础、最重要的设计原则——面向对象设计的终极目标
一个软件中的实体,如类、模块和函数,应该**对扩展开放(对提供方),对修改关闭(对使用方)**使用的方式不变。用抽象构建框架,用实现扩展细节。
当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。
方式一:

package sjms;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		GraphicDraw graphicdraw=new GraphicDraw();
		graphicdraw.drawgraph(1);//客户端肯定要改( 1输出矩形,2输出圆形,3输出三角形)
	}
}
	class Rectangle{  //你是一个成熟的矩形类了,你可以自己把自己画出来了
		void draw(){System.out.println("矩形");}
	}
	class Circle{     //你是一个成熟的圆形了,你可以自己把自己画出来了
		void draw(){System.out.println("圆形");}
	}
	class Triangle{   //新增三角形——肯定要改
		void draw(){System.out.println("三角形");}
	}
	class GraphicDraw{
		void drawgraph(int type){
			if(type==1){
				Rectangle rec=new Rectangle();
				rec.draw();
			}
			else if(type==2){
				Circle c=new Circle();
				c.draw();
			}
			else if(type==3){  //新增绘制三角形
				Triangle t=new Triangle();
				t.draw();
			}
		}
	}

分析:

方式1的优缺点:
1)优点是比较好理解,简单易操作。
2)缺点是违反了开闭原则(对扩展(提供方)开放,对修改(使用方)关闭):需要给类增加新功能的时候,尽量不要修改代码,或者尽可能少修改代码。
3)本例,需要新增一个图形种类时,修改的地方比较多。
改进思路: 发现Rectangle和Circle有共同的draw方法,创建抽象Shape类做成基类,并提供抽象的draw方法,让子类去实现,当需要新的图形时,只需要让新的图形继承Shape,并实现draw方法。画图类GraphicDraw不出现具体的类,用抽象类Shape。这样使用方的代码就不需要修改,满足开闭原则。
方式二:

package sjms;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		GraphicDraw graphicdraw=new GraphicDraw();
		graphicdraw.drawgraph(new Circle());
		graphicdraw.drawgraph(new Rectangle());
		graphicdraw.drawgraph(new Triangle());
	}
}
//Shape类,基类
    abstract class Shape{
	    public abstract void draw();//抽象方法
    }
    class GraphicDraw{   //新增绘制图形不需修改此类
		void drawgraph(Shape s){  s.draw(); //相当于子类对象的draw }
	}
	class Rectangle extends Shape{ 
		public void draw(){System.out.println("矩形");}
	}
	class Circle extends Shape{     
		public void draw(){System.out.println("圆形");}
	}
	class Triangle extends Shape{   //新增三角形
		public void draw(){System.out.println("三角形");}
	}

开放-封闭原则注意事项和细节:

一个软件实体应当对扩展开放,对修改关闭。
开闭原则(OCP)是面向对象设计中“可复用设计”的基石,是面向对象设计中最重要的原则之一,其它很多的设计原则都是实现开闭原则的一种手段。
开闭原则中“开”,是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的;
开闭原则中“闭”,是指对于原有代码的修改是封闭的,即不应该修改原有的代码。
3.里氏代换原则(Liskov Substitution Principle)
使得开放-封闭成为可能
在面向对象编程中,继承是实现软件复用的一个非常好的方法。

继承的优点:
1.子类拥有父类的所有方法和属性,从而可以减少创建类的工作量。
2.提高了代码的重用性。
3.提高了代码的扩展性,子类不但拥有了父类的所有功能,还可以添加自己的功能。
继承的缺点:
1.继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法。
2.降低了代码的灵活性。因为继承时,父类会对子类有一种约束。
3.增强了耦合性。当需要对父类的代码进行修改时,必须考虑到对子类产生的影响。有时修改了一点点代码都有可能需要对大段程序进行重构。
OO面向对象中的继承性的思考和说明:
继承包含这样一层含义:
父类中已经实现的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。
里氏代换原则:“继承必须确保超类所拥有的性质在子类中仍然成立。”也就是说,当一个子类的实例能够替换任何其超类的实例时,它们之间才具有is-A关系。(语法上没错,但逻辑上要符合)
子类型必须能够替换它们的基(父)类型。(子类可以以父类的身份出现)是关于继承机制的原则,是实现开放-封闭原则的具体规范,违反了里氏代换原则必然违反了开放-封闭原则。
只要有父类出现的地方,都可以使用子类来替代。而且不会出现任何错误或者异常。
但是反过来却不行。子类出现的地方,不能使用父类来替代。
里氏代换原则的主要作用:规范继承时子类的一些书写规则。其主要目的就是保持父类方法不被覆盖。
在适当的情况下,可以不使用继承而是通过聚合,组合,依赖来解决问题。
方式一:

package sjms;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		A a=new A();
		System.out.println("11-3="+a.func1(11,3));
		System.out.println("1-8="+a.func1(1,8));
		System.out.println("-------");
		B b=new B();
		System.out.println("11-3="+b.func1(11,3));
		//这里本意是求出11-3
		System.out.println("1-8="+b.func1(1,8));
		System.out.println("11+3+9="+b.func2(11,3));
	}
}
    class A{
	    public int func1(int num1,int num2){
	    	return num1-num2;
	    }
    }
	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)+9;
		}
	}

运行结果如下:
柏柏羊的第三篇博客——常见面向对象的设计原则2020.03.17_第3张图片
分析:
原来运行正常的相减功能发生了错误。原因就是类B重写了父类的方法func1,造成原有功能出现错误。 在实际编程中,常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候。
通用的解决思路:
让原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等关系代替。
方式二:

package sjms;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		A a=new A();
		System.out.println("11-3="+a.func1(11,3));
		System.out.println("1-8="+a.func1(1,8));
		System.out.println("-------");
		B b=new B();
		//因为B类不再继承A类,因此调用者,不会再认为func1是求减法
		//调用完成的功能就会很明确
		System.out.println("11+3="+b.func1(11,3));//这里本意是求出11+3
		System.out.println("1+8="+b.func1(1,8));
		System.out.println("11+3+9="+b.func2(11,3));
		//使用组合仍然可以使用到A类相关方法
		System.out.println("11-3="+b.func3(11,3));//这里本意是求出11-3
	}
}
    //创建一个更加基础的基类
    class Base{
    	//把更加基础的方法和成员写到Base类
    }
    //A类
    class A extends Base{
	    public int func1(int num1,int num2){
	    	return num1-num2;
	    }
    }
	class B extends Base{ 
		A a=new A();
		public int func1(int a,int b){
			return a+b;
		}
		public int func2(int a,int b){
			return func1(a,b)+9;
		}
		public int func3(int a,int b){
			return this.a.func1(a,b);
		}
	}

运行结果如下:
柏柏羊的第三篇博客——常见面向对象的设计原则2020.03.17_第4张图片
A和B是依赖关系 √

关系是可以分开的,则是聚合关系;关系是不可以分开的,则是组合关系。

A、B不可以分开,为组合关系。A组合到B √

子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法
设计接口或抽象类,由子类来实现抽象方法,这里使用的其实就是里氏代换原则。
里氏代换原则规定,子类不能覆写父类已实现的方法。父类中已实现的方法其实是一种已定好的规范和契约,如果随意修改它,那么可能会带来意想不到的错误。
子类中可以增加自己特有的方法(可以随时扩展)
子类继承了父类,拥有了父类和方法,同时还可以定义自己有,而父类没有的方法。这是在继承父类方法的基础上进行功能的扩展,符合里氏代换原则。
4. 依赖倒转原则(Dependence Inversion Principle)
又称依赖倒置原则
1)高层模块不应该依赖低层模块,二者都应该依赖其抽象
2)抽象不应该依赖细节,细节应该依赖抽象
3)依赖倒转(倒置)的中心思想是面向接口编程
4)依赖倒转原则是基于这样的设计理念:

相对于细节的多变性,抽象的东西要稳定的多。
以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。
在Java中,抽象指的是接口或抽象类,细节就是具体的实现类。
5)使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
方式一:

package sjms;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Person person=new Person();
		person.receive(new Email());
	}
}
    class Email{
	    public String getInfo(){
	    	return "电子邮件信息:hello world";
	    }
    }
    //Person类 接收消息,将Email类作为参数 产生了依赖
    //如果参数发生了变化,即接收的是微信或短信 整个方法需要改动
	class Person{ 
		public void receive(Email email){
			System.out.println(email.getInfo());
		}
	}

分析:
1.简单,比较容易想到。
2.如果我们获取的对象是微信,短信等,则新增类,同时Perons也要增加相应的接收方法。违背开闭原则
3.解决:引入一个抽象的接口IReceiver,表示接收者,这样Person类与接口IReceiver发生依赖。因为Emai1, WeiXin等属于接收的范围,它们各自实现IReceiver接口,这样就符合依赖倒转原则。
方式二:

package sjms;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Person person=new Person();
		person.receive(new Email());
		//传入不同实现类 实现不同的接受
		person.receive(new WeiXin());
	}
}
    //定义接受的接口
    interface IReciver{
    	public String getInfo();
    }
    class Email implements IReciver{
	    public String getInfo(){
	    	return "电子邮件信息:hello world";
	    }
    }
    class WeiXin implements IReciver{
    	 public String getInfo(){
 	    	return "微信信息:hello Wechat";
 	    }
     }
     //Person 类 接受消息 将IReciver 接口 作为参数 产生了依赖
	class Person{ 
		public void receive(IReciver reciver){
			System.out.println(reciver.getInfo());
		}
	}

1)低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好。
2)变量的声明类型尽量是抽象类或接口,这样变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化。
B obj=new A(); //B是抽象类,A继承B,如果要扩展A,则只要扩充B
3)继承时遵循里氏替换原则

  1. 合成/聚合复用原则(Composite/Aggregate Reuse Principle)
    如果有类A和类B,类A有方法opertion1()、opertion2()和opertion3(),类B需要用类A的方法opertion2() ,如何做?
    法1:让类B继承类A,则类B可以使用类A的所有方法,当然包括opertion2() 。
    分析:
    1、暴露了类A的另外2个方法,虽然类B不需要使用,但是破坏封装。
    2、如果类A的实现发生改变,则类B的实现也发生改变;这就提高了类A和类B的耦合性。
    合成/聚合复用原则是:尽量使用合成/聚合的方式,而不是使用继承
    法2:
    B依赖于A
    class B{
    opertion (A a) {
    a. opertion2() ; } //用类A的对象a来调用
    }
    法3:
    A聚合到B 定义B的时候可以不马上设置A
    class B{
    A a; //数据成员a是A的对象
    void setA (A a){
    a. opertion2() ;} //通过a调用opertion2
    }
    法4:
    组合 B定义的同时马上有A
    class B{
    A a=new A( );
    }
    合成/聚合复用原则来源:
    在面向对象的设计中,
    如果直接继承基类,会破坏封装,因为继承将基类的实现细节暴露给子类;
    如果基类的实现发生改变,则子类的实现也发生改变;
    从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性。
    于是就提出了合成/聚合复用原则:尽量使用合成/聚合,不要使用类继承达到复用的目的。
    设计原则核心思想:
    找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
    针对接口编程,而不是针对实现编程。
    为了交互对象之间的松耦合设计
  2. 接口隔离原则(Interface Segregation Principle)
    不应该依赖不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。
    场景:
    1.类B实现接口Interface1 ,类A通过接口Interface1依赖(使用)类B,但是只会用到1,2,3方法
    2.类D实现接口Interface1 ,类C通过接口Interface1依赖(使用)类D,但是只会用到1,4,5方法
    方式一:
package sjms;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		A a=new A();
		C c=new C();
		//类A 通过接口 依赖类B
		a.depend1(new B());
		a.depend2(new B());
		a.depend3(new B());
		//类C 通过接口 依赖类D
		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 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");
    	}
    }
    //类D实现接口Interface1
    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");
    	}
    }
    //类A通过接口Interface1依赖(使用)类B,但是只会用到1,2,3方法
	class A{ 
		public void depend1(Interface1 i){
			i.operation1();
		}
		public void depend2(Interface1 i){
			i.operation2();
		}
		public void depend3(Interface1 i){
				i.operation3();
		}
	}
	//类C通过接口Interface1依赖(使用)类D,但是只会用到1,4,5方法
		class C{ 
			public void depend1(Interface1 i){
				i.operation1();
			}
			public void depend4(Interface1 i){
				i.operation4();
			}
			public void depend5(Interface1 i){
					i.operation5();
			}
		}

运行结果如下:

分析:

类A通过接口Interface1依赖类B
类C通过接口Interface1依赖类D
如果接口 Interface1对于类A和类C来说不是最小接口,那么类B和类D必须去实现它们不需要的方法。
如上代码违背了接口隔离原则(不应该依赖不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上)
类A不需要用到类B 4,5方法
类C不需要用到类D 2,3方法
按隔离原则应当这样处理:
将接口Interface1拆分为独立的几个接口, 类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
方式二:

package sjms;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		A a=new A();
		C c=new C();
		//类A 通过接口 依赖类B
		a.depend1(new B());
		a.depend2(new B());
		a.depend3(new B());
		//类C 通过接口 依赖类D
		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();
    }
    //类B实现接口Interface1,Interface2的所有方法
    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");
    	}
    }
    //类D实现接口Interface1,Interface3的所有方法
    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");
    	}
    }
    //类A通过接口Interface1,Interface2依赖(使用)类B,只会用到1,2,3方法
	class A{ 
		public void depend1(Interface1 i){
			i.operation1();
		}
		public void depend2(Interface2 i){
			i.operation2();
		}
		public void depend3(Interface2 i){
				i.operation3();
		}
	}
	//类C通过接口Interface1,Interface3依赖(使用)类D,只会用到1,4,5方法
		class C{ 
			public void depend1(Interface1 i){
				i.operation1();
			}
			public void depend4(Interface3 i){
				i.operation4();
			}
			public void depend5(Interface3 i){
					i.operation5();
			}
		}

接口隔离原则:
使用多个专门的接口比使用单一的总接口要好。换而言之,接口尽量细化,接口中的方法尽量少。
过于臃肿的接口是对接口的污染。不应该强迫依赖于它们不用的方法:
类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类C来说不是最小接口,而类B和类D必须去实现它们不需要的方法。
接口隔离原则与单一职责原则:
它们定义的规则不同:
单一职责要求的是类和接口职责单一,注重的是职责,没有要求接口的方法减少,例如一个职责可能包含10个方法,这10个方法都放在一个接口中,并且提供给多个模块访问,各个模块按照规定的权限来访问,在系统外通过文档约束不使用的方法不要访问,这符合单一职责原则,却不符合接口隔离原则,因为接口隔离原则要求“尽量使用多个专门的接口”,专门的接口指什么?就是指提供给多个模块的接口,提供给几个模块就应该有几个接口,而不是建立一个庞大的臃肿的接口,所有的模块可以来访问。
7. 迪米特法则(Law of Demeter)
1)一个对象应该对其他对象保持最少的了解
2)类与类关系越密切,耦合度越大
3)迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不泄露任何信息。
4)迪米特法则还有个更简单的定义:只与直接的朋友通信
5)直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
class A{
B objb; //成员变量中的类B
methoda(C para); //方法参数中的类C
D methoda(); //方法返回值中的类D } //类B,C,D是类A的直接朋友
class C{
//类E出现在局部变量中
methodc(){E para =new E;}
} //类E不是类C的直接朋友
实例:
有一个学校,下属有各个学院和总部,现要求打印出学校总部员工ID和学院员工的id。编程实现上面的功能。
方式一:

package sjms;

import java.util.ArrayList; //引入数组
import java.util.List;
//学校总部员工类
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 getAllEmployee(){
        List 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 getAllEmployee(){
        List 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 list1 = sub.getAllEmployee(); //局部变量
        System.out.println("-------学院员工---------");
        for (CollegeEmployee employee : list1) {
            System.out.println(employee.getId());
        }
        //获取到学校总部的员工
        List list2 = this.getAllEmployee(); //直接调用
        System.out.println("-------学校总部员工---------");
        for (Employee employee : list2) {
            System.out.println(employee.getId());
        }
    }
}
public class Main {
    public static void main(String[] args) {
        //创建了一个SchoolManager对象
        SchoolManager schoolManager = new SchoolManager();
        //输出学院的员工id 和 学校总部的员工信息
        schoolManager.printAllEmployee(new CollegeManager());
    }
}

方式二:

package sjms;

import java.util.ArrayList;
import java.util.List;
//学校总部员工类
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 getAllEmployee(){
        List list = new ArrayList();
        for(int i = 0; i < 10; i++) {
          CollegeEmployee emp = new CollegeEmployee();
          emp.setId("学院员工 id = " + i);
          list.add(emp);
        }
        return list;
    }
    
    
    void printEmployee(){
        List list1 = getAllEmployee();
        System.out.println("-------学院员工---------");
        for (CollegeEmployee employee : list1) {
            System.out.println(employee.getId());
        }
    }
}

//学校管理类
//分析SchoolMangager 类的直接朋友有哪些Employee,CollegeManager
//CollegeEmployee 不是直接朋友而是一个陌生类,这样违背了迪米特法则
class SchoolManager{
    public List getAllEmployee(){
        List 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 list2 = this.getAllEmployee();
        System.out.println("-------学校总部员工---------");
        for (Employee employee : list2) {
            System.out.println(employee.getId());
        }
    }
}
public class Main {
    public static void main(String[] args) {
        //创建了一个SchoolManager对象
        SchoolManager schoolManager = new SchoolManager();

        //输出学院的员工id 和 学校总部的员工信息
        schoolManager.printAllEmployee(new CollegeManager());
    }
}

迪米特法则又叫做最少知识原则(Least Knowledge Principle或简写为LKP)
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
也就是说,一个对象应当对其它对象有尽可能少的了解。
迪米特法则应该注意的问题:
迪米特法则的目的在于降低类与类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,使得相互间存在尽可能少的依赖关系。信息的隐藏促进了软件的复用。
但是注意:
由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系, 并不是要求完全没有依赖关系。

你可能感兴趣的:(柏柏羊的第三篇博客——常见面向对象的设计原则2020.03.17)