开发一个系统并不是一件困难的事,但是为何维护好一个系统却是一件让人头疼不以的事?
 
在笔者的观念中这一切都源自于需求。
 
如果在软件开发完成之后,需求就不再改变,那大部分程序都不需要维护了。但是,事实呢,需求是变化的,而我们呢?也需要去拥抱这些变化。
 
因为需求的变化无常,使得某些系统的设计无法与新的需求相容。当这些破坏式的需求越来越多,一个系统也就慢慢的变的难以维护了。真的就无法避免这样的事情发生吗?当然不!
 
如果说我们在设计之初就为日后的变化留出了足够的空间,或者说,我们的设计一开始就是一个具有良好的扩展性,灵活性和可插拔性的设计,系统必然能相容变化,按照正确的维护方案维护。怎么做出一个良好的设计呢?关键就在于恰当的提高软件的可维护性和复用性。
 
好了说了以上一堆废话,现在开始正式介绍面向对象设计中的六大原则
 
一、开闭原则(OCP)
 
定义:对扩展开放,对修改关闭。
 
举个例子:
你班里有班长,学习委员,体育委员等等一系列班干部。因为在一次集体活动中你的组织能力被老师看中了,想提拔你当班干部,但是又不想随便撤销了之前的干部,怎么办?班长向老师提议,他缺一个团支书,于是你就被提拔成了团支书。
在这个例子中,老师开设了新的职位-团支书,在扩展班干部的同时也保留了原先的人马,这就是所谓的对扩展开放,对修改关闭。即:不通过去修改原有的代码逻辑,来为系统添加新的功能。
 
当然这实际上一个非常虚的定义,他只告诉你目标,却没告诉你怎么去做。所以基于此,接下来介绍的设计原则都是围绕开闭原则出发,告诉你怎么遵守OCP
 
二、里氏代换原则(LSP)
 
定义:如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有对象o1都替换成o2时,程序P的行为没有任何变化,那么类型T2是类型T1的子类。简单概括就是,任何父类出现的地方,子类一定可以出现。
 
举个例子:
有两匹马,白马和黑马,你要骑马,你既可以骑黑马,又可以骑白马。这里马是父类,白马,黑马是子类,你骑马的动作对象是马,所以只要你会骑马,不管是白马还是黑马你都可以骑。
     class Horse {
void ride(){}
} class WhiteHorse extends Horse { @Override
public void ride() {
System.out.println("骑白马");
}
} class BlackHorse extends Horse { @Override
public void ride() {
System.out.println("骑黑马");
}
} class Men {
void ride(Horse horse) {
horse.ride();
}
}
 
在看下面的代码:
     class Calculator {
void add(int x,int y) {
System.err.println(x+y);
}
} class AdvCalculator extends Calculator {
@Override
void add(int x,int y) {
System.out.println(x*y);
}
void sub(int x,int y) {
System.out.println(x-y);
}
}

子类AdvCalculator重写了父类Calculator的add方法,我们可以看出在调用add方法时,期望输出是两者和,但是由于子类复写了add方法,所以我们调用子类的add的时候获得的结果是两者的乘积,和期望不符合。

实际上这个原则隐藏了这样一层约束,当类B继承类A时,不能改变父类的规范和约束(不随意重写父类已经实现好的方法),如果子类随意修改这些契约,那么会对整个继承体系造成破坏。

*以上原则反过来是不成立,因为一般情况下子类包含了父类没有的新特性。
 
三、依赖倒转原则(DIP)
 
定义:依赖抽象,不依赖实现。
 
