设计模式七大原则的原理及其实现

程序员编程应该遵守的原则,也是设计模式的设计依据(设计模式为什么这样设计的依据,因为遵守了这七大原则)

单一职责原则

  • 概述:以一个类来说,一个类只负责一项职责,要是一个类负责两个不同的职责:职责1,职责2,当职责1因为后期需求变更需要改变这个类时,可能会造成职责2执行错误,所以可以将这个类分为类1和类2分别承担这两项职责。
package com.itguigu.principle.singleresoponsibility;
public class SingleBility1 {
	public static void main(String[] args) {
		Vehicle vehicle=new Vehicle();
		vehicle.run("摩托车");
		vehicle.run("汽车");
		vehicle.run("飞机");
	}
}
class Vehicle{
	public void run(String vehicle) {
			System.out.println(vehicle+"在公路上行驶");
	}
	 
}
/*输出:
 摩托车在公路上行驶
汽车在公路上行驶
飞机在公路上行驶
*/
//run()方法中违反了单一职责原则
//解决方案:根据交通工具运行方式不同,分解成不同的类-》方案二```

package com.itguigu.principle.singleresoponsibility;

public class SingleBility2 {
	public static void main(String[] args) {
		RoadVehicle road=new RoadVehicle();
		AirVehicle air=new AirVehicle();
		WaterVehicle water=new WaterVehicle();
		road.run("摩托车");
		road.run("汽车");
		air.run("飞机");
		water.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+"在水上上行驶");
}
}

/*输出:
摩托车在公路上行驶
汽车在公路上行驶
飞机在天上上行驶
轮船在水上上行驶
*/
//遵守了单一职责原则,
//改动很大,花销很大,将类分解还要修改客户端
//改进:直接修改,改动代码少-》方案三
package com.itguigu.principle.singleresoponsibility;

public class SingleBility3 {
	public static void main(String[] args) {
		Vehicle3 vehicle=new Vehicle3();
		vehicle.run("摩托车");
		vehicle.runWater("轮船");
		vehicle.runAir("飞机");
	}
}
class Vehicle3{
	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+"在水里上行驶");
    }
	 
}

/*输出:
 摩托车在公路上行驶
轮船在水里上行驶
飞机在天上行驶
*/
//这种修改方法没有对原来的类做大的修改,只是增加了方法,
//没有完全遵守单一职责,只在方法上遵守单一职责```

注意事项:
降低类的复杂度,一个类只负责一项职责
提高类的可读性,可维护性
降低变更引起的风险
在逻辑足够简单,才能在类的级别违反单一职责原则,而只在方法级别遵守单一职责原则

接口隔离原则

  • 概述:客户端不应该依赖他不需要的接口,即一个类对另外一个类的依赖应该建立在最小的接口上【就是一个类依赖一个接口应该是最少的实现方法,不要有多余的方法存在,比如有6个方法,但是只依赖三个,这种就不是最小接口,即违反了接口隔离原则】

举个栗子:引入以下问题

  • 依赖关系:

在这里插入图片描述

  • 依据类图编写代码实现:
package com.itguigu.principle.InterfaceSegregatiob;

public class Segregation1 {
	public static void main(String[] args) {
		
	}
}

class A{//A类通过interface1依赖(使用)B类,但是只会用到123方法,别的都浪费了
	public void depend1(interface1 inter) {
		 inter.operation1();
	}
	public void depend2(interface1 inter) {
		 inter.operation2();
	}
	public void depend3(interface1 inter) {
		 inter.operation3();
	}
}

class C{//C类通过interface1依赖(使用)D类,但是只会用到145方法,别的都浪费了
	public void depend1(interface1 inter) {
		 inter.operation1();
	}
	public void depend4(interface1 inter) {
		 inter.operation4();
	}
	public void depend5(interface1 inter) {
		 inter.operation5();
	}
}

interface interface1{
	 void operation1();
	 void operation2();
	 void operation3();
	 void operation4();
	 void operation5();
}
 
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");
		
	}
	
}


上面方法不满足隔离原则【因为类B和类D都必须去实现类A和类B一些不需要的方法
解决办法:是将interface1拆分称为几个独立的接口,类A和类C分别与他们需要的接口建立依赖关系。这就是接口隔离原则【要保证最小接口(把大的接口拆成小的接口)】

  • 改进后满足接口隔离原则的依赖关系:

在这里插入图片描述

  • 改进后的代码实现:
 package com.itguigu.principle.InterfaceSegregatiob;

public class Segregation2 {
	public static void main(String[] args) {
		A a  = new A();
		a.depend1(new B());//A类通过接口去依赖B类
		a.depend2(new B());
		a.depend3(new B());
	}
}

class A{ //A类通过interface1,interface2去依赖B类,去使用B类的一些方法
	public void depend1(interface1 inter) {
		 inter.operation1();
	}
	public void depend2(interface2 inter) {
		 inter.operation2();
	}
	public void depend3(interface2 inter) {
		 inter.operation3();
	}
}

class C{ //C类通过interface1,interface2去依赖D类,去使用类D的一些方法
	public void depend1(interface1 inter) {
		 inter.operation1();
	}
	public void depend4(interface3 inter) {
		 inter.operation4();
	}
	public void depend5(interface3 inter) {
		 inter.operation5();
	}
}

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 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");
		
	}
	
}

依赖倒置(倒转)原则

  • 概述:
    ①高层模块不应该依赖低层模块,二者都应该依赖其抽象(抽象类或者接口),不要去依赖一个具体的子类
    ②抽象不应该依赖细节,细节应该依赖抽象
    ③依赖倒转(倒置)的中心思想是面向接口编程
    ④依赖倒置原则是的设计理念是:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或抽象类,细节就是具体的实现类
    ⑤使用接口或者抽象类的目的是制定好规范,而不是设计任何具体的操作,把展现细节的任务交给他们的实现类去完成。

举个栗子:

  • 引入问题:完成一个Person类接收消息的功能
  • 传统的方式
package com.itguigu.principle.inversion;
//依赖倒转原则
public class Invertion{
	public static void main(String[] args) {
		new Person().receive(new Email());
	}
}

//完成一个Person接收消息的功能
//传统方式完成:
/*分析:
 * 1.简单,容易实现,容易想到
 * 2.如果要接收微信,短信等,又要新增类,而且Person类也要增加相应的方法
 * 3.解决思路:引入一个抽象的接口IReceiver表示接收者,这样Person类与此接口发生依赖
 * 因为:Email与微信,短信等都属于要接收的范围,他们可以各自实现Receiver的接口,这样就满足了依赖倒转原则
 * 体现了接口编程,细节依赖抽象
 * */
class Person{
	public void receive(Email email) {
		System.out.println(email.getInfo());
	}
	
}
class Email{
	public String getInfo() {
		return "电子邮件:"+"love you";
	}
}
  • 改进后符合依赖倒转原则的方式
 package com.itguigu.principle.inversion;

public class Invertion {
	public static void main(String[] args) {
		new Person().receive(new Email());
		new Person().receive(new Wechat());
	}
}

class Person{
	public void receive(IReceiver re){
		System.out.println(re.getInfo());
	}
}

//完成一个Person接收消息的功能
interface IReceiver{
	String getInfo();
}

class Email implements IReceiver{
	public String getInfo() {
		return "我是电子邮件:hello";
	}
}

class Wechat implements IReceiver{//对接口的依赖,稳定性比较好

	public String getInfo() {
		return "我是微信:hi";
	}
	
}
 
  • 依赖关系传递的三种方式和应用实例
    ①接口传递
    在这里插入图片描述
    ②构造方法传递
    在这里插入图片描述
    ③setter方式传递
    在这里插入图片描述
  • 注意事项:

《1》低层模块尽量都要有抽象类或接口,或者两者都有,这样程序的稳定性会更
《2》变量的声明类型尽量是抽象类或者接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化(对象与其引用之间有个缓冲层,就是对象的引用类型是其父类,而不是其本身,要扩展功能时,只需要在父类加,那么子类自然就有了)
《3》继承时遵循里式替换原则

里式替换原则

  • 概述:
    父类中凡是已经实现好的方法,实际上是设定的规范和契约
    继承带来弊端,程序之间增大了耦合关系,父类修改,子类会被影响
    正确使用继承就要满足,里式替换原则

  • 概念:引用基类的地方必须能够透明的使用其子类的对象

  • 要求:在使用继承时,遵循里式替换原则,在子类中尽量不要重写父类的方法

  • 继承让两个类的耦合性增强了,在适当的情况下,可以通过聚合,组合或者依赖来解决问题

  • 引入问题

