####一、单一职责原则(SRP)
一个类,只有一个引起它变化的原因。应该只有一个职责。每一个职责都是变化的一个轴线,如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致脆弱的设计。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。例如:MVC的分层。
####二、开闭原则(OCP)
软件中的对象(类、模块、函数)应该对于拓展是开放的,意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。
对于修改是封闭的。对模块行为进行扩展时,不必改动模块的源代码。
例子:一个文章管理系统,其中要实现两个方法,一个是获取所有文章的总数,一个是获取文章点击量。在ArticlesTools类中定义两个方法用于满足需求。
public class ArticlesTools{
public int getCount(){
//获取文章总数相关代码
........
}
public int getHitsCount(){
//获取文章点击量相关代码
.......
}
}
上述代码直接满足我们目前的需求,假设系统需求发生变化,现在需要这个系统能够获取不同分类文章的点击量及不同分类文章的总数。最基础的做法,就是直接在上面的ArticlesTools类中添加相关方法。比如:
public int getEmotionCount(){
//获取情感文章总数相关代码
......
}
public int getEmotionHitsCount(){
//获取情感文章点击量相关代码
......
}
如果文章分类只有几种的话,这样的方式或许勉强还能用,代码阅读起来也不会很困难。但如果分类有十多种、几十种的话,那ArticlesTools类就会变得十分臃肿,阅读及修改代码都变得十分困难。应该对ArticlesTools类进行扩展而不是修改。
正确做法:
public interface IArticlesTools{
public int getCount();
public int getHitsCount();
}
将原先的ArticlesTools作为一个接口,然后文章分类再去实现这个接口。
public class EmotionTools implements IArticlesTools{
........
@Override
public int getCount(){
//获取情感文章总数相关代码
......
}
@Override
public int getHitsCount(){
//获取情感文章点击量相关代码
......
}
}
每一种文章分类都单独出来去实现IArticlesTools,每个文章分类都是对IArticlesTools的一种扩展,不同分类之间也是解耦的。代码也容易维护与阅读。
####三、里氏替换原则(LSP)
里氏替换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。核心就是抽象,抽象依赖于继承。
####四、依赖倒置原则(DIP)
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
高层模块指调用类,抽象指接口或抽象类,细节指具体实现类。
例子:母亲给孩子讲故事,只要给她一本书,她就可以照着书给孩子讲故事了。
public class Book{
public String getContent(){
return "很久很久以前有一个阿拉伯的故事……";
}
}
/*
*妈妈类
*/
public class Mother{
//给一本书,开始读
public void narrate(Book book){
System.out.println("妈妈开始讲故事");
System.out.println(book.getContent());
}
}
/*
*孩子听故事
*/
public class ChildActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
Mother mother = new Mother();
mother.narrate(new Book());
}
}
运行结果就是:
妈妈开始讲故事
很久很久以前有一个阿拉伯的故事……
例子里Mother类就是高层类依赖了Book类,细节就是Book类,显然就违反了依赖倒置原则(DIP)。假设有一天孩子听腻了故事书,突然想听新闻了,这时这位母亲就慌了,她只会读故事书,不会读新闻。理论上母亲是万能的(实际上也是万能的☺)。正确做法如下:
//抽象层
public interface IReader{
public String getContent();
}
//细节层
public class Newspaper implements IReader {
//读报纸类依赖于抽象层
public String getContent(){
return "林书豪17+9助尼克斯击败老鹰……";
}
}
public class Book implements IReader{
//读故事书类依赖于抽象层
public String getContent(){
return "很久很久以前有一个阿拉伯的故事……";
}
}
//高层
public class Mother{
public void narrate(IReader reader){
System.out.println("妈妈开始讲故事");
System.out.println(reader.getContent());
}
}
这样孩子无论想听故事还是想听报纸,Mother类就都能满足。
####五、接口隔离原则(ISP)
一个类对另一个类的依赖应该建立在最小的接口上。
使用多个专门的接口比使用单一的总接口要好。
一个类对另外一个类的依赖性应当是建立在最小的接口上的。
一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有 关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。
例子:Manager类代表一个管理工人的管理者,有两种类型的工人:普通的和高效的,这两种工人都需要吃午饭。现在来了一批机器人,它们同样为公司工作,但是他们不需要吃午饭。一方面Robot类需要实现IWoker接口,因为他们要工作,另一方面,它们又不需要实现IWorker接口,因为它们不需要吃饭。
/*
*工作接口
*/
public interface IWorkable {
public void work();
}
/*
*吃饭接口
*/
public interface IFeedable{
public void eat();
}
/*
*高效工人
*/
class SuperWorker implements IWorkable, IFeedable{
public void work() {
//高效工人可以干很多活
.......
}
public void eat() {
//干得多吃得多
......
}
}
/*
*机器人
*/
class Robot implements IWorkable{
public void work() {
// 一直干活不需要吃饭
}
}
总结
如果已经设计成了胖接口,可以使用适配器模式隔离它。
####六、迪米特原则(LOD)
定义:一个对象应该对其他对象保持最少的了解。
一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,类的内部如何实现、如何复杂都与调用者或者依赖者没关系,调用者或者依赖者只需要知道他需要的方法即可,其他的我一概不关心。类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
/**
* 老师类
*/
public class Teacher {
//老师对体育委员发布命令, 清一下女生
public void commond(Monitor monitor){
List listGirls = new ArrayList() ;
//初始化女生
for(int i=0;i<20;i++){
listGirls.add(new Girl());
}
//告诉体育委员开始执行清查任务
monitor.countGirls(listGirls);
}
}
/**
* 班长
*/
public class Monitor {
//有清查女生的工作
public void countGirls(List listGirls){
System.out.println(" 女生数量是"+listGirls.size());
}
}
/**
* 女生
*/
public class Girl {
}
/**
* 调用类
*/
public class ClientActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
Teacher teacher = new Teacher();
//老师发布命令
teacher.commond(new Monitor());
}
}
运行Client的结果就是:女生数量是:20
首先来看 Teacher 有几个朋友,就一个Monitor类,这个就是朋友类, 迪米特法则要求一个类只和朋友类交流, 但是 commond 方法中我们与 Girl 类有了交流,声明了一个 List动态数组,也就是与一个陌生的类 Girl 有了交流,这样设计不好,耦合严重,修改时很容易出错。下面看看重新设计Teacher和Monitor类:
/**
* 老师类
*/
public class Teacher {
//老师对体育委员发布命令, 清一下女生
public void commond(Monitor monitor){
//告诉体育委员开始执行清查任务
monitor.countGirls();
}
}
/**
* 班长
*/
public class Monitor {
//有清查女生的工作
public void countGirls(){
List listGirls = new ArrayList() ;
//初始化女生
for(int i=0;i<20;i++){
listGirls.add(new Girl());
}
System.out.println(" 女生数量是"+listGirls.size());
}
}
/**
* 女生
*/
public class Girl {
}
/**
* 调用类
*/
public class Client {
public void main(String[] args) {
Teacher teacher = new Teacher();
//老师发布命令
teacher.commond(new Monitor());
}
}
程序做了一个简单的修改,就是把 Teacher 中的对女生类 List初始化(这个是有业务意义的,产生出 全班的所有人员)移动到了 Monitor 的 countGrils 方法中,避开了 Teacher 类对陌生类 Girl 的访问, 减少系统间的耦合,使得系统具有更低的耦合性和更好的可扩展性。
参考:《Android源码设计模式解析与实战》、百度百科