举个例子:
同样以黑马白马为例子,镖师骑着白马去送行,代码如下:
     class WhiteHorse {

         void ride() {
System.out.println("骑白马");
}
} class Men { void ride(WhiteHorse horse) {
horse.ride();
}
}
但是有一天,镖局没有白马了,只有黑马,代码如下:
     class BlackHorse {

         void ride() {
System.out.println("骑黑马");
}
}
但是此刻发现,镖师居然只能骑白马送信,要是想让镖师骑黑马送信,就必须修改镖师,这显然有问题,镖师也太任性了,黑马不是马么(耦合性太高)?必须要让镖师什么马都骑,不能惯着。
我们引入一个接口马:
    interface Horse{
void ride();
} class Men { void ride(Horse horse) {
horse.ride();
}
}
这样镖师类与接口马发生了依赖关系。而白马和黑马都实现了接口马。这样就符合了DIP,同时也让我们的镖师能骑所有的马送信了:
     class WhiteHorse implements Horse {

         public void ride() {
System.out.println("骑白马");
}
} class BlackHorse implements Horse { public void ride() {
System.out.println("骑黑马");
}
}
有这样一个事实,良好抽象层相对与实现层稳固多了。在笔者理解中,抽象是对具体对象抽离出本质特征的过程。它排除同属对象之前的差异,只留下普适的共同点。所以以良好抽象层为基础构建起来的系统一定比以具体实现类为基础搭建的系统稳定,DIP也正是基于此。
 
* 当然,有一些具体的类可能是相对稳定的,不会发生变化的,消费这个对象的类完全可以依赖这个具体实现,而不必在为此在创建一个抽象类
 
 
四、接口隔离原则(ISP)
 
定义:提供尽可能小的接口。
 
举个例子:
将接口理解为一个类所提供的所有方法的集合,也就是一种逻辑上才存在的概念,这样的话,对接口的划分实际上就是对类型的划分。也就是说一个接口可以理解为一个角色,看以下类图

从上面这个类图可以看出,由一个接口A给出了所有功能,同时可以看出类Client的下单属性里面包含了支付功能,而支付属性同时也包含了下单的功能,明显违反了ISP原则,这是一种糟糕的设计,那么依照ISP的做法是怎么样的呢。

从上图中可以看出,将接口划分成两个角色一个支付,一个下单。支付只针对支付,下单只针对下单。不建立单一臃肿庞大的接口,细分到角色,一个角色只负责一件事情。
 
五、合成/聚合复用原则(CAPP)
 
定义:尽量使用合成/聚合方式复用对象,而不是使用继承。
1、什么是合成/聚合
  将已有的对象纳入到新对象中,使之成为新对象的一部分。实际上就是把已有对象当作新对象的一个属性,合成和聚合的区别在于,合成。新对象负责管理被合成对象的生命周期,如人和手,人没了手还会在么?聚合则么不是,如公司和员工,公司不再了,员工当然不会随着消失
2、为什么要尽量使用合成/聚合
  a、破坏封装,将超类的实现细节暴露给子类。
  b、超类发生改变,引发子类的改变  
  c、继承关系编译器就确定了,在运行期间无法改变,不够灵活
举个例子:
人有多个身份,如父亲,儿子。如果以继承的方式去描述这些信息,这意味着人要么是父亲要么是儿子。但是一个人既可以是父亲又可以是儿子啊。这显然不合理。
这种错误的设计源于,错误的把人的等级结构和角色的等级结构混淆了。在这里要引入has-a 和 is-a的概念。is-a 是严格分类学意义上的定义,意思是一个类是另一个类的一种。而has-a表示的是某一个具有一种责任。父亲或者儿子只是一种角色,一只小狗也可以是一只大狗的儿子,难道这只小狗有着人的所有行为特征?基于此对上述设计进行改造
 

*里氏替换原则是继承复用的基础。只有两个类满足里氏替换原则的时候,才可能是“Is-A”关系。如果两个类是“Has-A”关系而非“Is-A”关系,但是设计成了继承,那么肯定违反里氏替换原则。
 
六、迪米特法则(LoD)
 
定义:一个对象应当对其他对象有尽可能的少了解
对于这个法则还有一种简单的定义,不要和陌生人说话,只和朋友通信。那么什么叫朋友呢?
  1、this
  2、方法入参
  3、对象属性
  4、对象属性如果是集合,里面所有元素也是朋友
  5、由当前对象所创建的对象
