软件开发七大原则

一、开闭原则:面对扩展开放,面对修改关闭

(1)指的是在开发的过程中尽量的去扩展代码,而不是去修改原来的代码,以免影响到之前的逻辑。

(2)强调的是用抽象构建框架,用实现扩展细节。

(3)可以提高软件系统的可复用性及可维护性

(4)例:原有课程类,闲杂要对课程进行打折,应该怎么处理呢?
原有课程类:

package test1;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public interface Course {
    public String getId();
    public String getName();
    public double getPrice();
}
package test1;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class JavaCourse implements Course {
  private String id;
  private String name;
  private double price;

    @Override
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Override
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public JavaCourse(String id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}

 新增打折课程类

package test1;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class JavaDiscountCourse extends JavaCourse{

    public JavaDiscountCourse(String id, String name, double price) {
        super(id, name, price);
    }

    public double getOriginPrice() {
        return super.getPrice();
    }

    public double getPrice() {
        return super.getPrice()*0.8;
    }

}

在不修改原来的逻辑的基础上作出对应的修改

测试类:

package test1;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class Test {
    public static void main(String[] args) {
        JavaDiscountCourse cource = new JavaDiscountCourse("001","java",100.0);
        System.out.println(cource.getPrice());
        System.out.println(cource.getOriginPrice());
    }
}

 二、依赖倒置原则:依赖于抽象接口,不要依赖于具体实现。

(1)要求对抽象进行编程,不要对实现进行编程。

(2)降低了客户与实现模块间的耦合。

(3)例:原有转换工具类可以转换word,pdf两种类型的文件,现在该工具类需要扩展使其在原来的基础上还能转换excel文件

如果是按照依赖实现编程:

package test2;

/**
 * des: office文档转pdf类
 * author:songyan
 * date: 2019/10/6
 **/
public class Trans {
    public void transWord(){
        System.out.println("转换");
    }
    public void transPDF(){
        System.out.println("pdf转换");
    }
}

客户端:

package test2;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class Client {
    public static void main(String[] args) {
        Trans trans = new Trans();
        trans.transWord();
        trans.transPDF();
    }
}

使用这种方式的弊端就在于,如果说要扩展转换工具类的工具类的功能,就需要去修改之前的代码,显然这种做法是非常不安全的,有可能就会影响之前代码。

更好的做法是:

package test2;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class TransUtil {
    public void trans(OfficeDocument document){
        document.trans();
    }
}
package test2;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class WordDocument implements OfficeDocument {
    @Override
    public void trans() {
        System.out.println("word文档转换");
    }
}
package test2;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class PDFDocuemnt implements OfficeDocument{

    @Override
    public void trans() {
        System.out.println("pdf转换");
    }
}
package test2;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class Test {
    public static void main(String[] args) {
        TransUtil trnas = new TransUtil();
        trnas.trans(new WordDocument());
        trnas.trans(new PDFDocuemnt());

    }
}

在转换工具类中是针对处理对象的接口进行处理的,在想要扩展功能的时候只需要添加一个实现类即可,例如:

package test2;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class ExcelDocument implements OfficeDocument{

    @Override
    public void trans() {
        System.out.println("excel转换");
    }
}
package test2;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class Test {
    public static void main(String[] args) {
        TransUtil trnas = new TransUtil();
        trnas.trans(new WordDocument());
        trnas.trans(new PDFDocuemnt());
        trnas.trans(new ExcelDocument());

    }
}

使用这种方法只需要扩展之前的代码,而不需要修改之前的代码,其实也就是上面说开闭原则。

这个例子其实就是“依赖注入”,那么依赖注入的方式又包括构造器注入,setter方法注入,下面简介一下这两种方式

1)构造器注入

package test2.generator;

import test2.OfficeDocument;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class TransUtil {
    private OfficeDocument officeDocument;

    public TransUtil(OfficeDocument officeDocument) {
        this.officeDocument = officeDocument;
    }

    public void trans() {
        officeDocument.trans();
    }
}
package test2.generator;

import test2.WordDocument;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class Test {
    public static void main(String[] args) {
        TransUtil transUtil = new TransUtil(new WordDocument());
        transUtil.trans();
    }
}

2)setter方式注入

package test2.setter;

import test2.OfficeDocument;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class TransUtil {
    private OfficeDocument officeDocument;

    public void setOfficeDocument(OfficeDocument officeDocument) {
        this.officeDocument = officeDocument;
    }

    public void trans(){
        officeDocument.trans();
    }
}
package test2.setter;

import test2.WordDocument;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class Test {
    public static void main(String[] args) {
        TransUtil  transUtil= new TransUtil();
        transUtil.setOfficeDocument(new WordDocument());
        transUtil.trans();
    }
}

