设计模式铺铺路(面向对象设计的原则一二)

mp.weixin.qq.com/s/OdwIswrJF…
java

咱们的知识星球立刻就要开始更新设计模式了,在更新设计模式以前,咱们是否是须要作一些准备呢?不然设计模式中一些遵循的原则你们会一头雾水,因此我今天来给你们说一些面向对象的七种原则,有人说是6种有人说是7种,我我的认为是7种,我就按照7种来讲,今天我就介绍2种,下一篇文章将会继续介绍剩下的五种原则,这些原则也会在设计模式中出现,各位技术人,欢迎你们的踊跃参加呦。git

前言

在面向对象的软件设计中,只有尽可能下降各个模块之间的耦合度,才能提升代码的复用率,系统的可维护性、可扩展性才能提升。面向对象的软件设计中,有23种经典的设计模式,是一套前人代码设计经验的总结,若是把设计模式比做武功招式,那么设计原则就比如是内功心法。经常使用的设计原则有七个,下文将具体介绍。编程

设计原则简介

  • 单一职责原则:专一下降类的复杂度,实现类要职责单一;设计模式

  • 开放关闭原则:全部面向对象原则的核心,设计要对扩展开发,对修改关闭;api

  • 里式替换原则:实现开放关闭原则的重要方式之一,设计不要破坏继承关系;bash

  • 依赖倒置原则:系统抽象化的具体实现,要求面向接口编程,是面向对象设计的主要实现机制之一;ide

  • 接口隔离原则:要求接口的方法尽可能少,接口尽可能细化;测试

  • 迪米特法则:下降系统的耦合度,使一个模块的修改尽可能少的影响其余模块,扩展会相对容易;ui

  • 组合复用原则:在软件设计中,尽可能使用组合/聚合而不是继承达到代码复用的目的。this

这些设计原则并不说咱们必定要遵循他们来进行设计,而是根据咱们的实际状况去怎么去选择使用他们,来让咱们的程序作的更加的完善。

单一职责原则

解释

就一个类而言,应该仅有一个引发它变化的缘由,通俗的说,就是一个类只负责一项职责。

此原则的核心就是解耦和加强内聚性

那么为何要使用单一职责原则:

若是一个类承担的职责过多,就等于把这些职责耦合在一块儿,一个职责的变化可能会削弱或者抑制这个类完成其余职责的能力。这种耦合会致使脆弱的设计。

这也是他的优势,咱们总结一下

优势

(1)下降类的复杂度;

(2)提升类的可读性,提升系统的可维护性;

(3)下降变动引发的风险(下降对其余功能的影响)。

咱们来举一些简单的例子来讲明一下这个单一职责原则

实例

//咱们用动物生活来作测试    class Animal{        public void breathe(String animal){            System.out.println(animal+"生活在陆地上");        }    }    public class Client{        public static void main(String[] args){            Animal animal = new Animal();            animal.breathe("羊");            animal.breathe("牛");            animal.breathe("猪");        }    }    运行结果 羊生活在陆地上 牛生活在陆地上 猪生活在陆地上复制代码

可是问题来了,动物并非都生活在陆地上的,鱼就是生活在水中的,修改时若是遵循单一职责原则,须要将 Animal 类细分为陆生动物类 Terrestrial,水生动物 Aquatic,代码以下:

