设计模式六⼤规则
1.单⼀职责原则(六⼤规则中的⼩萝莉,⼈见⼈爱):描述的意思是每个类都只负责单⼀的功能,切不可太多,并且⼀个类应当尽量的把⼀个功能做到极致。
2.⾥⽒替换原则(六⼤原则中最⽂静的姑娘,但却不太招⼈喜欢):这个原则表达的意思是⼀个⼦类应该可以替换掉⽗类并且可以正常⼯作。
3. 接⼝隔离原则(六⼤原则当中最挑三拣四的挑剔⼥,胸部极⼩):也称接⼝最⼩化原则,强调的是⼀个接⼝拥有的⾏为应该尽可能的⼩。
4.依赖倒置原则(六⼤原则中最⼩鸟依⼈的姑娘,对抽象的东西⾮常依赖):这个原则描述的是⾼层模块不该依赖于低层模块,⼆者都应该依赖于抽象,抽象不应该依赖于细节,细节应该依赖于抽象。
5.迪⽶特原则(六⼤原则中最害羞的姑娘,不太爱和陌⽣⼈说话):也称最⼩知道原则,即⼀个类应该尽量不要知道其他类太多的东西,不要和陌⽣的类有太多接触。
6.开-闭原则(六⼤原则中绝对的⼤⼤,另外五妹⼼⽢情愿⾂服):最后⼀个原则,⼀句话,对修改关闭,对扩展开放。
《简介》
说到设计模式,当初第⼀次听到时,第⼀反应就是很深奥,完全理解不了这个概念到底是什么意思,下⾯我先从⽹上摘录⼀份定义。
设计模式(Designpattern)是⼀套被反复使⽤、多数⼈知晓的、经过分类编⽬的、代码设计经验的总结。
上⾯是百度当中的解释,来解释⼀下这句简单的话的含义,⼏个关键词。
反复使⽤:这个不⽤过多解释,设计模式被使⽤太多了,上个系列spring源码当中就出现了很多模式,记忆中⽐较深刻的有模板模式,代理模式,单例模式,⼯⼚模式等等。
多数⼈知晓:这个就不需要过多解释了。
分类编⽬:就是说可以到⼀些特征去划分这些设计模式,从⽽进⾏分类。
代码设计经验:这句很重要,设计经验的总结,也就是说设计模式,是为了指导设计⽽从经验中总结出来的套路。
还有⼀种说法是说,设计模式是可以解决特定场景的问题的⼀系列⽅法,其实我觉得这个解释更贴切⼀点。
《为何学习设计模式》
上⾯简单的介绍,是让各位⾸先搞清楚设计模式是什么,下⾯我们来说说为什么要学习设计模式,学习总要有个驱动⼒。
有过⼯作经验的⼈都知道,特别是那些在维护⼀个项⽬的⼈更是体会的贴切,像我就是其中⼀个,有的时候,⼀个很简单的需求,或者说,本来应该是很快就可以实现的需求,但是由于系统当初设计的时候没有考虑这些需求的变化,或者随着需求的累加,系统越来越臃肿,导致随便修改⼀处都可能造成不可预料的后果,或者是我本来可以修改下配置⽂件或者改⼀处代码就可以解决的事情,结果需要修改N处代码才可以达到我的⽬的。
以上都是⾮常可怕的后果,这些我已经深深体会过了。
《设计模式的好处及注意点》
设计模式可以帮助我们改善系统的设计,增强系统的健壮性、可扩展性,为以后铺平道路。
但是,这些是我当初第⼀次接触设计模式时的感受,现在我并不这么认为,设计模式可以改善系统的设计是没错,但是过多的模式也会系统变的复杂。所以当我们第⼀次设计⼀个系统时,请将你确定的变化点处理掉,不确定的变化点千万不要假设它存在,如果你曾经这么做过,那么请改变你的思维,
让这些虚⽆的变化点在你脑⼦中彻底消失。
因为我们完全可以使⽤另外⼀种⼿法来容纳我们的变化点,那就是重构,不过这是我们在讨论过设计模式之后的事情,现在我们就是要
把这些设计模式全部理解,来锻炼我们的设计思维,⽽不是只做⼀个真正的码农。
《指导原则:六⼤规则》
在学习设计模式之前,为了不让设计模式显得很模式,我们还必须了解⼀个东西,那就是程序设计六⼤原则。
这些原则是指导模式的规则,我会给⼀些原则附上⼀个例⼦,来说明这个原则所要表达的意思,注意,原则是死的,⼈是活的,所以并不是要你完完全全遵守这些规则,否则为何数据库会有逆范式,只是在可能的情况下,请尽量遵守。
单⼀职责原则(六⼤规则中的⼩萝莉,⼈见⼈爱):描述的意思是每个类都只负责单⼀的功能,切不可太多,并且⼀个类应当尽量的把⼀个功能做到极致。
否则当你去维护这个系统的时候,你会后悔你当初的决定,下⾯是我⾃⼰思索的例⼦,给各位参考⼀下,给出代码。
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class Calculator {
public int add() throws NumberFormatException, IOException{
File file = new File("E:/");
BufferedReader br = new BufferedReader(new FileReader(file));
int a = Integer.adLine());
int b = Integer.adLine());
return a+b;
}
public static void main(String[] args) throws NumberFormatException, IOException {
Calculator calculator = new Calculator();
System.out.println("result:" + calculator.add());
}
}
来看上⾯这个例⼦,这个⽅法的作⽤是从⼀个⽂件中读出两个数,并返回它们的和,我相信各位也能看出当中有明显的多职责问题。如果没看出来的话,我想问各位⼀句,如果我想算这个⽂件中两个数字的差该如何做?
相信答案应该是我COPY出来⼀个div⽅法,把最后的加号改成减号。好吧,那我要除法呢?乘法呢?取模呢?COPY四次吗。这就造成了很多很多的代码重复,这不符合系统设计的规则。下⾯我把上述程序改善⼀下。
我们分离出来⼀个类⽤来读取数据,来看Reader。
st;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class Reader {
int a,b;
public Reader(String path) throws NumberFormatException, IOException{
BufferedReader br = new BufferedReader(new FileReader(new File(path)));
a = Integer.adLine());
b = Integer.adLine());
}
public int getA(){
return a;
}
public int getB(){
return b;
}
}
下⾯是我们单独的计算器类。
st;
import java.io.IOException;
public class Calculator {
public int add(int a,int b){
return a + b;
}
public static void main(String[] args) throws NumberFormatException, IOException {
Reader reader = new Reader("E:/");
Calculator calculator = new Calculator();
System.out.println("result:" + calculator.A(),B()));
}
}
我们将⼀个类拆成了两个类,这样以后我们如果有减法,乘法等等,就不⽤出现那么多重复代码了。
以上是我临时杜撰的例⼦,虽然很简单,并且没有什么现实意义,但是我觉得⾜够表达单⼀职责的意思,并且也⾜够说明它的重要性。单⼀职责原则是我觉得六⼤原则当中最应该遵守的原则,因为我在实践过程中发现,当你在项⽬的开发过程中遵循它,⼏乎完全不会给你的系统造成任何多余的复杂性,反⽽会令你的程序看起来井然有序。
⾥⽒替换原则(六⼤原则中最⽂静的姑娘,但却不太招⼈喜欢):这个原则表达的意思是⼀个⼦类应该可以替换掉⽗类并且可以正常⼯作。
那么翻译成⽐较容易理解的话,就是说,⼦类⼀般不该重写⽗类的⽅法,因为⽗类的⽅法⼀般都是对外公布的接⼝,是具有不可变性的,你不该将⼀些不该变化的东西给修改掉。
上述只是通常意义上的说法,很多情况下,我们不必太理解⾥⽒替换这个⽂静的姑娘,⽐如模板⽅法模式,缺省适配器,装饰器模式等⼀些设计模式,就完全不搭理这个⽂静的姑娘。
不过就算如此,如果你真的遇见了不得不重写⽗类⽅法的场景,那么请你考虑,你是否真的要把这个类作为⼦类出现在这⾥,或者说这样做所换来的是否能弥补你失去的东西,⽐如⼦类⽆法代替⽗类⼯作,那么就意味着如果你的⽗类可以在某⼀个场景⾥⼯作的很正常,那么你的⼦类当然也应该可以,否则就会出现下述场景。
⽐如我们有某⼀个类,其中有⼀个⽅法,调⽤了某⼀个⽗类的⽅法。
//某⼀个类
public class SomeoneClass {
//有某⼀个⽅法,使⽤了⼀个⽗类类型
public void someoneMethod(Parent parent){
}
}
⽗类代码如下。
public class Parent {
public void method(){
System.out.println("parent method");
}
}
结果我有⼀个⼦类把⽗类的⽅法给覆盖了,并且抛出了⼀个异常。
public class SubClass extends Parent{
//结果某⼀个⼦类重写了⽗类的⽅法,说不⽀持该操作了
public void method() {
throw new UnsupportedOperationException();
}
}
这个异常是运⾏时才会产⽣的,也就是说,我的SomeoneClass并不知道会出现这种情况,结果就是我调⽤下⾯这段代码的时候,本来我们的思维是Parent都可以传给someoneMethod完成我的功能,我的SubClass继承了Parent,当然也可以了,但是最终这个调⽤会抛出异常。
public class Client {
public static void main(String[] args) {
SomeoneClass someoneClass = new SomeoneClass();
someoneClass.someoneMethod(new Parent());
someoneClass.someoneMethod(new SubClass());
}
}
这就相当于埋下了⼀个个陷阱,因为本来我们的原则是,⽗类可以完成的地⽅,我⽤⼦类替代是绝对没有问题的,但是这下反了,我每次使⽤⼀个⼦类替换⼀个⽗类的时候,我还要担⼼这个⼦类有没有给我埋下⼀个上⾯这种。
所以⾥⽒替换原则是⼀个需要我们深刻理解的原则,因为往往有时候违反它我们可以得到很多,失去⼀⼩部分,但是有时候却会相反,所以要想做到活学活⽤,就要深刻理解这个原则的意义所在。
接⼝隔离原则(六⼤原则当中最挑三拣四的挑剔⼥,胸部极⼩):也称接⼝最⼩化原则,强调的是⼀个接⼝拥有的⾏为应该尽可能的⼩。
如果你做不到这⼀点你经常会发现这样的状况,⼀个类实现了⼀个接⼝,⾥⾯很多⽅法都是空着的,只有个别⼏个⽅法实现了。
这样做不仅会强制实现的⼈不得不实现本来不该实现的⽅法,最严重的是会给使⽤者造成假象,即这个实现类拥有接⼝中所有的⾏为,结果调⽤⽅法时却没收获到想要的结果。
⽐如我们设计⼀个⼿机的接⼝时,就要⼿机哪些⾏为是必须的,要让这个接⼝尽量的⼩,或者通俗点讲,就是⾥⾯的⾏为应该都是这样⼀种⾏为,就是说只要是⼿机,你就必须可以做到的。
上⾯就是接⼝隔离原则这个挑剔⼥所挑剔的地⽅,假设你没有满⾜她,你或许会写出下⾯这样的⼿机接⼝。
public interface Mobile {
public void call();//⼿机可以打电话
public void sendMessage();//⼿机可以发短信单例模式的几种实现方式
public void playBird();//⼿机可以玩愤怒的⼩鸟?
}
上⾯第三个⾏为明显就不是⼀个⼿机应该有的,或者说不是⼀个⼿机必须有的,那么上⾯这个⼿机的接⼝就不是最⼩接⼝,假设我现在的⾮智能⼿机去实现这个接⼝,那么playBird⽅法就只能空着了,因为它不能玩。
所以我们更好的做法是去掉这个⽅法,让Mobile接⼝最⼩化,然后再建⽴下⾯这个接⼝去扩展现有的Mobile接⼝。
public interface SmartPhone extends Mobile{
public void playBird();//智能⼿机的接⼝就可以加⼊这个⽅法了
}
这样两个接⼝就都是最⼩化的了,这样我们的⾮智能⼿机就去实现Mobile接⼝,实现打电话和发短信的功能,⽽智能⼿机就实现SmartPhone接⼝,实现打电话、发短信以及玩愤怒的⼩鸟的功能,两者都不会有多余的要实现的⽅法。
最⼩接⼝原则⼀般我们是要尽量满⾜的,如果实在有多余的⽅法,我们也有补救的办法,⽽且有的时候也确实不可避免的有⼀些实现类⽆法全部实现接⼝中的⽅法,这时候就轮到缺省适配器上场了,这个在后⾯再介绍。
依赖倒置原则(六⼤原则中最⼩鸟依⼈的姑娘,对抽象的东西⾮常依赖):这个原则描述的是⾼层模块不该依赖于低层模块,⼆者都应该依赖于抽象,抽象不应该依赖于细节,细节应该依赖于抽象。
上⾯⿊⾊加粗这句话是这个原则的原版描述,我来解释下我⾃⼰的理解,这个原则描述的是⼀个现实当中的事实,即实现都是易变的,⽽只有抽象是稳定的,所以当依赖于抽象时,实现的变化并不会影响客户端的调⽤。
⽐如上述的计算器例⼦,我们的计算器其实是依赖于数据读取类的,这样做并不是很好,因为如果我的数据不是⽂件⾥的了,⽽是在数据库⾥,这样的话,为了不影响你现有的代码,你就只能将你的Reader类整个改头换⾯。
或者还有⼀种⽅式就是,你再添加⼀个DBReader类,然后把你所有使⽤Reader读取的地⽅,全部⼿动替换成DBReader,这样其实也还可以接受,那假设我有的从⽂件读取,有的从数据库读取,有的从XML⽂件读取,有的从⽹络中读取,有的从标准的键盘输⼊读取等等。
你想怎么办呢?
所以我们最好的做法就是抽象出⼀个抽象类或者是接⼝,来表述数据读取的⾏为,然后让上⾯所有的读取⽅式所实现的类都实现这个接⼝,⽽我们的客户端,只使⽤我们定义好的接⼝,当我们的实现变化时,我只需要设置不同的实际类型就可以了,这样对于系统的扩展性是⼀个⼤⼤的提升。
针对上⾯简单的数据读取,我们可以定义如下接⼝去描述。
public interface Reader {
public int getA();
public int getB();
}
让我们原来的Reader改名为FileReader去实现这个接⼝,这样计算器就依赖于抽象的接⼝,这个依赖是⾮常稳定的,因为不论你以后要
从哪读取数据,你的两个获取数据的⽅法永远都不会变。
这样,我们让DBReader,XMLReader,NETReader,StandardOutPutStreamReader等等,都可以
实现Reader这个接⼝,⽽我们的客户端调⽤依赖于⼀个Reader,这样不管数据是从哪来的,我们都可以应对⾃如,因为我根本不关⼼你是什么Reader,我只知道你能让我获得A和B这两个值就⾏了。
这便是我们依赖于抽象所得到的灵活性,这也是JAVA语⾔的动态特性给我们带来的便利,所以我们⼀定要好好珍惜这个依赖于抽象的姑娘。
迪⽶特原则(六⼤原则中最害羞的姑娘,不太爱和陌⽣⼈说话):也称最⼩知道原则,即⼀个类应该尽量不要知道其他类太多的东西,不要和陌⽣的类有太多接触。
这个原则的制定,是因为如果⼀个类知道或者说是依赖于另外⼀个类太多细节,这样会导致耦合度过⾼,应该将细节全部⾼内聚于类的内部,其他的类只需要知道这个类主要提供的功能即可。
所谓⾼内聚就是尽可能将⼀个类的细节全部写在这个类的内部,不要漏出来给其他类知道,否则其他类就很容易会依赖于这些细节,这样类之间的耦合度就会急速上升,这样做的后果往往是⼀个类随便改点东西,依赖于它的类全部都要改。
⽐如我把上述的例⼦改变⼀下。
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class Reader {
int a,b;
private String path;
private BufferedReader br;
public Reader(String path){
this.path = path;
}
public void setBufferedReader() throws FileNotFoundException{
br = new BufferedReader(new FileReader(new File(path)));
}
public void readLine() throws NumberFormatException, IOException{
a = Integer.adLine());
b = Integer.adLine());
}
public int getA(){
return a;
}
public int getB(){
return b;
}
}
Reader类改成上述这个样⼦,显然它给其他的类透漏了太多细节,让别⼈知道了它的太多细节,这样我客户端调⽤的时候就很可能写成如下形式。
public class Client {
public static void main(String[] args) throws Exception {
Reader reader = new Reader("E:/");
reader.setBufferedReader();
int a = A();
int b = B();
//以下⽤于计算等等
}
}
这样客户端就依赖于reader的多个⾏为才能最终获取到A和B两个数值,这时候两个类的耦合度就太⾼了,我们更好的做法使⽤访问权限限制将⼆者都给隐藏起来不让外部调⽤的类知道这些。就像下⾯这样。
public class Reader {
int a,b;

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。