软件开发的六大设计原则

概述

刚开始进行软件开发的时候,只要会功能开发就好了,不太要考虑性能问题,项目的可读性,可维护性,但随着时间的推移,这些东西也是你面向高级工程师的必经之路,为了提升项目的可维护性和可复用性,增长系统的可扩展性和灵活性,咱们要尽可能根据后面要将的六大设计原则来进行程序开发,从而提升软件的开发效率,节约开发成本和维护成本,只有将这些原则烂熟于心,在日常的开发过程当中才能作到灵活应用java

原件开发过程当中的六大设计原则有编程

  • 单一职责原则
  • 里氏替换原则
  • 依赖倒置原则
  • 接口隔离原则
  • 迪米特法则
  • 开闭原则

下面咱们就具体看下这六大原则bash

六大设计原则(1):单一职责原则

单一职责原则 (Single Responsibility Principle - SRP)架构

There should never be more than one reason for a class to change, 致使一个类变化的缘由(事情)不能多于一个 ,通俗讲就是一个类只负责一件事情或一项职责ide

问题由来:假如一个类T负责职责P1和P2 ,假如因为p1职责发生变化修改类T时,此时可能会使本来正常的P2职责发生变化函数

解决方案:把原来的T类查分红T1和T2 ,T1负责P1 ,T2负责P2 ,这样当P1发生变化修改T1时,它自己对P2是隔离的,不会对P2形成任何影响性能

看一个例子 :一个学校的学生工做,分为生活辅导和学习辅导,假如由一个老师所有负责的话有点过重了,正确的作法是生活辅导有生活老师进行辅导,学习辅导由学习老师进行辅导学习

单一职责原则一样适用于方法,一个方法负责一项职责,若是一个方法负责的职责太多,那么它的颗粒度就会变粗,不利用重用,各个职责间的耦合度也会增大测试

备注ui

  • 在实际的开发过程当中职责拆分的颗粒度可有由具体的业务场景进行拆分,没有必要为了拆分而拆分,特别简单的功能也把颗粒度弄的特别细,只要总体符合单一职责原则就OK
  • 在实际的开发过程当中没有什么事一成不变的,随着业务的变化有可能会发生职责扩散,所谓职责扩散,就是随着业务变化P职责划分为P1和P2两个职责。这时候就违背的单一职责原则,但此时是否是要进行拆分(感受均可以),但这样作的风险在于职责扩散的不肯定性,由于咱们不会想到这个职责P,在将来可能会扩散为P1,P2,P3,P4……Pn。因此记住,在职责扩散到咱们没法控制的程度以前,马上对代码进行重构

六大设计原则(2):里氏替换原则

里氏替换原则(Liskov Substitution Principle - LSP)

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it 使用基类的指针或引用的函数,必须是在不知情的状况下,可以使用派生类的对象 ,通俗讲就是父类可以替换成子类,子类不能替换成父类 ,全部引用基类的地方必须能透明地使用其子类的对象

问题由来:有一个问题q有A的p1方法完成,如今A的子类B进行了功能扩展增长的P2方法,此时又B来处理问题q时可能会出错 解决方案:当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽可能不要重写父类A的方法,也尽可能不要重载父类A的方法

例子: 咱们须要一个功能两个数相加有A类完成 ,后来,咱们须要增长一个新的功能:完成两数相减再减1,而后再翻倍,由类B来负责

public void test(){
        int n1 = 10;
        int n2 = 2;
        A a1 = new A();
        Log.e(TAG, n1 +" - "+n2 + " = "+a1.fun1(n1,n2));
        A a2 = new B();
        Log.e(TAG, n1 +" - "+n2 + " = "+a2.fun1(n1,n2));
    }


    class A {
        //两个数相减
        public int fun1(int a ,int b){
            return a-b;
        }

    }

    class B extends A {
        @Override
        public int fun1(int a, int b) {
            return super.fun1(a, b) -1;
        }

        //实现 (a-b-1)*2
        public int fun2(int a, int b){
            return fun1(a,b)*2;
        }
    }
复制代码

此时咱们会发现本来运行正常的相减功能发生了错误,这是由于B是进行功能扩展的时候无心中重写了父类的方法致使了本来运行正常的功能出现了错误,因此在子类进行功能扩展时须要遵循里氏替换原则

里氏替换原则通俗的来说就是:子类能够扩展父类的功能,但不能改变父类原有的功能

六大设计原则(3):依赖倒置原则

依赖倒置原则 (Dependence Inversion Principle - DIP) High level modules should not depends upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions. 高层模块不该该依赖于低层模块,它们应该依赖于抽象。抽象不该该依赖于细节,细节应该依赖于抽象

问题由来:类A直接依赖类B,假如要将类A改成依赖类C,则必须经过修改类A的代码来达成。这种场景下,类A通常是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操做;假如修改类A,会给程序带来没必要要的风险。

解决方案:将类A修改成依赖接口I,类B和类C各自实现接口I,类A经过接口I间接与类B或者类C发生联系,则会大大下降修改类A的概率。

相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操做,把展示细节的任务交给他们的实现类去完成,依赖倒置原则的核心思想是面向接口编程

例子:妈妈给小朋友讲书本上的故事