package com.itguigu.principle.Loskev;

public class Liskov {
	public static void main(String[] args) {
		
		//问题:方法被无意之间重写了,这造成了错误
		//解决:将原来的父类和子类都集成一个更通俗的基类,
		//原有的继承关系去掉,采用依赖,聚合,组合灯关系代替
		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));//本意是调用父类的方法
		System.out.println("1-8="+b.func1(1,8));
		System.out.println("11+3+9="+b.func1(11,3));	
	}
}

class A{//返回两个数的差
	public int func1(int num1,int num2) {
		return num1-num2;
	}
}
class B extends A{//新增两个数相加再加9
	//无意间重写了父类方法
	public int func1(int num1,int num2) {
		return num1+num2;
	}
	public int func2(int num1,int num2) {
		return func1(num1,num2)+9;
	}
}

  • 解决方法:
package com.itguigu.principle.Loskev;

public class Liskov {
	public static void main(String[] args) {
		 
		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));//本意是调用父类的方法
		System.out.println("1+8="+b.func1(1,8));
		System.out.println("11+3+9="+b.func1(11,3));	
		
		//使用组合仍然可以调用到A类的方法
		System.out.println("11-3="+new B().fun3(11,3));
	}
}
//创建一个更加基础的类
class Base{//把更加基础的方法和成员写到Base类
			
}

class A extends Base{
	//返回两个数的差
	public int func1(int num1,int num2) {
		return num1-num2;
	}
}
//B类继承了A
//增加了一个新功能:完成两个数相加,然后和9求和
class B extends Base{
	//如果B需要使用A类的方法,使用组合关系
	private A a = new A();
	//这里,重写了A类的方法,可能是无意识
	public int func1(int num1,int num2) {
		return num1+num2;
	}
	public int func2(int num1,int num2) {
		return func1(num1,num2)+9;
	}
	
	//我们仍然使用A的方法
	public int fun3(int num1,int num2){
		return this.a.func1(num1, num2);
	}
}

开闭原则

  • 概述:
    《1》开闭原则是编程中最基础,最重要的设计原则
    《2》一个软件实体,如类,模块和函数应该对扩展开放【对提供方】,对修改关闭【使用方】。用抽象构建框架,用实现扩展细节。
    《3》当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
    《4》编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则
    举个栗子:

  • 题目:绘制图形

  • 这种方法存在的优缺点:
    优点:简单
    缺点:违反了OCP原则,使用方和提供方都修改了代码,当我们要增加新的功能时,尽量不修改或者少修改代码。当我们再次添加一个新的图形种类时,需要再做修改

  • 代码演示:

package com.itguigu.principle.ocp;

public class Ocp {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		GraphicEditor ga = new GraphicEditor();
		ga.drawShape(new Rectangle());
		ga.drawShape(new Circle());
	}

}

class GraphicEditor{
	//接收Shape对象,然后根据type,来绘制不同的图形
	public void drawShape(Shape s){
		if(s.m_type==1){
			drawRectangle(s);
		}else if(s.m_type==2){
			drawCircle(s);
		}
	}
	//绘制矩形
	public void drawRectangle(Shape r){
		System.out.println("绘制矩形");
	}
	//绘制圆形
	public void drawCircle(Shape r){
		System.out.println("绘制圆形");
	}
}
class Shape{
	int m_type;
}

class Rectangle extends Shape{
	Rectangle(){
		super.m_type=1;
	}
}

class Circle extends Shape{
	Circle(){
		super.m_type=2;
	}
}



  • 改进后满足OCP(开闭原则)的代码
package com.itguigu.principle.ocp;

public class Ocp {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		GraphicEditor ga = new GraphicEditor();
		ga.drawShape(new Rectangle());
		ga.drawShape(new Circle());
	}

}

class GraphicEditor{
	//接收Shape对象,然后根据type,来绘制不同的图形
	public void drawShape(Shape s){
		 s.draw();
	}
}
abstract class Shape{
	int m_type;

	public abstract void draw();
}

class Rectangle extends Shape{
	Rectangle(){
		super.m_type=1;
	}
	public void draw(){
		System.out.println("绘制矩形");
	}
}

class Circle extends Shape{
	Circle(){
		super.m_type=2;
	}
	public void draw(){
		System.out.println("绘制圆形");
	}
}