class Terrestrial{    public void breathe(String animal){        System.out.println(animal+"生活在陆地上");    }}class Aquatic{    public void breathe(String animal){        System.out.println(animal+"生活在水里");    }}    public class Client{        public static void main(String[] args){            Terrestrial terrestrial = new Terrestrial();            terrestrial.breathe("羊");            terrestrial.breathe("牛");            terrestrial.breathe("猪");                Aquatic aquatic = new Aquatic();            aquatic.breathe("鱼");        }运行结果:羊生活在陆地上牛生活在陆地上猪生活在陆地上鱼生活在水里复制代码

可是问题来了若是这样修改花销是很大的,除了将原来的类分解以外,还须要修改客户端。而直接修改类 Animal 来达成目的虽然违背了单一职责原则,但花销却小的多,代码以下:

class Animal{    public void breathe(String animal){        if("鱼".equals(animal)){            System.out.println(animal+"生活在水中");        }else{            System.out.println(animal+"生活在陆地上");        }    }}public class Client{    public static void main(String[] args){        Animal animal = new Animal();        animal.breathe("羊");        animal.breathe("牛");        animal.breathe("猪");        animal.breathe("鱼");    }复制代码

能够看到,这种修改方式要简单的多。可是却存在着隐患:有一天须要将鱼分为生活在淡水中的鱼和生活在海水的鱼,则又须要修改 Animal 类的 breathe 方法,而对原有代码的修改会对调用“猪”“牛”“羊”等相关功能带来风险,也许某一天你会发现程序运行的结果变为“牛生活在水中”了。

这种修改方式直接在代码级别上违背了单一职责原则,虽然修改起来最简单,但隐患倒是最大的。还有一种修改方式:

class Animal{    public void breathe(String animal){        System.out.println(animal+"生活在陆地上");    }    public void breathe2(String animal){        System.out.println(animal+"生活在水中");    }}public class Client{    public static void main(String[] args){        Animal animal = new Animal();        animal.breathe("牛");        animal.breathe("羊");        animal.breathe("猪");        animal.breathe2("鱼");    }}复制代码

能够看到,这种修改方式没有改动原来的方法,而是在类中新加了一个方法,这样虽然也违背了单一职责原则,但在方法级别上倒是符合单一职责原则的,由于它并无动原来方法的代码。

这三种方式各有优缺点,那么在实际编程中,采用哪一中呢?其实这真的比较难说,须要根据实际状况来肯定。个人原则是:只有逻辑足够简单,才能够在代码级别上违反单一职责原则;只有类中方法数量足够少,才能够在方法级别上违反单一职责原则;

例如本文所举的这个例子,它太简单了,它只有一个方法,因此,不管是在代码级别上违反单一职责原则,仍是在方法级别上违反,都不会形成太大的影响。实际应用中的类都要复杂的多,一旦发生职责扩散而须要修改类时,除非这个类自己很是简单,不然仍是遵循单一职责原则的好。

以上就是我所说的单一职责原则了,不少书中介绍的说它并不属于面向对象设计原则中的一种,可是我认为它是,因此我就把他解释出来了。

开放关闭原则

开放关闭原则又称为开放封闭原则。

定义

一个软件实体应该对扩展开放,对修改关闭,这个原则也是说,在设计一个模块的时候,应当使这个模块能够在不被修改的前提下被扩展,换句话说,应当能够在没必要修改源码的状况下改变这个模块的行为。

这句话其实刚开始看上去是有些矛盾的,接下来在我后边文章解释里面,我会把他解释清楚一点。

咱们先用个比较好玩的例子来讲一下。

西游记你们都看过,玉帝招安孙悟空的时候的桥段,你们还有没有印象?

当年大闹天宫的时候美猴王对玉皇大帝作了个挑战,美猴王说:皇帝轮流作,明年到我家,只叫他搬出去,将天宫让给我,对于这个挑战,太白金星给玉皇大帝提了个意见,咱们把它招上来,给他个小官作,他不就不闹事了?

换一句话说,不用兴师动众的,不破坏天庭的规矩这就是闭,可是收他为官,即是开,招安的方法即是符合开闭原则的,给他个‘弼马温’的官,即可以让这个系统正常不受威胁,是吧,何乐而不为?我给你们画个图。

招安的方法的关键就是不容许更改现有的天庭秩序,可是容许将妖猴归入现有的秩序中,从而扩展了这个秩序,用面向对象的语言来讲:不容许更改的是系统的抽象层,而容许扩展的是系统的实现层。

咱们写一些简单的代码来进行一个完整的展现来进行一下理解。

实例

//书店卖书    interface Books{        //书籍名称        public Sting getName();        //书籍价格        public int getPrice();        //书籍做者        public String getAuthor();    }    public class NovelBook implements Books {    private String name;    private int price;    private String author;    public NovelBook(String name, int price, String author) {        this.name = name;        this.price = price;        this.author = author;    }    @Override    public String getName() {        return name;    }    @Override    public int getPrice() {        return price;    }    @Override    public String getAuthor() {        return author;    }}复制代码

以上的代码是数据的实现类和书籍的类别;

下面咱们将要开始对书籍进行一个售卖活动:

public class BookStore {    private final static ArrayList<Books> sBookList = new ArrayList<Books>();    static {        sBookList.add(new NovelBook("天龙八部", 4400, "金庸"));        sBookList.add(new NovelBook("射雕英雄传", 7600, "金庸"));        sBookList.add(new NovelBook("钢铁是怎么炼成的", 7500, "保尔·柯查金"));        sBookList.add(new NovelBook("红楼梦", 3300, "曹雪芹"));    }    public static void main(String[] args) throws IOException {        NumberFormat format = NumberFormat.getCurrencyInstance();        format.setMaximumFractionDigits(2);       System.out.println("----书店卖出去的书籍记录以下---");        for (Books book : sBookList) {            System.out.println("书籍名称:" + book.getName()                    + "\t书籍做者:" + book.getAuthor()                    + "\t书籍价格:" + format.format(book.getPrice() / 100.00) + "元");        }    }}复制代码

运行结果以下:

D:\develop\JDK8\jdk1.8.0_181\bin\java.exe "-javaagent:D:\develop\IDEA\IntelliJ IDEA 2018.2.4\lib\idea_rt.jar=62787:D:\develop\IDEA\IntelliJ IDEA 2018.2.4\bin" -Dfile.encoding=UTF-8 -classpath D:\develop\JDK8\jdk1.8.0_181\jre\lib\charsets.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\deploy.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\dnsns.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\jaccess.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\localedata.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\nashorn.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\sunec.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\zipfs.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\javaws.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\jce.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\jfr.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\jfxswt.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\jsse.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\management-agent.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\plugin.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\resources.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\rt.jar;D:\develop\IDEA_Workspace\CloudCode\out\production\PattemMoudle com.yldyyn.test.BookStore----书店卖出去的书籍记录以下---书籍名称:天龙八部  书籍做者:金庸  书籍价格:¥44.00元书籍名称:射雕英雄传  书籍做者:金庸  书籍价格:¥76.00元书籍名称:钢铁是怎么炼成的  书籍做者:保尔·柯查金  书籍价格:¥75.00元书籍名称:红楼梦  书籍做者:曹雪芹  书籍价格:¥33.00元Process finished with exit code 0复制代码

可是若是说如今书店卖书的时候要求打折出售,40以上的咱们要7折售卖,40如下的咱们打8折。

方法有三种,第一个办法:修改接口。在 Books 上新增长一个方法 getOnSalePrice(),专门进行打折,全部实现类实现这个方法。可是这样修改的后果就是实现类 NovelBook 要修改, BookStore 中的 main 方法也修改,同时 Books 做为接口应该是稳定且可靠的,不该该常常发生变化,不然接口作为契约的做用就失去了效能,其余不想打折的书籍也会由于实现了书籍的接口必须打折,所以该方案被否认。

第二个办法:修改实现类。修改 NovelBook 类中的方法,直接在 getPrice() 中实现打折处理,这个应该是你们在项目中常用的就是这样办法,经过 class 文件替换的方式能够完成部分业务(或是缺陷修复)变化,可是该方法仍是有缺陷的,例如采购书籍人员也是要看价格的,因为该方法已经实现了打折处理价格,所以采购人员看到的也是打折后的价格,这就产生了信息的蒙蔽效果,致使信息不对称而出现决策失误的状况。该方案也不是一个最优的方案。

第三个办法,经过扩展实现变化增长一个子类 OffNovelBook,覆写 getPrice 方法,高层次的模块(也就是 static 静态模块区)经过 OffNovelBook 类产生新的对象,完成对业务变化开发任务。好办法,风险也小。

public class OnSaleBook extends NovelBook {    public OnSaleBook(String name, int price, String author) {        super(name, price, author);    }    @Override    public String getName() {        return super.getName();    }    @Override    public int getPrice() {        int OnsalePrice = super.getPrice();        int salePrce = 0;        if (OnsalePrice >4000){            salePrce = OnsalePrice * 70/100;        }else{            salePrce = OnsalePrice * 80/100;        }        return  salePrce;    }    @Override    public String getAuthor() {        return super.getAuthor();    }}复制代码

上面的代码是扩展出来的一个类,而不是在原来的类中进行的修改。

public class BookStore {    private final static ArrayList<Books> sBookList = new ArrayList<Books>();    static {        sBookList.add(new OnSaleBook("天龙八部", 4400, "金庸"));        sBookList.add(new OnSaleBook("射雕英雄传", 7600, "金庸"));        sBookList.add(new OnSaleBook("钢铁是怎么炼成的", 7500, "保尔·柯查金"));        sBookList.add(new OnSaleBook("红楼梦", 3300, "曹雪芹"));    }    public static void main(String[] args) throws IOException {        NumberFormat format = NumberFormat.getCurrencyInstance();        format.setMaximumFractionDigits(2);       System.out.println("----书店卖出去的书籍记录以下---");        for (Books book : sBookList) {            System.out.println("书籍名称:" + book.getName()                    + "\t书籍做者:" + book.getAuthor()                    + "\t书籍价格:" + format.format(book.getPrice() / 100.00) + "元");        }    }}复制代码

结果展现:

----书店卖出去的书籍记录以下---书籍名称:天龙八部  书籍做者:金庸  书籍价格:¥30.80元书籍名称:射雕英雄传  书籍做者:金庸  书籍价格:¥53.20元书籍名称:钢铁是怎么炼成的  书籍做者:保尔·柯查金  书籍价格:¥52.50元书籍名称:红楼梦  书籍做者:曹雪芹  书籍价格:¥26.40元Process finished with exit code 0复制代码

在开闭原则中,抽象化是一个关键,解决问题的关键在于抽象化,在 JAVA 语言这种面向对象的语言中,能够给系统定义出一个一劳永逸的,再也不更改的抽象化的设计,此设计容许拥有无穷无尽的实现层被实现。

在 JAVA 语言中,能够给出一个或者多个抽象的 JAVA 类或者是JAVA接口,规定全部的具体类必须提供方法特征做为系统设计的抽象层,这个抽象层会碰见全部的可能出现的扩展,所以,在任何扩展状况下都不回去改变,这就让系统的抽象层不须要修改,从而知足开闭原则的第二条,对修改进行闭合。

同时,从抽象层里面导出一个或者多个新的具体类能够改变系统的行为,这样就知足了开闭原则的第一条。

尽管不少时候咱们没法百分百的作到开闭原则,可是若是向着这个方向去努力,就可以有部分的成功,这也是能够改善系统的结构的。

我是懿,一个正在被打击还在努力前进的码农。欢迎你们关注咱们的公众号,加入咱们的知识星球,咱们在知识星球中等着你的加入。

相关文章
相关标签/搜索