软件架构设计原则

文章目录

软件架构设计原则

开闭性原则

什么是开闭性原则?就是我们已经写好的代码,对修改关闭,对扩展开放。

你就比如,公司是九点半上班,晚上六点半下班,你可以早来,可以晚走,这是可以扩展的。但是你不能晚来,早走,你不能在原有的逻辑上进行修改。

依赖倒置原则

细节通过传参进行控制,大的行为通过一个方法进行控制。你比如,现在有一个Tom类,如下

public class Tom{
    public void study(ICourse course){
        course.study();
    }
}

Tom的大的行为就是学习课程,这个大的行为我们使用一个方法study进行控制,而学习课程的一些细节,就是你学习的到底是什么课程,比方说你是学习java,还是学习python,又或者是学习AI算法,都可以通过传递不同的参数进行控制。我们只需要写一些实现类JavaCourse,PythonCourse,AICourse,让这些实现类都实现ICourse接口,然后把这些实现类当做参数传递给study方法就行了。

这样无论Tom想要学习什么课程,都可以通过传递不同的实现类参数给study来实现。这就叫做依赖倒置原则。

如果没有这个依赖倒置原则,你想一下会出现什么后果,就是如果Tom的学习兴趣很浓烈的话,那么每学习一门课程,你就需要要在Tom类里面定义一个方法,比如上面的这个例子,需要在Tom类里面定义三个方法,分别是JavaCourseStudy方法,PythonCourseStudy方法,AICourse方法,这样是不是就显得Tom这个类里面显得特别臃肿,所以这就是依赖倒置原则的好处。

单一职责原则

单一职责原则意思就是:一个类,接口,方法只负责一项职责。

先来说一下方法只负责一项职责是什么意思,就是一个方法中只能有一项功能,如果这个方法中的某一段很长的代码可以被单独抽离成一个功能,那么我们就需要把这段代码单独封装成一个功能。

接口只负责一项职责是什么意思,就是一个接口里面的抽象方法必须要属于同一个大类的功能。比如有专门获取信息的接口,有专门管理信息的接口,我们要写成两个接口,不能把它们写在一个接口里面。

类只负责一项职责是什么意思,我们的类实现了只负责一项职责的接口,那么它肯定也是只负责一项职责。

接口隔离原则

接口隔离原则的意思就是每一个接口必须要是一个最小单位,什么意思呢?

你就比如现在有三个接口:

//可以吃的动物接口
public interface IEatAnimal{
    void eat();
}

//可以飞的动物接口
public interface IFlyAnimal{
    void fly();
}

//可以游泳的动物接口
public interface ISwimAnimal{
    void swim();
}

如果我们现在有一个小鸟Bird,这个小鸟既可以飞又可以吃,但是不可以游泳,那么我们就可以写成这种格式:

public class Bird implements IEatAnimal,IFlyAnimal{
    @Override
    void eat(){}
    @Override
    void fly(){}
}

上面定义的三个接口就是最小单位的接口。

那怎么判断接口不是最小单位的接口呢?你就想一下,如果一个实体类实现了一个接口,这个接口中的抽象方法会不会有得不到继承的。比如现在有一个动物接口,如下:

public interface IAnimal{
    void eat();
    void fly();
    void swim();
}

然后一个小鸟Bird来实现这个接口,那么就会出现一个问题,因为swim()小鸟是做不到的,所以就会出现一个用不着实现的多余方法,那么IAnimal这个接口就不是最小单位的接口。

迪米特原则

迪米特原则的意思就是:只和熟悉的人交流,不和陌生人交流。

其实也就是不要跨层管理,上层就只负责它的直属下层就行了,不要负责它的直属下层的直属下层。

你比如,现在有一个Boss要统计课程的数量,它需要去问TeamLeader,然后TeamLeader会去统计课程的数量。如果Boss不仅要管他的直属下层TeamLeader还要管它的直属下层的直属下层,就会出现下面的这个情况,如下:

//Boss类的代码如下
public class Boss{
    public void commandCheckNumber(TeamLeader teamLeader){
        //模拟Boss一页一页往下翻页,TeamLeader实时统计
        List<Course> courseList = new ArrayList<Course>();
        for(int i=0; i<20; i++){
            courseList.add(new Course());
        }
        teamLeader.checkNumberOfCourse(courseList);
    }
}

//TeamLeader类的代码如下
public class TeamLeader{
    public void checkNumberOfCourse(List<Course> courseList){
        System.out.println("目前已经发布的课程数量是:"+courseList);
    }
}