迪米特法则(最少知道原则)

  • 概述
    《1》一个对象应该对其他对象保持最少的了解
    《2》类与类关系越密切,耦合度越大
    《3》迪米特法则又叫最少知道原则即一个类对自己依赖的类知道的越少越好。页就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄露任何信息。
    《4》迪米特法则还有个更简单的定义:只与直接的朋友通信
    《5》直接的朋友:每个对象都会与其他对象由耦合关系,只要两个对象之间有耦合关系,我们就说两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的 形式出现在类的内部。
    举个栗子:
  • 题目:有一个学校,下属有各个学院和总部,现要求打印出学校总部员工ID和学院员工的id。
  • 代码实现:
package com.itguigu.principle.Demeter;

import java.util.ArrayList;
import java.util.List;

//迪米特法则(最少知道法则)
//客户端使用
public class Demeter1 {
	public static void main(String[] args) {
	    //创建了一个SchoolManager对象
		SchoolManager school = new SchoolManager();
		//输出学院员工id,和学校总部的员工id
		school.printAllEmployee(new CollegeManager());
	}
}

//学校总部员工
class Employee{
	private String id;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = 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<CollegeEmployee>();
		//增加了十个员工
		for(int i = 0;i<10;i++){
			CollegeEmployee emp = new CollegeEmployee();
			emp.setId("学院员工id="+i);
			list.add(emp);
		}
		return list;
	}
}
//学校管理类

/*分析:
 * Employee(方法返回值),CollegeManager(方法参数)是直接朋友,
 * CollegeEmployee不是直接朋友
 * 违背了迪米特法则:陌生的类不要以局部变量的方式出现
 * 
 * */
class SchoolManager{
	//返回学校总部的所有员工
	
	
		public List<Employee> getAllEmployee(){
			List<Employee> list  = new ArrayList<Employee>();
			//增加了五个员工
			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 e : list1) {
				System.out.println(e.getId());
			}
			
			List<Employee> list2  = this.getAllEmployee();
			System.out.println("-----------学校总部员工-------------");
			for (Employee e : list2) {
				System.out.println(e.getId());
			}
			
		}
}
  • 修改后符合迪米特法则的代码
package com.itguigu.principle.Demeter;

import java.util.ArrayList;
import java.util.List;

//迪米特法则(最少知道法则)
//客户端使用
public class Demeter {
	public static void main(String[] args) {
	    //创建了一个SchoolManager对象
		SchoolManager school = new SchoolManager();
		//输出学院员工id,和学校总部的员工id
		school.printAllEmployee(new CollegeManager());
	}
}

//学校总部员工
class Employee{
	private String id;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = 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<CollegeEmployee>();
		//增加了十个员工
		for(int i = 0;i<10;i++){
			CollegeEmployee emp = new CollegeEmployee();
			emp.setId("学院员工id="+i);
			list.add(emp);
		}
		return list;
	}
	//添加了这个方法
	public void printEmployee(){
		List<CollegeEmployee> list1  = getAllEmployee();
		System.out.println("-----------分公司员工-------------");
		for (CollegeEmployee e : list1) {
			System.out.println(e.getId());
		}
	}
	
	
}
//学校管理类
 
class SchoolManager{
	//返回学校总部的所有员工
	
	
		public List<Employee> getAllEmployee(){
			List<Employee> list  = new ArrayList<Employee>();
			//增加了五个员工
			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  = getAllEmployee();
			System.out.println("-----------学校总部员工-------------");
			for (Employee e : list2) {
				System.out.println(e.getId());
			}
			
		}
}
  • 总结:把自己类里面的处理逻辑封装好,只对外提供方法,不要把在别的类里面处理自己的逻辑;迪米特法则是降低类之间的耦合;但是值得注意的是,由于每个类都减少不必要的依赖,因此迪米特方法只是要求降低类间(对象间)耦合关系,并不是完全没有依赖关系

合成复用法则

  • 概述:原则是尽量使用合成或聚合的方式,而不是使用继承
    在这里插入图片描述

总结

  1. 找出应用中可能需要变化之外,把他们独立出来,不要和那些不需要变化的代码混在一起
  2. 针对接口编程,而不是针对实现编程
  3. 为了交互对象之间的松耦合设计而努力
  4. 工厂,单例,装饰,适配器,代理
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值