public void test(){
        Book book = new Book("猫和老鼠的故事.......");
        Mother mother = new Mother();
        mother.readStory(book);
    }


    class Mother {
        public void readStory(Book book){
            Log.e(TAG,"开始讲故事了");
            Log.e(TAG,book.getContent());
        }
    }

    class Book {
        private String content;

        public Book(String content){
            this.content = content;
        }

        public String getContent(){
            return "读物内容:"+content;
        }
    }
复制代码

如上所示已经完成了妈妈讲故事,可是若是哪一天妈妈想给小朋友读报纸,你会发现妈妈干不了,须要需改上面的内容,添加读报纸的功能,这是就很是很差,假如日后还要添加读手机内容的功能,岂不是要一直进行代码修改,此时就用到了依赖倒置原则(面向接口编程),咱们设计也给IReader接口,让书,报纸....都继承这个接口

public void test(){
        IReader book = new Book("猫和老鼠的故事.......");
        IReader newPaper = new NewPaper("中美贸易战有了最近进展.......");
        Mother mother = new Mother();
        mother.readStory(book);
        mother.readStory(newPaper);
    }


    class Mother {
        public void readStory(IReader reader){
            Log.e(TAG,"开始讲故事了");
            Log.e(TAG,reader.getContent());
        }
    }

    interface IReader{
        abstract String getContent();
    }

    class NewPaper implements IReader{
        public String content;
        public NewPaper(String c){
            this.content = c;
        }

        @Override
        public String getContent() {
            return "报纸内容:"+content;
        }
    }

    class Book implements IReader{
        private String content;

        public Book(String content){
            this.content = content;
        }

        @Override
        public String getContent() {
            return "书本内容:"+content;
        }
    }
复制代码

如上所示妈妈就完成了读报纸的功能,且若是之后想读手机,读期刊,在不修改代码的状况下都能很便捷的扩展开发

六大设计原则(4):接口隔离原则

接口隔离原则 (Interface Segregation Principle - ISP)

The dependency of one class to another one should depend on the smallest possible interface.一个类对另外一个类的依赖应该创建在最小的接口上 ,通俗的说就是一个类不该该依赖它不须要的接口

如上图所示类B只须要方法4和方法5两个功能,类D只须要方法2,方法3两个功能,但因为接口颗粒度比较多,致使类B不能不现实方法1,方法2,方法3 三个无用的功能,类D不能不现实方法1,方法4,方法5 三个无用的功能

接口隔离原则主要指:创建单一接口,不要创建庞大臃肿的接口,尽可能细化接口,接口中的方法尽可能少。要为各个类创建专用的接口,而又创建一个接口供全部类使用

六大设计原则(5):迪米特法则 (最少知识原则)

迪米特法则 (Law of Demeter)

Only talk to you immediate friends,只与你最直接的朋友交流 ,一个对象应该对其余对象保持最少的了解

在开发过程当中最常提到的就是高内聚,低耦合,只有耦合度变低,代码的复用率才能提升,这也是迪米特法则所要求的 迪米特法则又叫最少知道原则,也就是说对本身依赖的类知道越少越好,一个类只向外暴露必要的信息和方法,迪米特法则还有一个更简单的定义:只与直接的朋友通讯 ,首先来解释一下什么是直接的朋友:每一个对象都会与其余对象有耦合关系,只要两个对象之间有耦合关系,咱们就说这两个对象之间是朋友关系。耦合的方式不少,依赖、关联、组合、聚合等。其中,咱们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出如今局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要做为局部变量的形式出如今类的内部

六大设计原则(6):开闭原则

开闭原则 (Open Closed Principle - OCP) Software entities like classes, modules and functions should be open for extension but closed for modifications.类、模块与方法,对于扩展开放对于修改封闭

开闭原则是面向对象程序设计的终极目标,它使软件实体拥有必定的适应性和灵活性的同时具有稳定性和延续性。具体来讲,其做用以下。

  1. 对软件测试的影响 软件遵照开闭原则的话,软件测试时只须要对扩展的代码进行测试就能够了,由于原有的测试代码仍然可以正常运行。
  2. 能够提升代码的可复用性 粒度越小,被复用的可能性就越大;在面向对象的程序设计中,根据原子和抽象编程能够提升代码的可复用性。
  3. 能够提升软件的可维护性 遵照开闭原则的软件,其稳定性高和延续性强,从而易于扩展和维护。

思考:java中三大特性之一的多态是否违反了里氏替换原则??

里氏替换原则的定义大体是:子类能够扩展父类的功能,但不能改变父类原有的功能,就是进行不要重写父类的方法。 多态的定义是:在父类中定义的属性和方法被子类继承以后,能够具备不一样的数据类型或表现出不一样的行为,这使得同一个属性或方法在父类及其各个子类中具备不一样的含义。 多态是让子类去复写父类的方法从而到达一样的引用不一样的行为的效果,这貌似是和里氏替换原则是相悖的 其实里氏替换原则是针对继承而言的,继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类从新定义。 而多态更多的是用在父类是接口或者抽象类的情形下,子类覆盖并从新定义父类的方法。不一样的子类有不一样的实现,从而有不一样的行为 Ps:纯属我的理解,若是哪位大神感受个人理解有错误,欢迎指正

相关文章
相关标签/搜索