以抽象为基准比以细节为基准搭建起来的框架要稳健的多,因此,大家在拿到需求之后要面向抽象接口来编程,先顶层在底层来设计代码结构。

 

三、单一职责原则:一个类,一个接口,一个方法应该只有一个职能

(1)如果有多个职能,则其中一个职能发生改变之后,就需要修改这个类的功能,就有可能影响到另一个职能。所以我们有必要对他们进行一定程度的拆分。

(2)降低类的复杂度,提高类的可读性,提高系统的可维护性,降低变更引起的风险

(3)例:有一个对nginx操作的工具类如下:

package test3;

/**
 * Nginx工具类
 * author:songyan
 * date: 2019/10/6
 **/
public class NginxUtil {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void start(){
        System.out.println("启动nginx");
    }
}

里面有设计nginx基本信息的方法,也有启动nginx的方法,比如说,nginx的启动方法发生了改变,这个时候就需要修改nginxUtil类,那再修改的过程中就有可能对他的其他方法产生影响,因此我们可以对他进行以下划分,将他隔离开:

package test3;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class NginxInfo {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
package test3;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class NginxOperator {
    public void start(){
        System.out.println("nginx启动");
    }
}

接口与此类似。。

方法不符合单一职责而原则的例子:

    public void modefied(String name,String fav){
        System.out.println("修改名字");
        System.out.println("修改爱好");
    }

方法符合单一职责而原则的例子:

    public void modefiedName(String name){
        System.out.println("修改姓名");
    }
    public void modefiedFav(String fav){
        System.out.println("修改爱好");
    }

其实这种方式乍一看可能感觉不到这样写的好处在哪里,我突然想到我最近的一个任务,是修改之前的同事写的一个系统,里面大多数的逻辑代码都是摞在一起的,一个方法中有几百行代码,做什么的都有,可能有的时候只需要改其中的一个点,但是可能就会影响到其他部分的代码,还有一个缺点就是,里面的代码错综复杂,可能的要找到你想改的地方都很难。

总结一下就是,在写代码的过程中如果尽量的保持单一指责原则就会,提高类的可读性,提高系统的可维护性,降低变更引起的风险。

 

四、接口隔离原则:使用多个专一的接口,而不使用单一的总接口

(1)一个类对一个类的接口应该建立在最小的接口之上。

(2)尽力单一的接口,而不要建立单一臃肿的接口

(3)尽量的细化接口,接口中的方法尽量少(不是越少越好,要适度)

(4)例:前几天总结的office文档转pdf的方式,其中openoffice,aspose是支持windows,linux两种系统的,但是jacob只支持windows系统,下面的代码就会存在一定的问题。

在下面的代码中,将windows转换的方式,Linux转换的方式放在了一个接口中:

package test4;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public interface Itrans {
    void windowsTrans();
    void linuxTrans();

}

在openoffice,aspose两种方式中,是没有问题的,见下:

package test4;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class AsposeTrans implements  Itrans{
    @Override
    public void windowsTrans() {
        System.out.println("Aspose在windows的转换");

    }

    @Override
    public void linuxTrans() {
        System.out.println("Aspose在linux的转换");

    }
}
package test4;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class OpenofficeTrans implements Itrans{
    @Override
    public void windowsTrans() {
        System.out.println("oppenoffice在windows的转换");
    }

    @Override
    public void linuxTrans() {
        System.out.println("oppenoffice在linux的转换");
    }
}

但是,你会发现在jacob中,他是不支持在Linux的转换的,但是实现这个接口的话必须重写这个接口。。

package test4;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class JacobTrans implements Itrans{
    @Override
    public void windowsTrans() {
        System.out.println("Jacob在windows的转换");
    }

    @Override
    public void linuxTrans() {

    }
}

针对上面的情况可以做以下完善,将接口中的方法拆分到两个接口中:

package test4;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public interface IWindowsTrans {
    void windowsTrans();
}
package test4;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public interface ILinuxTrans {
    void linuxTrans();
}

对两种系统都支持的方式可以去实现两种接口

package test4;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class AsposeTrans implements  IWindowsTrans,ILinuxTrans{
    @Override
    public void windowsTrans() {
        System.out.println("Aspose在windows的转换");

    }

    @Override
    public void linuxTrans() {
        System.out.println("Aspose在linux的转换");

    }
}

对只支持一种系统的方式只需要实现一种接口

package test4;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class JacobTrans implements  IWindowsTrans{
    @Override
    public void windowsTrans() {
        System.out.println("Jacob在windows的转换");
    }
    
}

 

五、迪米特法则:最少知道原则。

(1)迪米特原则主要强调只和朋友交流,不和陌生人说话。出现在成员变量、方法的输入、输出参数中的类都可以称之为成员朋友类,而出现在方法体内部的类不属于朋友类。

(2)例:老板让leader查询课程的数量

package test5;

import java.util.ArrayList;
import java.util.List;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class Boss {
    public void askClassNumber(){
        List<Clazz> clazzLiat = new ArrayList<Clazz>();
        clazzLiat.add(new Clazz());
        clazzLiat.add(new Clazz());
        clazzLiat.add(new Clazz());
        TeamLeader teamLeader = new TeamLeader();
        System.out.println(teamLeader.getClassNumber(clazzLiat));
    }
}
package test5;

import java.util.ArrayList;
import java.util.List;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class TeamLeader {
    public int getClassNumber(List<Clazz> clazzLiat) {
        return clazzLiat.size();
    }
}
package test5;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class Clazz {
}

以上代码实现所要求的功能是完全没有问题的,但是,根据迪米特法则,Boss类是没有必要跟Clazz类关联的,所以可以改成以下代码

package test5;

import java.util.ArrayList;
import java.util.List;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class Boss {
    public void askClassNumber(){

        TeamLeader teamLeader = new TeamLeader();
        System.out.println(teamLeader.getClassNumber());
    }
}
package test5;

import java.util.ArrayList;
import java.util.List;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class TeamLeader {
    public int getClassNumber() {
        List<Clazz> clazzLiat = new ArrayList<Clazz>();
        clazzLiat.add(new Clazz());
        clazzLiat.add(new Clazz());
        clazzLiat.add(new Clazz());
        return clazzLiat.size();
    }
}
package test5;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class Clazz {
}
package test5;

/**
 * author:songyan
 * date: 2019/10/6
 **/
public class Test {
    public static void main(String[] args) {
        Boss boss = new Boss();
        boss.askClassNumber();
    }
}

简单说就是boss只需要去问leader课程数量有多少,具体的统计过程需要leader自己来完成,最终把结果给BOSS即可。

 

六、里氏替换原则:父类可以使用的地方也可以适用子类

(1)例:在开闭原则的例子中,在打折类中使用了getPrice获取了打折后的价格,使用getOriginPrice获取打折之前的价格,这里其实是违背了里氏替换原则的,根据里氏替换原则,JavaCourse可以通过getPrice获取课程的价格(原价),那么其子类应该也是可以通过该方法获取课程原价的,但是在重写的时候逻辑却换成了获取折后价。

那么正确的写法应该如下:

package test6;

/**
 * author:songyan
 * date: 2019/10/7
 **/
public interface ICourse {
    public String getId();
    public String getName();
    public double getPrice();
}
package test6;

/**
 * author:songyan
 * date: 2019/10/7
 **/
public class JavaCourse implements ICourse{
    private String id;
    private String name;
    private double price;

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public JavaCourse(String id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}
package test6;

/**
 * author:songyan
 * date: 2019/10/7
 **/
public class DiscountJavaCourse extends JavaCourse{
    public  double getPrice(){
        return super.getPrice();
    }

    public double getDiscountPrice(){
           return super.getPrice()*0.8;
    }
    public DiscountJavaCourse(String id, String name, double price) {
        super(id, name, price);
    }
}

 

七、合成复用原则:尽量使用组合,聚合的方式而不是继承的方式实现复用

(1)合成服用原则具体指的是在创建新的对象的时候使用已有的对象。

(2)使用的方式有两种:组合/聚合,继承。

(3)在新对象与“已有的对象”两者的关系是“is-a”时,使用继承关系;两者的关系是"has-a"时,使用组合/聚合的方式。

(4)在使用合成服用原则时,注意不要滥用继承关系

(5)区分“组合”,“聚合”,“继承”

  1)组合(整体与部分,同生命周期)

   新旧对象是“has-a”的关系,是一种“强”的拥有关系,原有的对象是新对象的一部分,并且两个对象的生命周期是一样的。

   例:人与四肢的关系。人有四肢,四肢是人体的一部分,人没了,四肢也就不存在了。

  2)聚合(整体与部分,不同生命周期)

    新旧对象是"has-a"的关系,是一种“弱”的拥有关系,原有的对象不是新对象的一部分,两个对象的生命周期可以不相同。

    例:人与人群的关系。人群里面有人,当人群散了,人照样可以存在。

  3)继承

    例:学生与人的关系。学生是人,输入人类的一种。

   新旧对象是“is-a”的关系。

posted on 2019-10-06 17:39  song.yan  阅读(231)  评论(0编辑  收藏  举报