提高代码质量之六大设计原则

单一职责原则(六大规则中的小萝莉,人见人爱):描述的意思是每个类都只负责单一的功能,切不可太多,并且一个类应当尽量的把一个功能做到极致。

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出来一个div方法,把最后的加号改成减号。好吧,那我要除法呢?乘法呢?取模呢?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()));
    }
    
}

我们将一个类拆成了两个类,这样以后我们如果有减法,乘法等等,就不用出现那么多重复代码了。

以上是我临时杜撰的例子,虽然很简单,并且没有什么现实意义,但是我觉得足够表达单一职责的意思,并且也足够说明它的重要性。单一职责原则是我觉得六大原则当中最应该遵守的原则,因为我在实践过程中发现,当你在项目的开发过程中遵循它,几乎完全不会给你的系统造成任何多余的复杂性,反而会令你的程序看起来井然有序。

里氏替换原则(六大原则中最文静的姑娘,但却不太招人喜欢):这个原则表达的意思是一个子类应该可以替换掉父类并且可以正常工作。

那么翻译成比较容易理解的话,就是说,子类一般不该重写父类的方法,因为父类的方法一般都是对外公布的接口,是具有不可变性的,你不该将一些不该变化的东西给修改掉。

上述只是通常意义上的说法,很多情况下,我们不必太理解里氏替换这个文静的姑娘,比如模板方法模式,缺省适配器,装饰器模式等一些设计模式,就完全不搭理这个文静的姑娘。

不过就算如此,如果你真的遇见了不得不重写父类方法的场景,那么请你考虑,你是否真的要把这个类作为子类出现在这里,或者说这样做所换来的是否能弥补你失去的东西,比如子类无法代替父类工作,那么就意味着如果你的父类可以在某一个场景里工作的很正常,那么你的子类当然也应该可以,否则就会出现下述场景。

比如我们有某一个类,其中有一个方法,调用了某一个父类的方法。

//某一个类
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();
    }
    
}

这个异常是运行时才会产生的,也就是说,我的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.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;
    }
}

转自:博客园左潇龙的技术博客--http://www.cnblogs.com/zuoxiaolong

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容

  • 目录: 设计模式六大原则(1):单一职责原则 设计模式六大原则(2):里氏替换原则 设计模式六大原则(3):依赖倒...
    加油小杜阅读 716评论 0 1
  • 一、基础知识:1、JVM、JRE和JDK的区别:JVM(Java Virtual Machine):java虚拟机...
    杀小贼阅读 2,359评论 0 4
  • 设计模式六大原则 设计模式六大原则(1):单一职责原则 定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类...
    viva158阅读 763评论 0 1
  • 一、六大设计原则 原则一:单一职责原则 定义 Software entities (classes, module...
    鱼弎思阅读 963评论 1 4
  • 昨晚在凤凰的清吧里坐了一个小时,是一段难得的休闲时光。其实凤凰的许多清吧是和酒吧一样的,都可以蹦迪!但是昨天晚上去...
    易辛铃儿阅读 86评论 0 1