任何一个对象满足以上任何一个条件,就是对象朋友,其他都是陌生人。
 
举个例子,排长统计自己手下有多少士兵,代码:
     class Platoon {
private Squad[] squads; void gather() {
int count = 0;
for (Squad squad : squads) {
for (Man man : squad.gather()) {
count += man.gather();
}
}
System.out.println("一个排一共有"+count+"名士兵");
}
} class Squad {
private Man[] mans; Man[] gather() {
return this.mans;
}
} class Man {
int gather() {
return 1;
}
}
 从上述代码中可以看出,类Platoon的设计是有问题的,Platoon中直接与Man类发生了通信,使两个类发生了耦合,根据LoD原则,类只和自己的朋友交流,man对象明显是Platoon的陌生人。而且从现实的逻辑上看,排长只需知道自己有几个班就好了,班上成员只要交给班长就好了。如果事必躬亲,那还需要班长干什么。根据LoD原则,改进后的设计如下:
   class Platoon {
private Squad[] squads; void gather() {
int count = 0;
for (Squad squad : squads) {
count += squad.gather();
}
System.out.println("一个排一共有"+count+"名士兵");
}
} class Squad {
private Man[] mans; int gather() {
int count = 0;
for (Man man : mans) {
count += man.gather();
}
return count;
}
} class Man {
int gather() {
return 1;
}
}

其实,迪米特法则谈论的就是对象之间的信息流量、流向以及信息的影响的控制。将实现的细节隐藏起来,让模块之间只通过api交互,这实际上就是信息的隐藏。它可以使模块之间脱耦,允许模块独立开发维护等一系列好处。所以在设计类的时候最好遵循以下几点

1、优先考虑将类设置成不变类

2、尽量降低一个类的访问权限

3、尽量降低成员的访问权限

--------------------------------------------------------end--------------------------------------------------------

 
以上粗略的介绍了设计模式的六大原则,是笔者自己个人的感悟,如有谬论,欢迎指出。如有兴趣深入了解,可以参考《java与模式》已经网上其他优秀的博文,
 

作者:燃点null
出处:http://www.cnblogs.com/lightnull/

本文为燃点null原创,欢迎转载,但未经同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

 

