概述

假如咱们能站在巨人的膀子上眺望远方,为啥还要自己去艰难的翻越一座又一座的高山呢。面向目标规划准则便是长辈们在实战中给咱们总结下来的宝贵经历和财富。咱们能够基于这些经历,编写出很优秀的面向目标程序。在我将近5年的编程工作中,我发现,面向目标规划准则每个人如同都知道一点,每一个人如同都会点,可是写出来的代码依然是我行我素。野路子频出。许多编程不按照准则来,一股脑的瞎干,写出的程序给到后面人维护的时分,迎来的是一堆埋怨和骂声,以及无休止的一次又一次的重构,重构后发现仍是和上一次代码相同的货色。真的是让人感叹到:“攻城狮不暇自哀,而后人哀之;后人哀之而不鉴之,亦使后人而复哀后人也”。所以面向目标的规划准则咱们需求熟悉而且尽量恪守。多看长辈们留下的经历,一开端赶需求肯定来不及运用这些准则,那是由于咱们不熟悉这些准则,当咱们用熟悉了后,咱们会发现每一次完成需求的时分咱们考虑得更加全面了,所以一起学起来吧。

1.开闭准则

开闭准则(Open Closed Principle ) 是由伯兰特.梅耶在1988年发行的《面向目标软件结构》中提出,开闭准则着重,软件实体应该要对修正封闭,对扩展敞开。

软件实体是指:项目中划分出来的模块,类或许接口以及办法都称为软件实体
开闭准则的意图是为了下降维护带来的新危险。咱们能够想象这样一个场景,假定咱们要封装一个投屏SDK,产品的要求是运用X公司的投屏SDK供给的功用,咱们封装一个来供项目运用。这时假如不运用开闭准则,没有经历的程序员或许会如下去做:

public class CastSDKManager {
    private static final CastSDKManager sInstance 
            = new CastSDKManager();
    public static CastSDKManager getsInstance() {
        return sInstance;
    }
    public void startScan(){
        // do something……
        XCastSDK.startScan();
    }
    public void stopScan(){
        // do something……
        XCastSDK.startScan();
    }
    public void startCast(){
        // do something……
        XCastSDK.startCast();
    }
    public void stopCast(){
        // do something……
        XCastSDK.stopCast();
    }
}

这样做其实功用啥的都能完成,可是会带来一个问题,那便是扩展的问题,假定产品有一天说,咱们发现XCastSDK只支撑投屏到TV,不支撑投到电脑,咱们要增加一个Y公司的YCastSDK,支撑投屏到电脑。那么这时分咱们就需求去修正源码了。或许是再写一个CastManager完成投屏到电脑,然后在事务的代码中再接一次Y公司的投屏SDK,这样事务接入方不一定会买账,人家只想投屏,你却让人家去接两套投屏接口。已然事务方不同意,那就只要咱们封装SDK的小伙伴去修正SDK了,由于之前的规划没有考虑到开闭准则,所以咱们此时只能再去修正CastSDKManager 中的代码,参加Y公司的投屏SDK的接入。

那如何规划一个契合开闭准则的投屏SDK呢?答案便是运用承继和接口。大致伪代码如下所示:
咱们能够首要界说一个接口规则投屏的根本功用,比方开端扫描设备,中止扫描设备,开端投屏,中止投屏,为了演示,剩余的接口就不列出来了。如有扩展的功用,咱们能够界说扩展的接口,界说如下:

根本功用接口:

public interface ICastSDK {
    void startScan();
    void stopScan();
    void startCast();
    void stopCast();
}

扩展投屏SDK功用接口:
假定咱们Y公司的投屏要求能够设置比特率和判断当时是否正在投屏,咱们能够运用扩展的投屏接口去完成。

public interface IExtCastSDK extends ICastSDK{
    void setBitRate(int bitRate);
    boolean isCasting();
}

然后,咱们能够分别创立对应公司的SDK去完成投屏的接口就行了,比方,X公司就仅仅完成了根本的投屏接口,代码如下:

public class XCastSDK implements ICastSDK{
    @Override
    public void startScan() {
    }
    @Override
    public void stopScan() {
    }
    @Override
    public void startCast() {
    }
    @Override
    public void stopCast() {
    }
}

Y公司需求完成设置比特率的办法,所以需求完成扩展接口,代码如下:

public class YCastSDK implements ICastSDK,IExtCastFunction{
    @Override
    public void startScan() {
    }
    @Override
    public void stopScan() {
    }
    @Override
    public void startCast() {
    }
    @Override
    public void stopCast() {
    }
    @Override
    public void setBitRate(int bitRate) {
    }
    @Override
    public boolean isCasting() {
        return false;
    }
}

运用的时分咱们能够写一个管理类,持有ICastSDK,然后利用多态去依据用户的想要的
投屏类型调用对应的SDK内的投屏办法:伪代码如下所示:

public class CastSDKManager {
    private ICastSDK mICastSDK;
    private static final CastSDKManager sInstance
            = new CastSDKManager();
    public static CastSDKManager getsInstance() {
        return sInstance;
    }
    // 这儿仅仅做演示需求这么做,其实这儿能够做成工厂办法去依据类型加载对应的类,
    //这儿首要介绍投屏的规划开闭准则。
    public void setCastSDK(String type){
        if("XCastSDK".equals(type)){
            mICastSDK = new XCastSDK();
        }else if("YCastSDK".equals(type)){
            mICastSDK = new YCastSDK();
        }
    }
    public void startScan(){
        // do something……
        if (mICastSDK != null) {
            mICastSDK.startScan();
        }
    }
    public void stopScan(){
        // do something……
        if (mICastSDK != null) {
            mICastSDK.stopScan();
        }
    }
    public void startCast(){
        // do something……
        if(mICastSDK instanceof YCastSDK){
            ((YCastSDK) mICastSDK).setBitRate(100000);
            ((YCastSDK) mICastSDK).isCasting();
        }
        if (mICastSDK != null) {
            mICastSDK.startCast();
        }
    }
    public void stopCast(){
        // do something……
        if (mICastSDK != null) {
            mICastSDK.stopCast();
        }
    }
}

这样,咱们的SDK大体的架构就规划完成了,今后产品想要扩展新的投屏SDK,只需求承继ICastSDK 接口,然后新加对应的类去完成就能够了,而不用去修正一个CastSDK类,这便是开闭准则着重的对修正封闭,对扩展敞开

2.里氏替换准则

里氏替换准则(Liskov Substitution principle)是对子类型的特别界说。它由芭芭拉利斯科夫(Barbara Liskov)在1987年在一次会议上名为“数据的笼统与层次”的演说中首要提出,里氏替换准则认为:承继必需求确保超类所具有的性质,在子类中仍然树立。简略来说便是,子类承继父类时,能够添加新的办法,不能改动父类原有的功用,也便是说尽量不要去重写父类的办法。

就比方下面的比方,父亲喜爱吃鱼,儿子喜爱吃虾,还喜爱打球。
父亲类界说:

public class Father {
    public void favorite(){
        System.out.println("喜好是喜爱吃鱼");
    }
}

儿子类界说承继父亲类:

public class Son extends Father{
    // 这儿的办法重写就很不好,爸爸只喜爱吃鱼
    // 一重写这个办法,爸爸的喜好就变了,会导致后面用到爸爸的喜好时,或许会犯错
    @Override
    public void favorite() {
        System.out.println("喜好是喜爱吃虾");
    }
    public void favoriteSport(){
        System.out.println("儿子喜爱打球");
    }
}

在上面咱们就不应该重写父类的favorite办法,假如咱们要表明儿子喜爱吃虾的喜好,能够像界说儿子喜爱打球相同去界说一个儿子的喜好,这样就不会和父亲的喜好弄混。

3.依靠倒置准则

依靠倒置准则(Dependence Inversion Principle)的意思是程序要依靠于笼统接口,不要依靠于详细完成。依靠倒置准则是Robert C.Martin于1996年在C++Report上宣布的文章中提出的。也称依靠回转。了解起来便是,面向接口编程,笼统不应该依靠于细节,细节应该要依靠笼统,由于在软件规划中,细节具有多变性,而笼统则相对安稳,所以用笼统搭建起来的架构比用细节搭建起来的架构安稳得多

此处的笼统指的是接口和笼统类,而细节指的是详细的完成类
依靠倒置准则的作用是下降耦合,进步系统的安稳性,削减并行开发引起的危险,进步代码的可读性和可维护性。这儿的比方也能够参阅开闭准则一节,咱们将投屏的接口全都笼统出来,一切要接入的投屏SDK都需求完成这些统一的接口,这样,咱们的代码就更易读,安稳性也高,由于削减了犯错的概率。而且各个公司的投屏SDK之间也几乎没有耦合。

若要做到依靠倒置其实也不难,咱们只需求在项目中写代码时,每个类尽量供给接口或笼统类。变量的类型声明尽量时接口或许笼统类,任何的类尽量不要从详细的类派生,最终是运用承继时尽量遵循里氏替换准则

4.单一责任准则

单一责任准则(Single Responsibility Principle,SRP)是由罗伯特C马丁(Robert C. Martin)于《敏捷软件开发:准则、模式与实践》一书中给出的。单一责任准则规则一个类应该有且仅有一个引起它变化的原因,不然类应该被拆分。简略了解便是一个类尽量只干和这个类相关的事,不要既干自己的事,又干其他类的事。

单一责任的意图是为了进步类的内聚性,下降耦合性,由于比较简略,就不举比方说明晰

5.接口阻隔准则

接口阻隔准则(Interface Segregation Principle)也是由罗伯特C马丁提出的。 接口阻隔准则尽量将臃肿庞大的接口拆分红更小更详细的接口,让接口中只包括调用类感兴趣的办法。

