设计模式中的七大原则(代码 + 图解)
文中涉及的代码:链接:提取码:tqjq
文章目录
- 设计模式
- 1. 目的
- 2. 分类
- 3. 原则
- 3.1单一职责原则
- 3.2 接口隔离原则
- 3.3 依赖倒转原则
- 3.4 里氏替换原则
- 3.5 开闭原则
- 3.6 迪米特法则
- 3.7 合成复用原则
- 4. 参考
设计模式
1. 目的
对于某个具体的任务来说,如果要你编码实现它所要求的功能,不同的人会给不同的实现方式。可能写代码的人觉得自己的代码没有问题,但代码的重用性、可扩展性和可维护性等特性可能就很差。
因此,为了使软件可以很好的应对耦合性、内聚性、可维护性、重用性、灵活性和可阅读性等多方面的挑战,我们就需要使得编写的软件遵从某些原则和要求,而这就是设计模式需要完成的事情。
设计模型的目的是使得软件在上述的各种特性上具有更好的表现,具体来说:
重用性:相同的功能,不必重写编写;相似的功能,只需修改很少的部分
可读性:即编程的规范性,所写代码应该容易被其他人阅读和理解
可扩展性:可以很方法的在原有基础上添加或删除某些功能
可靠性:对软件某些部分的更新不会影响整体的性能
高内聚、低耦合
2. 分类
常用的设计模型总共有23种,它们大致可以分为创建型模式(Creational Patterns)、结构型模式(Structural Pattern)和行为型模式(Behavioral Pattern)。
3. 原则
在具体学习设计模式之前,我们需要先了解每种设计模式都需要遵循的原则。总的来说,设计模式中常用的七大原则如下所示:
- 单一职责原则
- 接口隔离原则
- 依赖倒转原则
- 里氏替换原则
- 开闭原则
- 迪米特法则
- 合成复用原则
3.1单一职责原则
单一职责原则(Single Responsibility Principle, SRP)指出:软件模块应该只有一个被修改的理由。简单来说,即一个类应该只负责一项职责。如果每个模块负责的职责有多个,那么修改该模块的原因可能就有很多,从而导致在修改该模块的同时还要考虑他它的修改是否会影响其他模块的正常运行。
其中类的职责可以分为两类:
- 数据职责:通过成员变量表示
- 行为职责:通过成员方法表示
单一职责原则的根本目的在于实现高内聚、低耦合。下面我们通过一个例子来感受一下单一职责原则指的是什么,以及为什么需要遵从。假设此时定义一个Person类,如下所示:
public class Person {private String name;public Person(String name) {this.name = name;}public void run(String subject){System.out.println(this.name + " is studying " + subject + " ...");}
}
类中只有一个run()
,方法接收一个字符串,输出信息指示某类人正在学习什么。例如:
public class Demo {public static void main(String[] args) {Person p = new Person("Student");p.run("CS");}
}
输出为Student is studying CS ...
。如果构造函数中传入Teacher、Farmer、Police等任何职业,输出永远表示的是在学习某个课,这显然是不太合理。究其原因是因为run()
负责的工作太多了,更好的方式是为不同的职业分别创建不同的类,然后在各个类中分别创建自己的run()
。
public class Student {public void run(String subject){System.out.println("Student is studying " + subject + " ...");}
}
public class Teacher {public void run(String subject){System.out.println("Teacher is teaching " + subject + " ...");}
}
分别创建了Student类和Teacher类之后,在使用时只需要根据具体的职业实例化相应的类对象,最后使用run()
即可。
public class Demo {public static void main(String[] args) {new Student().run("CS");new Teacher().run("CS");}
}
此时的输出为:
Student is studying CS ...
Teacher is teaching CS ...
这样就在类级别上遵从了单一职责原则。从另一个角度出发,另一个选择是应该为不同的职业创建不同的方法,从而在方法级别上遵从单一职责原则。
public class Person {private String name;public Person(String name) {this.name = name;}public void study(String subject){System.out.println(this.name + " is studying " + subject + " ...");}public void teach(String subject){System.out.println(this.name + " is teaching " + subject + " ...");}
}
然后在使用时根据不同的职业调用不同的方法:
public class Demo {public static void main(String[] args) {new Person("Student").study("CS");new Person("Teacher").teach("CS");}
}
此时输出为:
Student is studying CS ...
Teacher is teaching CS ...
将上面提到的类级别和方法级别上遵从单一原则表示到UML类图上,如下所示:
总结:通过遵从单一职责原则,程序可以降低类的复杂度,使得一个类只负责一项职责。从而提高了类的可读性和可扩展性。虽然上面的例子中从类和方法两个级别分别遵从单一职责原则,但实际中只有在类中方法足够少,方法逻辑足够简单才使用方法上的单一职责。否则,尽量都在类级别上遵从单一职责原则。
3.2 接口隔离原则
客户端不应该依赖于它所不需要的接口。
接口隔离原则(Interface Segregation Principle, ISP)指的是一旦一个接口中的方法过多,就需要将其划分为一些更细小的接口,从而使得接口的实现类(客户端)只知道它所想使用的方法即可。简单来说就是,每个接口应该只代表一类角色,它只包含所代表的角色所需的方法,而不应该包含此外其他方法。此外满足接口隔离原则时,接口首先必须满足职责单一原则,在满足高内聚的同时,接口中的方法应该尽量少。
例如,现在有一个ICar接口,接口中包含方法sell()
、repair()
和drive()
。如果此时创建接口的实现类Seller,那么就需要实现接口中全部的三个方法,而显然ISeller并不关心repair()
和drive()
。同理,如果现在创建实现类IDriver,它并不关心sell()
、repair()
。此时,程序就不满足接口隔离原则。为了使程序满足接口隔离原则,应该将接口进行更细的划分为ISellable、IRepairable和IDrivable,三个接口分别包含sell()
、repair()
和drive()
。
interface ISellable{void sell();
}interface IRepairable{void repair();
}
interface IDrivable{void drive();
}class Seller implements ISellable{@Overridepublic void sell() {System.out.println("Selling ...");}
}class Mechanic implements IRepairable{@Overridepublic void repair() {System.out.println("repairing ...");}
}class Driver implements IDrivable{@Overridepublic void drive() {System.out.println("driving ...");}
}public class Demo {public static void main(String[] args) {new Seller().sell();new Mechanic().repair();new Driver().drive();}
}
此时程序的输出为:
Selling ...
repairing ...
driving ...
如果将上面 的过程表现在UML类图上,大致可以表示为:
3.3 依赖倒转原则
高级模块不应该依赖低级模块,两者都应该依赖抽象。
抽象不应该依赖细节,细节应该依赖抽象。
依赖倒转原则(Dependency Inversion Principle, DIP)指的是代码应该依赖于抽象的类,而不要依赖于具体的类;要面向接口或抽象类编程,而不是针对具体的类编程。其实仔细对照我们实际的编程过程也好理解,虽然OOP已经将所有的东西都抽象为类,但是在此基础上还应该进行进一步的抽象。找到某些类中更加共性的东西,构建构建抽象的接口和抽象类,而在具体的实现类中实现细节。
假设此时定义Person类,类中的方法receive表示从何处接收信息,并调用参数代表的对象中的方法输出相应的信息。
class Email{public void show(){System.out.println("Email --- hello world...");}
}
class Person{public void receive(Email email){email.show();}
}
public class Demo {public static void main(String[] args) {Person p = new Person();p.receive(new Email());}
}
如果此时不只使用Email,还有WeChat、Facebook、Twitter……当前的方法就无法满足要求了。因此,根据依赖倒转原则,应该从不同的媒体中抽象出一个接口,接口中只包含show()
。然后,通过接口不同的实现类来表示不同的媒体。最后使用时,Person的receive()
中只需要接收接口的实例即可。它会根据传入的不同的接口实现类对象,输出相应的信息。
interface Receiver{void show();
}class Email implements Receiver{@Overridepublic void show() {System.out.println("Email --- hello world...");}
}class WeChat implements Receiver{@Overridepublic void show() {System.out.println("WeChat --- hello world...");}
}class Person{public void receive(Receiver receiver){receiver.show();}
}public class NewDemo {public static void main(String[] args) {new Person().receive(new Email());new Person().receive(new WeChat());}
}
将其表现在UML类图上如下所示:
3.4 里氏替换原则
里氏替换原则(Liskov Substitution Principle, LSP)指的是所有引用基类的地方必须能透明的使用其子类对象,而且使用子类对象进行替换后,程序不会出现任何的错误和异常,反之不成立。根据里氏替换原则,程序中进行使用基类类型进行对象的定义,在运行时确定子类类型,并用子类对象进行替换。
使用继承时,为了遵从里氏替换原则,程序应注意:
- 子类必须实现父类中声明的所有方法
- 子类进行不要重写父类中的方法
- 尽量把基类设计为抽象类或接口,子类为对应的实现类
通过让程序遵从里氏替换原则,使得程序可以更好的使用继承。假设此时定义一个Father类,类中有一个方法method()
实现传入的两个参数相加,并返回相加的结果。同时涉及一个Son类,类中重写method()
。
class Father{public int method(int x, int y){return x + y;}
}class Son extends Father{@Overridepublic int method(int x, int y) {return x - y;}
}
public class Demo {public static void main(String[] args) {System.out.println(new Father().method(3, 1));System.out.println(new Son().method(3, 1));}
}
此时的程序就不满足里氏替换原则。为了遵从里氏替换原则,应该将基类抽象为一个抽象类,抽象类中包含方法method()
,然后根据不同的运算分别创建接口的实现类,并重写抽象类中的方法。
abstract class Method{public abstract int method(int x, int y);
}class Add extends Method{@Overridepublic int method(int x, int y) {return x + y;}
}class Sub extends Method{@Overridepublic int method(int x, int y) {return x -y;}
}public class NewDemo {public static void main(String[] args) {Method m = new Add();System.out.println(m.method(3, 1));Method m1 = new Sub();System.out.println(m1.method(3, 1));}
}
同样的,我们将其表示到UML类图中,如下所示:
3.5 开闭原则
开闭原则(Open-Closed Principle, OCP)指的是一个软件实体应当对扩展开放,对修改关闭。也就是说在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即实现在不修改源代码的情况下改变这个模块的行为。
假设现在有一个GraphicEditor类,类中有一个方法drawShape()
来根据不同的参数画不同的图。
class Shape{int type;
}class Circle extends Shape {Circle() {super.type = 1;}
}class Triangle extends Shape {Triangle() {super.type = 2;}
}class GraphicEditor{public void drawShape(Shape s){if (s.type == 1){drawCircle();} else if (s.type == 2){drawTriangle();}}private void drawTriangle() {System.out.println("drawing triangle...");}private void drawCircle() {System.out.println("drawing circle...");}}
public class Demo {public static void main(String[] args) {GraphicEditor g = new GraphicEditor();g.drawShape(new Circle());g.drawShape(new Triangle());}
}
如果想要画其他的图形呢?我们只能创建新类,并同时更改GraphicEditor类中drawShape()
的逻辑。而这明显破坏了开闭原则,对于要实现的新功能,我们应该使用扩展的方式,而不是修改类的实现代码。
根据开闭原则,我们可以将Shape变为一个抽象类,类中包含抽象方法draw()
,Circle和Triangle继承Shape,并实现draw()
。GraphicEditor所做不再是根据对象的type值调用不同的方法,而是直接调用传入对象的draw()
即可,程序会根据传入的具体对象调用相应的方法。
abstract class Shape{int type;public abstract void draw();
}class Circle extends Shape {@Overridepublic void draw() {System.out.println("drawing circle...");}
}class Triangle extends Shape {@Overridepublic void draw() {System.out.println("drawing triangle...");}
}class Rectangle extends Shape{@Overridepublic void draw() {System.out.println("drawing Rectangle...");}
}class GraphicEditor{public void drawShape(Shape s){s.draw();}
}public class NewDemo {public static void main(String[] args) {GraphicEditor g = new GraphicEditor();g.drawShape(new Circle());g.drawShape(new Triangle());}
}
这样对于使用者来说,它不必修改使用的代码;对于提供方法来说,每需要增加新功能,只需要在此基础上进行扩展,而不必修改类的代码。
将上面的例子表示到UML类图中为:
3.6 迪米特法则
迪米特法则(Law of Demeter, LoD)又称最少知道原则,它是指一个对象应该对其他的对象保持最少的了解,对象之间的关系越密切,耦合度越大。迪米特法则另一种说法是:只与直接朋友进行通信。直接朋友指的是出现在成员变量、方法参数、方法返回值中的类称为当前类的直接朋友,而出现在局部变量中的类称为陌生人。
因此, 迪米特法则也可以定义为只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
例如明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与媒体公司的业务洽淡。这里的经纪人是明星的朋友,而媒体公司是陌生人,所以适合使用迪米特法则。
public class Star {String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
public class Company {String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
public class Agent {Star myStar;Company company;public Star getMyStar() {return myStar;}public void setMyStar(Star myStar) {this.myStar = myStar;}public Company getCompany() {return company;}public void setCompany(Company company) {this.company = company;}public void meeting(){System.out.println("meeting...");}public void business(){System.out.println("doing business...");}
}
将其表现在UML类图中为:
3.7 合成复用原则
合成复用原则(Composite Reuse Principle, CRP)又称为组合/聚合复用原则,它的核心思想是尽量使用聚合和组合的方式,而不是继承。也就是说,一个新的对象里通过关联关系(组合/聚合)来使用一些已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用其已有功能的目的。
假设此时有Car类,类中包含方法sell()
和repair()
,类Seller和Mechanic继承了Car。但对于Mechanic来说,它并不需要sell()
;对于Seller来说,它不需要repair()
。因此,使用继承来使用Car中的方法并不是一个好的选择。
class Car{public void sell(){System.out.println("selling...");}public void repair(){System.out.println("repairing...");}
}class Seller extends Car{ }class Mechanic extends Car{}public class Demo {public static void main(String[] args) {new Seller().repair();new Mechanic().sell();}
}
因此,根据合成复用原则,可以将Car类对象当作Seller和Mechanic的直接朋友使用,从而用组合/聚合代替继承。
class Car{public void sell(){System.out.println("selling...");}public void repair(){System.out.println("repairing...");}
}class Seller{public void sell(Car c){c.sell();}
}class Mechanic{public void repair(Car c){c.repair();}
}public class NewDemo {public static void main(String[] args) {new Seller().sell(new Car());new Mechanic().repair(new Car());}
}
例子表现到UML类图中为:
4. 参考
软件设计模式概述
菜鸟教程-设计模式
设计模式的七大原则
设计模式中的七大原则(代码 + 图解)相关推荐
- Java设计模式---设计模式概述及七大原则
网课指路:尚硅谷Java设计模式(图解+框架源码剖析)_哔哩哔哩_bilibili 设计模式介绍 1) 设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验, 模式不是代码 ...
- 设计模式常用的七大原则总结
设计模式常用的七大原则: 单一职责原则 在方法上(方法很少,逻辑足够简单)或类上遵守单一职责原则都可以. 可以降低类的复杂性 接口隔离原则 客户端不应该依赖他不需要的接口,即一个类对另一个类的依赖应该 ...
- 设计模式中遵循的原则:单一职责、开发-封闭、依赖倒转
设计模式中遵循的原则:单一职责.开放-封闭.依赖倒转 单一职责原则 一个类而言,应该仅有一个引起它变化的原因. 如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会消弱或者抑制这 ...
- 设计模式01-设计模式中的七大原则
设计模式 目录 设计模式 前言 设计模式的重要性 设计模式在软件中哪里? 正文 1.设计模式的目的 2.设计模式七大原则(设计模式的设计依据,开发人员应该遵循的规则) 1.单一职责原则 Single ...
- 设计模式-七大原则(图解一目了然)
文章目录 写在前面 设计模式 单一职责原则 接口隔离原则 依赖倒转原则 里氏替换原则 开闭原则 迪米特法则 合成复用原则 小结 写在前面 概念性的东西全是文字或代码容易看不下去,我尽量图文并茂. 还有 ...
- 设计模式-软件架构设计七大原则及开闭原则详解
前言 在日常工作中,我们使用Java语言进行业务开发的时候,或多或少的都会涉及到设计模式,而运用好设计模式对于我而言,又是一个比较大的难题.为了解决.克服这个难题,Remi酱特别开了这个博客来记录自己 ...
- 大话设计模式之设计模式遵循的七大原则
最近几年来,人们踊跃的提倡和使用设计模式,其根本原因就是为了实现代码的复用性,增加代码的可维护性.设计模式的实现遵循了一些原则,从而达到代码的复用性及增加可维护性的目的,设计模式对理解面向对象的三大特 ...
- 设计模式-软件设计七大原则
目录 综述 1.开闭原则 1.1开闭原则的定义 1.2开闭原则的作用 1.3开闭原则的实现方法 2.里氏替换原则 2.1里氏替换原则的定义 2.2里氏替换原则的作用 2.3里氏替换原则的实现方法 3. ...
- java设计模式——浅显易懂之七大原则
大家好,我是老王.一名正在学java设计模式的大三学生.准备连载java设计模式系列供自己以后复习和大家学习讨论.由于本人是初学者,站的角度更多是它是什么,我们要怎么做的角度进行思考,有出错的地方欢迎 ...
最新文章
- 获取VirtualBox COM对象失败,Unable to start the virtual device
- 微量元素重塑新生态-农业大健康·李喜贵:谋定功能性农业
- 开启mysql扩展模块_1.启用mysqli扩展模块
- 每天一道LeetCode-----在字符串s中找到最短的包含字符串t中所有字符的子串,子串中字符顺序无要求且可以有其他字符
- php tp框架分页源代码,ThinkPHP3.2框架自带分页功能实现方法示例
- 7.2图的存储结构(邻接表)
- 理解搜索引擎并且善用google
- 【MySQL】MySQL每秒统计一次showglobal status
- Go语言大神亲述:历七劫方可成为程序员!
- 使用seaborn制图(箱型图)
- 如何使用PDF expert在Mac上给PDF调整页面顺序?
- Oracle-Linux安装配置python3.6环境
- 增长量计算n+1原则_我是如何快速做资料分析的?(二)
- 网页文件是用HTML语言编写的,用HTML语言制作简单的网页
- 微型计算机经历了那几个阶段,微型计算机的发展经历了哪几个阶段,各阶段微处理器的主要特征是什么...
- java 415_@RequestBody接受参数报415错误
- 回收测试JInternalFrame
- Pycharm下载及安装保姆级教学(Mac)
- 小韦系统装工行网银U盾驱动的方法
- 推荐 : 你想用深度学习谱写自己的音乐吗?这篇指南来帮助你!(附代码)...
热门文章
- 查找计算机硬件和软件的翻译工具,计算机软件和硬件,PC computer software and hardware,音标,读音,翻译,英文例句,英语词典...
- 网关系统就该这么设计,万能通用,稳的一批!
- 数据大屏|基于Flask搭建数据可视化大屏1
- AI基础:卷积神经网络
- 【前端学习】Day-16 JS基础、循环、函数、数组、字符串、字典
- 岁末回首,义无反顾!
- java matcher.group_详解正则表达式Matcher类中group方法
- HCIE-RS论述题QOS
- 第五章 7-1 输出星期名缩写
- [量化-033]金融哲学-道德经解读-004-道德经最好理解的部分