java设计模式(一)【六大原则】的更多相关文章

  1. Java 设计模式(二)-六大原则

    Java 设计模式(二)-六大原则 单一职责原则(Single Responsibility Principle) 定义: 不要存在多余一个原因导致类变更,既一个类只负责一项职责. 问题由来: 当类A ...

  2. C#之设计模式之六大原则(转载)

    设计模式之六大原则(转载) 关于设计模式的六大设计原则的资料网上很多,但是很多地方解释地都太过于笼统化,我也找了很多资料来看,发现CSDN上有几篇关于设计模式的六大原则讲述的比较通俗易懂,因此转载过来 ...

  3. 设计模式之六大原则——接口隔离原则(ISP)

    设计模式之六大原则——接口隔离原则(ISP)  转载于:http://www.cnblogs.com/muzongyan/archive/2010/08/04/1792528.html 接口隔离原则 ...

  4. java中的设计模式及其六大原则

    设计模式分类: 一共分为3大类:创造型模式.结构型模式.行为型模式. 创造型模式:工厂方法(FactoryMethod).抽象工厂模式(AbstractFactory).建造者模式(Builder). ...

  5. JAVA面向对象-----java面向对象的六大原则

    现在编程的主流语言基本上都是面向对象的.如C#,C++,JAVA.我们在使用时,已经构造了一个个的类.但是往往由于我们在类内部或外部的设计上存在种 种问题,导致尽管是面向对象的语言,却是面向过程的逻辑 ...

  6. java设计模式之七大原则

    java设计模式 以下内容为本人的学习笔记,如需要转载,请声明原文链接   https://www.cnblogs.com/lyh1024/p/16724932.html 设计模式 1.设计模式的目的 ...

  7. .NET 设计模式的六大原则理论知识

    1. 单一职责原则(SRP)(Single Responsibility Principle)2. 里氏替换原则(LSP)(Liskov Substitution Principle)3. 依赖倒置原 ...

  8. java对象的六大原则

    对象的六大原则: 1.单一职责原则(Single Responsibility Principle  SRP) 2.开闭原则(Open Close Principle OCP) 3.里氏替换原则(Li ...

  9. 0 Java实现 一篇文章说尽设计模式之六大原则

    我们知道,设计模式很有用,学好设计模式不但能让你写出更简洁,优雅的代码,还能使得代码的结构更清晰,也更有利于扩展 当然设计模式也不是万能的,一成不变的.设计模式只是前人总结出来的一种经验,一种特定问题 ...

  10. Java设计模式学习——设计原则

    第一章 设计原则 1.开闭原则 一个软件实体,像类,模块,函数应该对扩展开放,对修改关闭 在设计的时候,要时刻考虑,让这个类尽量的好,写好了就不要去修改.如果有新的需求来,在增加一个类就完事了,原来的 ...

随机推荐

  1. 【kuangbin专题】计算几何_凸包

    1.poj1113 Wall 题目:http://poj.org/problem?id=1113 题意:用一条线把若干个点包起来,并且线距离任何一个点的距离都不小于r.求这条线的最小距离是多少? 分析 ...

  2. C#6.0语言规范(十) 类

    类是可以包含数据成员(常量和字段),函数成员(方法,属性,事件,索引器,运算符,实例构造函数,析构函数和静态构造函数)和嵌套类型的数据结构.类类型支持继承,这是一种派生类可以扩展和专门化基类的机制. ...

  3. 修改tomcat默认端口号

    修改tomcat端口号 端口修改tomcat tomcat服务器的默认端口号是8080 1 只启动一个tomcat的情况 当我们不想使用8080端口,需要修改为其他端口时,我们可以: 1, 打开tom ...

  4. DevOps - CI - Sonar

    Sonar 官方信息 https://www.sonarqube.org/ https://www.sonarqube.org/downloads/ https://docs.sonarqube.or ...

  5. qtcreator_process_stub中文输出乱码

    使用qt运行程序输出中文,全都变成了□,让人很头疼,百度了很久,找了一些解决方案都是: 用vim打开x11-common,在控制台输入 vim /etc/X11/Xresources/x11-comm ...

  6. spring cloud(服务消费者(利用ribbon实现服务消费及负载均衡)——初学二)

    Ribbon是一个基于HTTP和TCP客户端的负载均衡器,利用ribbon实现服务消费,并实现客户端的负载均衡. 一.准备工作(利用上一节的内容) 启动服务注册中心 启动computer-servic ...

  7. [Java初探外篇]__关于时间复杂度与空间复杂度

    前言 我们在前面的排序算法的学习中了解到了,排序算法的分类,效率的比较所使用到的判断标准,就包括时间复杂度和空间复杂度,当时因为这两个定义还是比较难以理解的,所以决定单独开一篇文章,记录一下学习的过程 ...

  8. 利用:before和:after伪类制作CSS3 圆形按钮 含demo

    要求 必备知识 基本了解CSS语法,初步了解CSS3语法知识. 开发环境 Adobe Dreamweaver CS6 演示地址 演示地址 预览截图(抬抬你的鼠标就可以看到演示地址哦): 制作步骤: 一 ...

  9. Visual Studio 2017 取消 break mode

    用 Visual Studio 2017 (以下简称 VS 2017) 运行程序,程序出错后,只是进入中断模式,仅显示 The application is in break mode而没有像 VS ...

  10. python get请求

    #!/usr/bin/python #-*- coding:UTF-8 -*-#coding=utf-8 import requests import time import hashlib impo ...