前言
喜欢看技术性的文章,记得以前总是说设计模式不用太早接触之类的话, 促使我决定好好学习设计模式的原因
- 现在我觉得时机也差不多了
- 实在有些代码写完之后发现很难维护,或者改动的地方特别多, 肯定是我当时的设计有问题
所发表所有内容仅代表个人观点。
设计模式概念
说到设计模式,第一反应就是很深奥,完全理解不了这个概念到底是什么意思, 网上的定义最多的是: 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。
反复使用
, 多人知晓
: 意味着被众人认可
分类编目
: 归纳和总结特征进行分类
代码设计经验
: 经前人验证过的, 保证好使的套路
当然, 其实这都是个人理解的不同而不同, 权威的概念:
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。
为何学习设计模式
就像开篇我所说的, 当你发现一个新的需求,或者小的需求变更, 难以更改以前的代码, 有木有种牵一发而动全身的感觉?有? 那你跟我一样, 该好好学学设计模式了, 同学,其他不用多讲了, 还需要其他理由就自行百度吧.
设计模式的六大原则
单一职责
描述的意思是每个类都只负责单一的功能,切不可太多,并且一个类应当尽量的把一个功能做到极致。
这个我参考了一些资料, 发现怎么写的都有, 还要根据项目实际情况考虑,也不能说过分的拆分职责.
建议:
接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化
例子:
我看到一个例子, 简单描述:
public interface IPhone {
public void 拨号(string number)
public void 接电话()
public void 通话()
public void 应答()
public void 挂断()
}
原文解释:
拨号 ()
和挂断 ()
两个方法实现的是协议管理,分别负责拨号接通和挂机;通话 ()
和应答 ()
是数据的传送,把我们说的话转换成模拟信号或数字信号传递到对方,然后再把对方传递过来的信号还原成我们听得懂语言。我们可以这样考虑这个问题,协议接通的变化会引起这个接口或实现类的变化吗?会的!那数据传送(想想看,电话不仅仅可以通话,还可以上网)的变化会引起这个接口或实现类的变化吗?会的!那就很简单了,这里有两个原因都引起了类的变化,而且这两个职责会相互影响吗?电话拨号,我只要能接通就成,甭管是电信的还是网通的协议;电话连接后还关心传递的是什么数据吗?不关心,你要是乐意使用56K的小猫传递一个高清的片子,那也没有问题。通过这样的分析,我们发现类图上的电话 接口包含了两个职责,而且这两个职责的变化不相互影响,那就考虑拆开成两个接口,然后把这个接口拆成两个接口, 一个负责数据传输, 一个负责接与挂电话...
我认为这个说法也不完全正确, 像这种情况, 设计一个成一个"电话相关的接口, 也不是不对的,它具有这些方法, 很正常. 所以这东西仁者见仁, 智者见智!
单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否合理,但是“职责”和“变化原因”都是没有具体标准的,一个类到底要负责那些职责?这些职责怎么细化?细化后是否都要有一个接口或类?这些都需从实际的情况考虑。因项目而异,因环境而异。
总结:
这应该是很简单的一个原则了, 同时也是很模糊的一个原则, 需要经验, 环境, 团队协作等很多方面的考虑, 所以原则是死的 , 人是活的! !!! 尽量遵守!
里氏替换原则
这个原则表达的意思是一个子类应该可以替换掉父类并且可以正常工作。同样的,用基类替换现在的子类,必须保证程序运行(包括业务流程)都是不变的。这是我看了一些资料后的理解
例子:
首先设计一个Person类 人类, 具有很多能力, 包括生育, 但仅限女性对吧
public class Person {
// 生育 需要传入一个女人对象
public void bearChildren(Woman woman) {
woman.bearChildren();
}
}
再设计一个Woman 女人对象和更细致一点的Girl 女孩对象, 女孩对象继承女人对象
public class Woman {
public void bearChildren() {
System.out.println("顺利生产");
}
}
public class Girl extends Woman {
// 重写了父类的生育方法
@Override
public void bearChildren() {
throw new RuntimeErrorException(null, "我还是小孩,无法生孩子");
}
}
运行Person类的生育方法
public class App {
public static void main(String[] args) {
Person person = new Person();
person.bearChildren(new Woman());
person.bearChildren(new Girl());
}
}
运行结果:
woman 顺利生产
girl Exception in thread "main" javax.management.RuntimeErrorException: 我还是小孩,无法生孩子
这个异常是运行时才会产生的,也就是说,Person类并不知道会出现这种情况,Woman传给Person.bearChildRen完成生育功能,Girl类继承了Woman,当然也可以了实现同样功能,但是最终这个调用会抛出异常。
注意:
一个子类应该可以替换掉父类并且可以正常工作, 那么现在替换后程序没有正常工作, 所以违背了里氏替换原则
这项原则并不是要避免多态,而是要求子类在继承父类的时候,不能与父类已经定好的契约冲突,也就是不要重写父类已经实现的方法。
简而言之:子类必须可以替换成父类对象(并且行为不会变),子类不要添加基类没有的约束,那么怎么检验是否符合这个原则呢?就是用基类替换现在的子类,必须保证程序运行(包括业务流程)都是不变的。
子类不能添加父类没有的约束的例子:
比如场景是让汽车奔跑,子类是1.奔驰(前提是加100L汽油)2.宝马(前提要洗车才能跑)。
问题来了,不同的子类有了不同的约束条件。
public abstract class Car {
public abstract void Run();
}
public class Benz : Car {
public int Oil;
public overrride void Run() {
//跑
}
}
public class BMW: Car {
public bool WashCar;
public overrride void Run() {
//跑
}
}
下面有一个工厂方法(目的是体现用基类替换子类,因为工厂方法返回值是父类类型)
public Class Factory {
public static Car Create() {
if(1) return new Benz();
if(2) return new BMW();
}
}
最后是实际调用代码:
来一辆奔驰:Car Benz=Factory.Create(1)
;
来一辆宝马:Car BMW=Factory.Create(2)
;
然后我准备开车了....
Benz.Run();
BMW.Run();
您觉得能行吗?肯定不行,还没加油呢,还没洗车呢。
我前面还得先
Benz.Oil=100;
BMW.WashCar=true;
才能
Benz.Run();
BMW.Run();
但是明显这样就违背父类预期的原则了,怎么办?其实可以把参数放到子类构造函数的形参中。解决问题方案有多种,但原则就是”子类必须可以替换成父类对象(并且行为不会变),子类不要添加基类没有的约束"
依赖倒转原则
针对接口编程,依赖于抽象而不依赖于具体。
例子
我现在需要从一个文本文件中获取变量number的值, 有如下类
public class Reader {
public static int getNumber(String path){
BufferedReader br = new BufferedReader(new FileReader(new File(path)));
return Integer.valueOf(br.readLine())''
}
}
我调用 Reader.getNumber("file.text")
获取到了number的值
那么如果需求变更了, 需要从数据库中获取这个number的值呢? 从XML文件中获取呢? 我需要改Reader getNumber的方法,或者我需要加一堆方法, getNumberFormXMl... getNumberFormDB?
不如直接写一个接口
public interface Redaer {
public int getNumber();
}
用不同的实现getNumber
即可. 你可以有XMLReader
, 或者DBReader
来实现getNumber
,客户端只管Reader.getNumber()
就好, 无需关心number是怎么来的.
接口隔离原则
也称接口最小化原则,强调的是一个接口拥有的行为应该尽可能的小。
例子:
比如我们设计一个手机的接口时,就要手机哪些行为是必须的,要让这个接口尽量的小,或者通俗点讲,就是里面的行为应该都是这样一种行为,就是说只要是手机,你就必须可以做到的。
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接口,实现打电话、发短信以及玩愤怒的小鸟的功能,两者都不会有多余的要实现的方法。
还有一个例子:
比如电商项目的订单系统,看看别人是怎么设计的
订单这个类,有两个地方会使用到,一般是前台门户,用户查询订单, 后台管理, 管理员处理订单, 增删改查, 其实门户也可以增删改查订单的,有些系统,这里就假设门户只能查询订单, 有其他也是同样道理
interface IOrderForPortal{ 门户接口
String getOrder();
}
interface IOrderForAdmin{ 后台接口
String deleteOrder();
String updateOrder();
String insertOrder();
String getOrder();
}
/*
// 如果门户和后台查询订单方法相同实现, 也可以这样
interface IOrderForPortal{
String getOrder();
}
interface IOrderForAdmin extendsIOrderForPortal{
String updateOrder();
String deleteOrder();
}
*/
class Order implements IOrderForPortal,IOrderForAdmin{
private Order(){
//--什么都不干,就是为了不让直接 new,防止客户端直接New,然后访问它不需要的方法.
}
//返回给Portal
public static IOrderForPortal getOrderForPortal(){
return (IOrderForPortal)new Order();
}
//返回给Admin
public static IOrderForAdmin getOrderForAdmin(){
return (IOrderForAdmin)new Order();
}
//--下面是接口方法的实现.只是返回了一个String用于演示
public String getOrder(){
return "implemented getOrder";
}
public String insertOrder(){
return "implementedinsertOrder";
}
public String updateOrder(){
return "implementedupdateOrder";
}
public String deleteOrder(){
return "implementeddeleteOrder";
}
}
public class TestCreateLimit{
public static void main(String[]
args){
IOrderForPortal orderForPortal =Order.getOrderForPortal();
IOrderForAdmin orderForAdmin = Order.getOrderForAdmin();
System.out.println("Portal门户调用方法:"+orderForPortal.getOrder());
System.out.println("Admin管理后台调用方法:"+orderForAdmin.getOrder()+";"+orderForAdmin.insertOrder()+";"+orderForAdmin.updateOrder()+";"+orderForAdmin.deleteOrder());
}
}
这样就能很好的满足接口隔离原则了,调用者只能访问它自己的方法,不能访问到不应该访问的方法.
很多人会觉的接口隔离原则跟之前的单一职责原则很相似,其实不然。其一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构建。
采用接口隔离原则对接口进行约束时,要注意以下几点:
- 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
- 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
- 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则。
迪米特法则,又称最少知道原则
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立
这个原则的制定,是因为如果一个类知道或者说是依赖于另外一个类太多细节,这样会导致耦合度过高,应该将细节全部高内聚于类的内部,其他的类只需要知道这个类主要提供的功能即可。
所谓高内聚就是尽可能将一个类的细节全部写在这个类的内部,不要漏出来给其他类知道,否则其他类就很容易会依赖于这些细节,这样类之间的耦合度就会急速上升,这样做的后果往往是一个类随便改点东西,依赖于它的类全部都要改。
开闭原则
就是说我任何的改变都不需要修改原有的代码,而只需要加入一些新的实现,就可以达到我的目的,这是系统设计的理想境界,但是没有任何一个系统可以做到这一点,哪怕我一直最欣赏的spring框架也做不到,虽说它的扩展性已经强到变态。
这个原则更像是前五个原则的总纲,前五个原则就是围着它转的,只要我们尽量的遵守前五个原则,那么设计出来的系统应该就比较符合开闭原则了,相反,如果你违背了太多,那么你的系统或许也不太遵循开闭原则。
参考:
感谢这些作者为我们这些后来者提供了大量的阅读资料, 还有一些百度查到的零碎的资料
http://www.cnblogs.com/zuoxiaolong/p/pattern1.html
http://www.jianshu.com/p/60aea0cf6bda