留意:咱们要为各个类树立它们所需求的专用接口,而不要去企图树立一个很大很杂的接口供一切类调用

比方咱们现在有一个动物的接口规划如下:

public interface IAnimal {
    void fly();
    void run();
    void swim();
    // 吃草
    void eatGrass();
    // 吃肉
    void eatMeat();
    // 叫声
    void call();
}

很显然这个接口包括许多的动物的动作,比方吃草,吃肉,叫声,飞,游水,跑,等,这些行为都放到一个接口中很显然是不合适的,由于假如这时咱们想要创立一条鱼目标,咱们或许只用到的接口只要游水,吃草,吃肉这几个接口,可是鱼的这个类完成了IAnimal接口后,就需求完成接口中的一切办法:
如下所示:

public class Fish implements IAnimal{
    @Override
    public void fly() {
    }
    @Override
    public void run() {
    }
    @Override
    public void swim() {
    }
    @Override
    public void eatGrass() {
    }
    @Override
    public void eatMeat() {
    }
    @Override
    public void call() {
    }
}

实际上咱们并不需求悉数完成这些接口,咱们只需求去做笼统就行了,比方将动物分类,分红会飞的,吃草的,吃肉的,会游水的,跑得快的,叫声是都有的所以能够抽取拆分一下,下面是拆分后的接口:
首要飞和游水是一部分动物的特性,咱们能够将飞和游的这两个个功用分别单独笼统成一个接口,如下所示:

飞的动物接口

public interface IFlyAnimal {
    void fly();
}

游水的动物接口

public interface ISwimAnimal {
    void swim();
}

然后便是一切的动物都具有跑,和发出叫声的特点,所以咱们把这几个动作分红一个接口,如下所示:

public interface IAnimalBehavior {
    void run();
    void call();
}

最终是动物有吃草的,吃肉的,咱们就笼统成一个同一的接口就行,如下所示:

public interface IAnimalTaste {
    void eatTaste();
}

这样划分好了后,咱们就能够对接口进行组合运用了,比方咱们想要创立一条鱼的类,咱们都知道鱼能够游水,能够吃,放陆地上它会扑腾,就当它也会走吧,然后发出声音,会吃东西,不会飞,所以咱们就只完成ISwimAnimal IAnimalTaste IAnimalBehavior 三个接口就行。如下所示:

public class Fish implements ISwimAnimal,IAnimalTaste,IAnimalBehavior{
    @Override
    public void run() {
    }
    @Override
    public void call() {
    }
    @Override
    public void eatTaste() {
    }
    @Override
    public void swim() {
    }
}

同理,假定咱们这时分要创立一个天鹅的类,天鹅能飞,能走,能游水,能吃,所以咱们就把ISwimAnimal IAnimalTaste IFlyAnimal IAnimalBehavior都完成了就能够了。 如下所示:

public class Cygnus implements IAnimalBehavior,IFlyAnimal,ISwimAnimal,IAnimalTaste{
    @Override
    public void run() {
    }
    @Override
    public void call() {
    }
    @Override
    public void eatTaste() {
    }
    @Override
    public void fly() {
    }
    @Override
    public void swim() {
    }
}

到这儿咱们能够对比下接口阻隔准则和单一责任准则的区别,首要两者都是为了进步类的内聚性,下降耦合性,他们的区别是,单一责任准则注重的是责任,而接口阻隔准则注重的是对接口依靠的阻隔。单一责任准则首要是束缚类,它针对的是程序中的完成和细节,接口阻隔准则首要束缚的是接口,首要针对笼统和程序全体框架的架构

6.迪米特规律(最少知道准则)

迪米特规律又叫最少知道准则,它产生于美国东北大学一个叫迪米特的研讨项目,由伊恩.荷兰提出,迪米特规律的界说是:只与你的朋友攀谈,不跟陌生人说话其含义是假如两个实体间没有直接通讯,那么就不应该产生直接的相互调用。迪米特规律的意图也是下降类之间的耦合度,进步模块之间的相对独立性。

迪米特规律中的朋友是指当时目标的本身,当时目标的成员目标,当时目标所创立的目标,当时目标的办法参数等,这些目标同当时目标存在相关,聚合,或许组合联系能够直接访问这些目标的办法。也便是说只要能持有上述的这些朋友,就能调用这些朋友的办法。

7.合成复用准则

合成复用准则(Composite Reuse Principle, CRP)又叫组合聚合准则,它要求在软件复用时,要尽量运用组合或许聚合等相关完成

简略说便是复用代码时不要再老是想到用承继了,承继很香,但在不同的场景中也有局限性和缺乏,所以复用代码时尽量的运用组合和聚合完成代码复用,至于啥叫聚合,啥叫组合?送你八个字,百度一下,你就知道,这儿就不多赘述了
留意:若要运用承继联系,一定要严格遵循里氏替换准则,合成复用准则和里氏替换准则氏相得益彰的,两者都是开闭准则的详细完成规范