设计模式详解之程序设计六大原则
原文链接:http://www.cnblogs.com/zuoxiaolong/p/pattern1.html
1、设计模式简介
设计模式(Designpattern)是一套被反复使用(spring源码当中就出现了很多模式,如模板模式,代理模式,单例模式,工厂模式等)、多数人知晓的、经过分类编目的、代码设计经验的总结。还有一种说法,设计模式是可以解决特定场景的问题的一系列方法。
设计模式可以帮助我们改善系统的设计,增强系统的健壮性、可扩展性,为以后铺平道路,但是过多的模式也会系统变的复杂。
在学习设计模式之前,先了解程序设计六大原则。 这些原则是指导模式的规则,原则是死的,人是活的,所以并不是要完完全全遵守这些规则,否则为何数据库会有逆范式,只是在可能的情况下,请尽量遵守。
2、单一职责原则
单一职责原则:描述的意思是每个类都只负责单一的功能,切不可太多,并且一个类应当尽量的把一个功能做到极致。
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:/data.txt"); BufferedReader br = new BufferedReader(new FileReader(file)); int a = Integer.valueOf(br.readLine()); int b = Integer.valueOf(br.readLine()); return a+b; } public static void main(String[] args) throws NumberFormatException, IOException { Calculator calculator = new Calculator(); System.out.println("result:" + calculator.add()); } }
上面这个例子有明显的多职责问题,如果想算这个文件中两个数字的差,应该是COPY出来一个方法,把最后的加号改成减号。那除法,乘法,取模要COPY四次,这就造成了很多的代码重复。下面分离出来一个Reader类用来读取数据。
package com.test; 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.valueOf(br.readLine()); b = Integer.valueOf(br.readLine()); } public int getA(){ return a; } public int getB(){ return b; } }
package com.test; 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:/data.txt"); Calculator calculator = new Calculator(); System.out.println("result:" + calculator.add(reader.getA(),reader.getB())); } }
将一个类拆成了两个类,这样以后如果有减法,乘法等等,就不用出现那么多重复代码了,单一职责原则是六大原则当中最应该遵守的原则。
3、里氏替换原则
里氏替换原则:这个原则表达的意思是一个子类应该可以替换掉父类并且可以正常工作。
子类一般不该重写父类的方法,因为父类的方法一般都是对外公布的接口,是具有不可变性的,不该将一些不该变化的东西给修改掉。上述只是通常意义上的说法,很多情况下,不必一直遵守该原则,比如模板方法模式,缺省适配器,装饰器模式等。不过就算如此,如果真的遇见了不得不重写父类方法的场景,那么请考虑,这样做所换来的是否能弥补失去的东西,比如子类无法代替父类工作,就会出现问题。
//某一个类 public class SomeoneClass { //有某一个方法,使用了一个父类类型 public void someoneMethod(Parent parent){ parent.method(); } }
//父类
public class Parent { public void method(){ System.out.println("parent method"); } }
//子类
public class SubClass extends Parent{ //结果某一个子类重写了父类的方法,说不支持该操作了 public void method() { throw new UnsupportedOperationException(); } }
public class Client { public static void main(String[] args) { SomeoneClass someoneClass = new SomeoneClass(); someoneClass.someoneMethod(new Parent()); someoneClass.someoneMethod(new SubClass()); } }
这就相当于埋下了一个个陷阱,因为本来父类可以完成的地方,子类替代是没有问题的,但是现在每次使用一个子类替换一个父类的时候,还要担心这个子类有没有埋下一个上面这种炸弹。里氏替换原则需要深刻理解,因为往往有时候违反它可以得到很多,失去一小部分,但是有时候却会相反,所以要想做到活学活用。
4、接口隔离原则
接口隔离原则:也称接口最小化原则,强调的是一个接口拥有的行为应该尽可能的小。
如果不遵守会发现这样的状况,一个类实现了一个接口,里面很多方法都是空着的,只有个别几个方法实现了。这样做不仅会强制实现的人不得不实现本来不该实现的方法,最严重的是会给使用者造成假象,即这个实现类拥有接口中所有的行为,结果调用方法时却没收获到想要的结果。比如设计一个手机的接口时,就要手机哪些行为是必须的,要让这个接口尽量的小。
public interface Mobile { public void call();//手机可以打电话 public void sendMessage();//手机可以发短信 public void playBird();//手机可以玩愤怒的小鸟? }
上面第三个行为明显就不是一个手机必须有的,非智能手机去实现这个接口,那么playBird方法就只能空着了,建立下面这个接口去扩展现有的Mobile接口。
public interface SmartPhone extends Mobile{ public void playBird();//智能手机的接口就可以加入这个方法了 }
这样两个接口就都是最小化的了,最小接口原则一般是要尽量满足的,如果实在有多余的方法,也有补救的办法,而且有的时候也确实不可避免的有一些实现类无法全部实现接口中的方法,这时候就轮到缺省适配器上场了,这个后面有介绍。
5、依赖倒置原则
依赖倒置原则:这个原则描述的是高层模块不该依赖于低层模块,二者都应该依赖于抽象,抽象不应该依赖于细节,细节应该依赖于抽象。
实现都是易变的,而只有抽象是稳定的,所以当依赖于抽象时,实现的变化并不会影响客户端的调用。比如上述的计算器例子,计算器其实是依赖于数据读取类的,这样做并不是很好,因为如果数据不是文件里的了,而是在数据库里,这样的话,为了不影响你现有的代码,你就只能将你的Reader类整个改头换面。或者还有一种方式就是,再添加一个DBReader类,然后把所有使用Reader读取的地方,全部手动替换成DBReader,这样其实也还可以接受,那假设有的从文件读取,有的从数据库读取,有的从XML文件读取等等就不好办。所以最好的做法就是抽象出一个抽象类或者是接口,来表述数据读取的行为,然后让上面所有的读取方式所实现的类都实现这个接口,而客户端只使用定义好的接口,当实现变化时,只需要设置不同的实际类型就可以了,这样对于系统的扩展性是一个大大的提升。
针对上面简单的数据读取,我们可以定义如下接口去描述。
public interface Reader { public int getA(); public int getB(); }
让原来的Reader改名为FileReader去实现这个接口,这样计算器就依赖于抽象的接口,这个依赖是非常稳定的,因为不论以后要从哪读取数据,两个获取数据的方法永远都不会变。这便是依赖于抽象所得到的灵活性,这也是JAVA语言的动态特性带来的便利。
6、迪米特原则
迪米特原则:也称最小知道原则,即一个类应该尽量不要知道其他类太多的东西,不要和陌生的类有太多接触。
这个原则的制定,是因为如果一个类知道或者说是依赖于另外一个类太多细节,这样做的后果往往是一个类随便改点东西,依赖于它的类全部都要改,会导致耦合度过高,应该将细节全部高内聚于类的内部,其他的类只需要知道这个类主要提供的功能即可。
比如把上述的例子改变一下。
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.valueOf(br.readLine()); b = Integer.valueOf(br.readLine()); } 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:/test.txt"); reader.setBufferedReader(); reader.readLine(); int a = reader.getA(); int b = reader.getB(); //以下用于计算等等 } }
这样客户端就依赖于reader的多个行为才能最终获取到A和B两个数值,这时候两个类的耦合度就太高了,更好的做法使用访问权限限制将二者都给隐藏起来。
public class Reader { int a,b; private String path; private BufferedReader br; public Reader(String path) throws Exception{ super(); this.path = path; setBufferedReader(); readLine(); } //注意,我们变为私有的方法 private void setBufferedReader() throws FileNotFoundException{ br = new BufferedReader(new FileReader(path)); } //注意,我们变为私有的方法 private void readLine() throws NumberFormatException, IOException{ a = Integer.valueOf(br.readLine()); b = Integer.valueOf(br.readLine()); } public int getA(){ return a; } public int getB(){ return b; } }
迪米特原则虽说是指的一个类应当尽量不要知道其他类太多细节,但其实更重要的是一个类应当不要让外部的类知道自己太多。两者是相辅相成的,只要将类的封装性做的很好,那么外部的类就无法依赖当中的细节。
7、开-闭原则
开-闭原则:最后一个原则,一句话,对修改关闭,对扩展开放。
就是说任何的改变都不需要修改原有的代码,而只需要加入一些新的实现,就可以达到目的,这是系统设计的理想境界,但是没有任何一个系统可以做到这一点,哪怕一直最欣赏的spring框架也做不到,虽说它的扩展性已经强到变态。这个原则更像是前五个原则的总纲,前五个原则就是围着它转的,只要尽量的遵守前五个原则,那么设计出来的系统应该就比较符合开闭原则了,相反,如果违背了太多,那么系统或许也不太遵循开闭原则。
在《大话设计模式》一书中,提到一句话与各位共勉,即用抽象构建框架,用细节实现扩展。以上六个原则写出来是为了指导后面设计模式的描述,下期预告:单例模式。