乍一看上面的代码是没有问题的,但是你仔细分析可以发现,Boss类它不仅和它的直属下层TeamLeader进行了交流,还和它的直属下层的下层Course进行了交流,这样就不符合迪米特原则了。我们可以进行一些修改,让Boss类只和它的直属下层TeamLeader进行交流,不要和Course进行交流,至于和Course进行交流的工作应该分给TeamLeader,如下:

//Boss类的代码如下
public class Boss{
    public void commandCheckNumber(TeamLeader teamLeader){
        teamLeader.checkNumberOfCourse(courseList);
    }
}

//TeamLeader类的代码如下
public class TeamLeader{
    public void checkNumberOfCourse(List<Course> courseList){
        List<Course> courseList = new ArrayList<Course>();
        for(int i=0; i<20; i++){
            courseList.add(new Course());
        }
        System.out.println("目前已经发布的课程数量是:"+courseList);
    }
}

对应到我们的项目中就是,我现在能想到的就是一个三层架构,上层直接去调用它的下层,但不能出现即调用它的下层又调用它的下下层的情况。

里式替换原则

什么是里式替换原则呢?意思就是所有用到父类的地方,都可以用它的子类替换。引申含义其实是:子类可以扩展父类的功能,但不可以改变父类原有的功能。有两个规范:

1.子类可以实现父类中的抽象方法,但是不能重写父类中的非抽象方法,父类中的非抽象方法,子类中必须要保持父类中原有的非抽象方法的完整性。

2.子类中可以增加自己特有的方法。

比如,如果子类中重写了父类的非抽象方法,当用子类替换父类的时候,可能就出现异常了,如下:

有一个长方形类:

/**
 * @Date 2022/1/31 12:31
 * @Author 望轩
 */
public class Rectangle {
    private long height;
    private long width;
    public long getWidth(){
        return width;
    }
    public long getHeight(){
        return height;
    }
    public void setHeight(long height){
        this.height = height;
    }
    public void setWidth(long width){
        this.width = width;
    }
}

有一个正方形类继承长方形类,但是正方形类重写了父类中的非抽象方法setHeight和setWidth,如下:

/**
 * @Date 2022/1/31 12:35
 * @Author 望轩
 */
public class Square extends Rectangle {
    @Override
    public long getWidth() {
        return super.getWidth();
    }

    @Override
    public long getHeight() {
        return super.getHeight();
    }

    @Override
    public void setHeight(long height) {
        super.setHeight(height);
        super.setWidth(height);
    }

    @Override
    public void setWidth(long width) {
        super.setWidth(width);
        super.setHeight(width);
    }
}

然后写了一个测试类,如下:

/**
 * @Date 2022/1/31 12:37
 * @Author 望轩
 */
public class Test {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        rectangle.setWidth(20);
        rectangle.setHeight(10);
        resize(rectangle);
    }
    
    //当长方形的宽大于等于高的时候循环,让长方形高加一
    public static void resize(Rectangle rectangle){
        while (rectangle.getWidth() >= rectangle.getHeight()){
            rectangle.setHeight(rectangle.getHeight()+1);
            System.out.println("width:"+rectangle.getWidth()+",height:"+rectangle.getHeight());
            System.out.println("resize方法结束"+"\nwidth:"+rectangle.getWidth()+",height:"+rectangle.getHeight());
        }
    }
  
    
}

当测试类中的resize方法的参数是Rectangle父类的时候,测试方法可以成功执行。那么按照里式替换原则,我们可以把父类替换成它的子类,仍然可以成功的执行,但是当我们把这里的父类对象Rectangle替换成它的子类Square之后,测试方法就不能成功执行了,会出现死循环,因为我们的Square子类覆盖了父类的非抽象方法setHeight和setWidth,导致width和height都是同步变化的,所以while循环会一直循环下去。

因此,根据里式替换原则,子类一定不能覆盖父类的原有的方法,但子类可以增加自己独有的方法。

所以像上面的这种,正方形,长方形的例子,我们要重新写一个接口,把正方形和长方形共有的东西给抽象出去,然后再让正方形,长方形分别重写这个接口,然后实现不同的方法逻辑,如下:

//定义一个四边形接口
public interface Quadrangle{
    void setWidth(long width);
    void setHeight(long height );
}

//正方形实现这个接口
public class Square implements Quadrangle{
    private long width;
    private long height;
    @Override
    void setWidth(long width){
        this.width = width;
        this.height = height;
    }
    @Override
    void setHeight(long height){
        this.height = height;
        this.width = width;
    }
}

//长方形实现这个接口
public class Rectangle implements Quadrangle{
    private long width;
    private long height;
    @Override
    void setWidth(long width){
        this.width = width;
    }
    @Override
    void setHeight(long height){
        this.height = height;
    }
}
上一篇:Python基础7-面向对象高级编程


下一篇:linux 安装python-setuptools