• 敲几年代码了,还讲不清楚设计模式的七大原则?


    前言

    遵守前人所总结的设计模式可以让程序具有更好的代码重用性、可扩展性、可靠性、可读性,使得程序具有高内聚低耦合的特性,那么设计模式又遵守哪些原则呢?或者说设计模式设计的依据是什么呢?本文就讲解设计模式所遵循的七大原则。

    一、单一职责原则

    该原则要求程序中一个类尽可能只负责一项职责。因为一个类如果负责多项职责,有可能改变其中一个职责的需求时,导致该类负责的其它职责出现错误。

    二、接口隔离原则

    1. 概念

    该原则要求设计的接口应该是最小接口,将一个存在冗余的大接口拆分为多个最小接口,即一个类实现该接口中的所有方法都会被用到,而不是实现了,却用不到。

    2. 未遵守接口隔离原则案例

    A和B实现了接口interface1,C和D分别依赖于A和B,但是C类中只用到了A类实现的fun1,fun2,fun3,剩余实现的fun4和fun5都没用到;D类中只用到了B类实现的fun1,fun4,fun5,剩余实现的fun2和fun3都没用到;因此设计的接口并非最小接口,不满足接口隔离原则,应该设计多个接口,实现接口的隔离。
    在这里插入图片描述

    代码示例:

    interface interface1
    {
    	void fun1();
    	void fun2();
    	void fun3();
    	void fun4();
    	void fun5();
    }
    
    class A implements interface1
    {
    
    	@Override
    	public void fun1() {
    
    	}
    
    	@Override
    	public void fun2() {
    
    	}
    
    	@Override
    	public void fun3() {
    
    	}
    
    	@Override
    	public void fun4() {
    
    	}
    
    	@Override
    	public void fun5() {
    
    	}
    }
    
    class B implements interface1
    {
    
    	@Override
    	public void fun1() {
    
    	}
    
    	@Override
    	public void fun2() {
    
    	}
    
    	@Override
    	public void fun3() {
    
    	}
    
    	@Override
    	public void fun4() {
    
    	}
    
    	@Override
    	public void fun5() {
    
    	}
    }
    
    class C
    {
    	//C类依赖于A类用到了fun1
    	public void depend1(interface1 A)
    	{
    		A.fun1();
    	}
    	//C类依赖于A类用到了fun2
    	public void depend2(interface1 A)
    	{
    		A.fun2();
    	}
    	//C类依赖于A类用到了fun3
    	public void depend3(interface1 A)
    	{
    		A.fun3();
    	}
    	//但是A类实现的fun4,fun5都没用到,这不是白白实现了吗?
    }
    
    class D
    {
    	//C类依赖于B类用到了fun1
    	public void depend1(interface1 B)
    	{
    		B.fun1();
    	}
    	//C类依赖于B类用到了fun4
    	public void depend2(interface1 B)
    	{
    		B.fun4();
    	}
    	//C类依赖于B类用到了fun5
    	public void depend3(interface1 B)
    	{
    		B.fun5();
    	}
    	//但是B类实现的fun2,fun3都没用到,这不是白白实现了吗?
    }
    
    • 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
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106

    3. 将上个案例修改为遵守接口隔离原则

    修改方法很简单,就是将接口拆分为多个最小接口,实现接口直接的隔离。
    在这里插入图片描述

    代码示例:

    interface interface1
    {
    	void fun1();
    }
    
    interface interface2
    {
    	void fun2();
    	void fun3();
    }
    
    
    interface interface3
    {
    	void fun4();
    	void fun5();
    }
    
    class A implements interface1,interface2
    {
    
    	@Override
    	public void fun1() {
    
    	}
    
    	@Override
    	public void fun2() {
    
    	}
    
    	@Override
    	public void fun3() {
    
    	}
    }
    
    class B implements interface1,interface3
    {
    
    	@Override
    	public void fun1() {
    
    	}
    
    	@Override
    	public void fun4() {
    
    	}
    
    	@Override
    	public void fun5() {
    
    	}
    }
    
    class C
    {
    	//C类依赖于A类用到了fun1
    	public void depend1(interface1 A)
    	{
    		A.fun1();
    	}
    	//C类依赖于A类用到了fun2
    	public void depend2(interface2 A)
    	{
    		A.fun2();
    	}
    	//C类依赖于A类用到了fun3
    	public void depend3(interface2 A)
    	{
    		A.fun3();
    	}
    	//A类实现接口中的所有方法都有用到,满足接口隔离原则
    }
    
    class D
    {
    	//C类依赖于B类用到了fun1
    	public void depend1(interface1 B)
    	{
    		B.fun1();
    	}
    	//C类依赖于B类用到了fun4
    	public void depend2(interface3 B)
    	{
    		B.fun4();
    	}
    	//C类依赖于B类用到了fun5
    	public void depend3(interface3 B)
    	{
    		B.fun5();
    	}
    	//B类实现接口中的所有方法都有用到,满足接口隔离原则
    }
    
    • 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
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95

    三、依赖倒转(倒置)原则 --基于接口编程

    1. 概念

    该原则中心思想就是要求程序面向接口编程,即细节依赖于抽象。因为相对于细节的多变性,抽象的东西要稳定的多。在java中抽象指的是接口或者抽象类,细节就是具体的实现类。依赖倒转原则就是要去程序使用接口或者抽象类制定好规则,然后具体的实现细节由实现类去完成。

    2. 未遵守依赖倒转原则的案例

    Person类完成接收消息的方法,代码如下

    class Email {
    	public String getInfo() {
    		return "电子邮件信息: hello,world";
    	}
    }
    
    class Person {
        //直接对具体类依赖,违反依赖倒转原则
    	public void receive(Email email ) {
    		System.out.println(email.getInfo());
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    该实现简单明了,但是直接基于具体类进行实现,违反了依赖倒转原则,如果我们想要通过微信,或者QQ接收消息,那就需要再增加相应的微信类和QQ类,并且在Person类中增加相应的方法

    3. 将上个案例修改为遵守依赖倒转原则

    引入一个抽象的接口IReceiver, 表示接收者, 这样Person类与接口IReceiver发生依赖。实现代码如下:

    //定义接口
    interface IReceiver {
    	public String getInfo();
    }
    
    class Email implements IReceiver {
    	public String getInfo() {
    		return "电子邮件信息: hello,world";
    	}
    }
    
    //增加微信
    class WeiXin implements IReceiver {
    	public String getInfo() {
    		return "微信信息: hello,ok";
    	}
    }
    
    //方式2
    class Person {
    	//这里我们是对接口的依赖
    	public void receive(IReceiver receiver ) {
    		System.out.println(receiver.getInfo());
    	}
    }
    
    • 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

    为了遵守依赖倒转原则,我们在程序开发时应该将底层模块尽量都用抽象类或者接口,这样可以使得程序具有更好的稳定性,其次变量的声明类型尽量是接口或者抽象类,这样有利于程序的扩展及优化。

    四、里氏替换原则–尽量替换继承

    里式替换就是用来解决继承所存在的弊端,它要求使用继承时子类尽可能不要重写父类的方法,或者在适当的情况下最好用聚合、组合、依赖替换继承。继承给程序带来便利的同时,也产生了明显的弊端,比如如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生bug。

    如果非要用继承关系,子类又非要对父类中的方法进行修改,那么可以将原来的父类和子类都继承一个更通俗的类,并将原来的继承关系用聚合,组合,依赖等替换。

    如本来B继承自A,而B又要对A中的fun方法重写,这就违反了里式替换原则,UML类图如下:
    在这里插入图片描述
    这是我们可以将A和B继承于一个更通俗的类,让将B组合到A中,使其满足里式替换原则,UML类图如下:
    在这里插入图片描述

    五、开闭原则【核心原则】–扩展开放,修改关闭

    1. 概念

    开闭原则是编程中最重要、最基础的设计原则,编程中的其它设计原则及设计模式的最终目的都是为了遵守开闭原则。

    开闭原则要求当软件需求发生变化时,尽量是通过扩展模块来实现变化,而不是通过修改已有模块来实现变化,即对扩展开放,对修改关闭。

    2. 未遵守开闭原则的案例

    实现绘图功能,UML类图如下:
    在这里插入图片描述

    从类图中可以看出,该案例将绘值什么图形的控制权放在了GraphicEditor类【使用方】,那么如果未来想要增加一个图形,就首选需要扩展一个图形类,然后也要修改已有的GraphicEditor类,为其添加方法,并修改其中的drawShape方法,这违反了开闭原则。

    具体实现代码如下:

    public class Ocp {
    
    	public static void main(String[] args) {
    		//使用看看存在的问题
    		GraphicEditor graphicEditor = new GraphicEditor();
    		graphicEditor.drawShape(new Rectangle());
    		graphicEditor.drawShape(new Circle());
    		graphicEditor.drawShape(new Triangle());
    	}
    
    }
    
    //这是一个用于绘图的类 [使用方]
    class GraphicEditor {
    	//接收Shape对象,然后根据type,来绘制不同的图形
    	public void drawShape(Shape s) {
    		if (s.m_type == 1)
    			drawRectangle(s);
    		else if (s.m_type == 2)
    			drawCircle(s);
    		else if (s.m_type == 3)
    			drawTriangle(s);
    	}
    
    	//绘制矩形
    	public void drawRectangle(Shape r) {
    		System.out.println(" 绘制矩形 ");
    	}
    
    	//绘制圆形
    	public void drawCircle(Shape r) {
    		System.out.println(" 绘制圆形 ");
    	}
    	
    	//绘制三角形
    	public void drawTriangle(Shape r) {
    		System.out.println(" 绘制三角形 ");
    	}
    }
    
    //Shape类,基类
    class Shape {
    	int m_type;
    }
    
    class Rectangle extends Shape {
    	Rectangle() {
    		super.m_type = 1;
    	}
    }
    
    class Circle extends Shape {
    	Circle() {
    		super.m_type = 2;
    	}
    }
    
    //新增画三角形
    class Triangle extends Shape {
    	Triangle() {
    		super.m_type = 3;
    	}
    }
    
    
    • 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
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    3. 将上个案例修改为遵守开闭原则

    修改后的UML类图如下:
    在这里插入图片描述
    从图中可看出,修改后的策略是将绘制什么图形的控制权给了各个实现类,此时再新增一个图形,只需要扩展一个图形类即可,不需要修改已有模块的代码

    具体实现代码如下:

    public class Ocp {
    
    	public static void main(String[] args) {
    		//使用看看存在的问题
    		GraphicEditor graphicEditor = new GraphicEditor();
    		graphicEditor.drawShape(new Rectangle());
    		graphicEditor.drawShape(new Circle());
    		graphicEditor.drawShape(new Triangle());
    		graphicEditor.drawShape(new OtherGraphic());
    	}
    
    }
    
    //这是一个用于绘图的类 [使用方]
    class GraphicEditor {
    	//接收Shape对象,调用draw方法
    	public void drawShape(Shape s) {
    		s.draw();
    	}
    
    	
    }
    
    //Shape类,基类
    abstract class Shape {
    	public abstract void draw();//抽象方法
    }
    
    class Rectangle extends Shape {
    
    	@Override
    	public void draw() {
    		// TODO Auto-generated method stub
    		System.out.println(" 绘制矩形 ");
    	}
    }
    
    class Circle extends Shape {
    	@Override
    	public void draw() {
    		// TODO Auto-generated method stub
    		System.out.println(" 绘制圆形 ");
    	}
    }
    
    //新增画三角形
    class Triangle extends Shape {
    	@Override
    	public void draw() {
    		// TODO Auto-generated method stub
    		System.out.println(" 绘制三角形 ");
    	}
    }
    
    //新增一个图形
    class OtherGraphic extends Shape {
    
    	@Override
    	public void draw() {
    		// TODO Auto-generated method stub
    		System.out.println(" 绘制其它图形 ");
    	}
    }
    
    • 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
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    六、迪米特法则–只与直接朋友交流

    1. 概念

    迪米特法则(Demeter Principle)又叫最少知道原则即一个类对自己依赖的类知道的越少越好(最好只是使用依赖类的相关方法,而不要过多的剖析依赖类的细节)。 即尽可能的降低类与类之间的不必要的耦合,但不是要求完全消除依赖关系

    迪米特法则就是要去只与直接的朋友沟通。什么是直接的朋友?我们称出现在成员变量,方法参数,方法返回值中的类为直接的朋友。而出现在局部变量中的类不是直接的朋友

    也就是说,迪米特法则要求陌生的类最好不要以局部变量的形式出现在类的内部。

    如下代码就违法了迪米特法则:

    class A{
    	
    }
    class B{
    	void fun()
    	{
    		A a=new A();//出现在局部变量的其它类,违反迪米特法则
    		//调用a做一些操作
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    七、合成复用原则–少用继承

    • 适用情况:
      在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分,新的对象通过这些对象的委派达到复用已有功能的目的。
    • 设计原则:尽量使用合成/聚合,尽量不要使用继承

    八、总结

    一切设计原则都是为了使得交互对象之间的耦合度尽可能降低而努力。

    九、参考资料

    尚硅谷设计模式

  • 相关阅读:
    java毕业设计开题报告基于SSM实现的家政服务系统
    lombok插件浅析
    Rust WASM 与 JS 计算素数性能对比
    MyBatis 环境搭建配置全过程【IDEA】
    rename 批量修改文件名简单用法
    八大元素定位方法(Python/Java)
    C++基础——匿名对象介绍、拷贝对象时的一些编译器优化
    RuntimeError: Address already in use 端口号冲突,解决思路总结
    Java随笔-动态代理
    WSL1 安装 debian xfce 用xrdp 导入远程桌面
  • 原文地址:https://blog.csdn.net/qq_34720818/article/details/126366627