每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心,这样,你就能一次又一次地使用该方案而不必做重复劳动。这个思想同样适用于面向对象的设计模式,核心就在于提供给了相关问题的解决方案
学习设计模式, 就是为了让我们显着更加专业, 是的, 我们是专业coder😂
希望童鞋们,多看几遍,直至“懂”,这个“懂”怎么定义?了解概念就算懂了吗?
我认为,真正使用设计模式,设计原则到实际项目业务中,解决或者预见性的设计一些流程。给以后的代码,流程提高可读性,可扩展、可维护性才是懂。
当然,理解概念后,需要自己去学习优秀项目中真正应用到的地方。学习理解为什么别人这么用,如果是自己会怎么去设计?多多思考。
每个公司都不缺只会写业务代码的程序员。努力成为别人无法代替的程序员。
设计模式的六大原则
https://www.cnblogs.com/denghailei/p/6214604.html
①单一原则
意思: 只做一件事.
我用个伪代码表达一下
//计算文件中的
class Calculator {
//相加
public int add() throws NumberFormatException, IOException{
File file = new File("E:/data.txt");
BufferedReader br = new BufferedReader(new FileReader(file));
int a = Integer.valueOf(br.readLine());
int b = Integer.valueOf(br.readLine());
return a+b;
}
}
要是谁封装了这么一个类, 然后你用了, 卧槽什么怎么只有加法, 没有减法 乘法, 你然后是不是要 copy 一下 ,把+ 号改为其他符号 .
q:这个类中哪里违反了单一原则呢?
梳理一下, 我们用这个计算类是为什么干什么, 计算文本中的和
引申一下, 实际的操作步骤:
① 获取文件内容
② 计算返回值
这个类同时拥有两个职责
q:这个类还存在什么问题?
在开发初期, 我们很有可能为了快速, 进行大量的copy, 这样就导致了很多的代码重复, 这样可能我们为了书写一个相减或相乘的方法, 来copy 加法中的代码进行修改
下面我们需要把上面的代码修改一下
//获取文件内容类
class Reader{
int a,b;
public Reader(String path)throws Exception{
BufferedReader br = new BufferedReader(new FileReader(new File(path)));
a= Integer.valueOf(br.readLine);
b= Integer.valueOf(br.readLine);
}
int getA(){
return a;
}
int getB(){
return b;
}
}
//单独的计算类
class Calculator{
int add(int , int b){
return a+b;
}
int subtract(int , int b){
return a-b;
}
}
优点:
增加代码的可读性,可维护性。
想象一下一个场景,一个方法中有N个功能代码堆积在一块。
后期如果修改功能,不说别人,就算自己开发的,也要从头开始阅读所有功能代码再找到要修改的地方。
如果使用单一原则就方便多了,一个功能块一个方法。
可读性大大提升,一眼过去即可找到要修改的功能模块。
ps:
其实我们项目中存在着大量类似代码,造成这种原因有很多种。我大体举例了几类
1.写代码随心所欲,拿到文档就开始写。
这种占比应该是最多的,不可否认,我以前也存在这样的问题。
往往在拿到需求文档后,第一时间开始写代码,所有代码堆积在一个类中,往往这是新手很喜欢犯的一个错误。
推荐大家拿到文档后,首先阅读别人类似功能的代码,思考有没有借鉴的地方(不是copy恶心代码),如果脑子第一反应是觉得代码恶心,阅读性差的话,好了不用copy了,自己设计一套优雅的流程,如果你继续copy,后面的童鞋继续copy你的.......N版迭代后,项目终有一天会无法维护。
别人在看到你的代码时,说出口往往是“什么垃圾代码”,而不是“卧槽,代码真骚啊。可以借鉴”。
②里氏替换原则
LSP的原定义比较复杂,我们一般对里氏替换原则 LSP的解释为:子类对象能够替换父类对象,而程序逻辑不变。
里氏替换原则有至少以下两种含义:
里氏替换原则是针对继承而言的,如果继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类重新定义 。子类只能通过新添加方法来扩展功能,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑和父类一致的方法,这时用子类对象将父类对象替换掉时,当然逻辑一致,相安无事。
如果继承的目的是为了多态,而多态的前提就是子类覆盖并重新定义父类的方法,为了符合LSP,我们应该将父类定义为抽象类,并定义抽象方法,让子类重新定义这些方法,当父类是抽象类时,父类就是不能实例化,所以也不存在可实例化的父类对象在程序里。也就不存在子类替换父类实例(根本不存在父类实例了)时逻辑不一致的可能。
不符合LSP的最常见的情况是,父类和子类都是可实例化的非抽象类,且父类的方法被子类重新定义,这一类的实现继承会造成父类和子类间的强耦合,也就是实际上并不相关的属性和方法牵强附会在一起,不利于程序扩展和维护。
如何符合LSP?总结一句话 —— 就是尽量不要从可实例化的父类中继承,而是要使用基于抽象类和接口的继承。
通俗点:子类拿来就可以当父类用,而且还不出错。接口要符合,限制要比父类宽泛。
优点:
代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性 提高代码的重用性 子类可以形似父类,但是又异于父类。 提高代码的可扩展性,实现父类的方法就可以了。许多开源框架的扩展接口都是通过继承父类来完成。 提高产品或项目的开放性
但是所有的事物都有二面性,继承除了有上述优点,也有下面缺点:
继承是侵入性的,只要继承,就必须拥有父类的所有方法和属性 降低了代码的灵活性,子类必须拥有父类的属性和方法,让子类有了一些约束
增加了耦合性,当父类的常量,变量和方法被修改了,需要考虑子类的修改,这种修改可能带来非常糟糕的结果,要重构大量的代码。
③接口隔离原则
开发中中 我们经常发现 我们在实现一个接口的同时, 有很多方法都是空的
//手机接口
public interface BaseMobile {`
public void call();//打电话
public void sendSms();//发短信
public void playBird();//玩游戏
}
只要是手机那就应该有打电话发短信的功能, 但是玩游戏的功能并不一定是每个手机都有的
所以我们可以先让去掉 玩游戏 这个接口 最小化这个接口
//修改后的代码
public interface BaseMobile {
public void call();
public void sendSms();
}
//适配器
public interface IOSMobile extends BaseMobile{
public void playBird();
}
这样, 就很清晰的理解接口最小化原则,
但是凡事都有特例,
不需要发短信功能的手机存在吗, 很显然存在啊. 同样的, 时代发展的很快, 手机的基本功能也在变迁, 就像这样是个手机应该就能上网之类的功能
ps:写到这里, 我明显感觉我的思考问题的方式在发生着改变, 我们的代码, 应该遵循的一些规范, 往往在人们的生活方式改变的同时改变着, 代码离不开生活, 美好的生活由代码构成, 在今后的社会, 有可能我们的代码就像是科学一样, 人人都应该了解她, 学习她.
④依赖倒置原则
原则:面向接口编程。抽象指的是接口或抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约, 而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
https://www.cnblogs.com/chenxkang/p/6657744.html
以上文章讲的非常好,这里就不重复赘述了。
//正确用法
//抽象 人
public interface People{
void myInfo();//自我介绍
void canDo();//能做什么
}
//中国人
public class China:People{
public void myInfo(){
System.out.println("我是中国人");
}
void canDo(){
System.out.println("我会code,搬砖,修车,开车,滑稽");
}
}
//美国人
public class America:People{
public void myInfo(){
System.out.println("我是美国人");
}
void canDo(){
System.out.println("我会code,mygod,yea,aye,huhuh");
}
}
//高层
public class PeopleInfo{
private People people;
public PeopleInfo(People people){
this.people =people;
}
public void myInfo(){
people.myInfo();
}
void canDo(){
people.canDo();
}
}
思考,如果这里的高层PeopleInfo被定义成了某个具体的实现(中国人)
`
//反面例子
//高层
public class PeopleInfo{
private China china ;
public PeopleInfo(China china ){
this.china =china ;
}
public void myInfo(){
china .myInfo();
}
void canDo(){
china .canDo();
}`可能暂时实现了业务,如果以后扩展,该高层移民,变成了美国人?岂不是重新修改PeopleInfo的代码?将其修改成America,如果在实际项目很复杂的场景,岂不是要重写?
因此PeopleInfo这个类依赖于接口People, 而具体方法不会影响PeopleInfo , 只要改变实现People即可 ,也就是细节依赖于抽象
并且 PeopleInfo 不依赖于China和America, 也就是依赖关系被倒置了
这里我解释下可能有的同学不是很清楚其中的利害 依赖倒置
PeopleInfo 依赖于 People ,当我们需要使用PeopleInfo做某些事情的时候, 这时需要注入China或其他实现类, 但是PeopleInfo并不依赖China,
PeopleInfo只依赖People .
⑤迪米特原则
迪米特法则(Law of Demeter )又叫做最少知识原则,也就是说,一个对象应当对其他对象尽可能少的了解。不和陌生人说话。英文简写为: LoD。
一切都是为了高内聚低耦合的封装思想
有点难理解,我也是看了非常做资料之后才有一点感悟。
https://blog.csdn.net/liuziteng0228/article/details/54845132
https://blog.csdn.net/macrohui29/article/details/83627614
https://www.cnblogs.com/xiaobai1226/p/8670245.html
https://www.cnblogs.com/junyuhuang/p/5630539.html
一个类公开的public方法和属性越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。因此,为了保持朋友类间的距离,在设计时需要反复衡量:是否还可以再减少public方法和属性,是否可以修改为private,package-private,protected等访问权限,是否可以加上final关键字。
注意: 迪米特原则要求类“羞涩”一点,尽量不要对外公开太多的public方法和非静态的public变量,尽量内敛,多使用private,package-private,protected等访问权限。
⑥开闭原则
定义:一个软件实体。如类/模块/函都应该对扩展开放,对修改关闭。
问题由来:在软件的生命周期内,因为变化,升级和维护等原因需要对软件原有代码进行修改,可能会给旧代码引入错误,也有可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现。
开闭原则是面向对象设计中最基础的设计原则,它指导我们如何建立稳定灵活的系统,开闭原则只定义了对修改关闭,对扩展开放。其实只要遵循前面5中设计模式,以及使用23种设计模式的目的就是遵循开闭原则,设计出来的软件就是符合开闭原则的。
用抽象构建架构,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保证架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了,当然前提是抽象要合理,要对需求的变更有前瞻性和预见性。
单一职责原则告诉我们实现类要职责单一;
里氏替换原则告诉我们不要破坏继承关系;
依赖倒置原则告诉我们要面向接口编程;
接口隔离原则告诉我们在设计接口的时候要精简单一;
迪米特法则告诉我们要降低耦合
(总纲)开闭原则告诉我们要对扩展开发,对修改关闭;
那么如何去遵守这六个原则,对这六个原则的遵守并不是是和否的问题,而是多和少的问题,也就是说,我们一般不会说有没有遵守,而是说遵守程度达到多少,任何事过犹不及,设计模式六个设计原则也是一样,制定这六个原则并不是一味的要求我们去遵守他们,而是根据实际情况灵活运用,
图中的每一条维度代表一项原则,我们依据对这个遵守程度在维度上画一个点,则如果对这项原则遵守的合理的话,这个点会落在红色的圈中,如果遵守的差会落在小圆圈里,遵守的差会落在大圆圈外面,一个良好的设计体现在图中,应该是六个点都在同心圆中的六边形。
如上图,设计1和设计2属于良好设计,他们对六项原则遵守程度都在合理范围内,设计3和设计4虽然有些不足,但也基本可以接受,设计5和设计6,一个几乎不遵守,一个遵守过度,那么这两种设计模式都是迫切需要重构的。
原文https://blog.csdn.net/zhengzhb/article/details/7296944
本文由 SAn 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为:
2019/10/18 10:28