软件架构设计原则

1 软件架构设计原则

1.1 开闭原则

开闭原则(Open-Closed Principle,OCP)是指一个软件实体(如类、模块和函数)应该对扩展开放,对修改关闭。

课程接口ICourser:

public interface ICourse {
    Integer getId();
    String getName();
    Double getPrice();
}

Java架构课程的类JavaCourse:

public class JavaCourse implements ICourse {
    private Integer Id;
    private String name;
    private Double price;

    public JavaCourse(Integer id, String name, Double price) {
        this.Id = id;
        this.name = name;
        this.price = price;
    }

    @Override
    public Integer getId() {
        return this.Id;
    }

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

    @Override
    public Double getPrice() {
        return this.price;
    }
}

处理优惠逻辑的类JavaDiscountCurse:

public class JavaDiscountCourse extends JavaCourse {

    public JavaDiscountCourse(Integer id, String name, Double price) {
        super(id, name, price);
    }
    public Double getOriginPrice() {
        return super.getPrice();
    }
    @Override
    public Double getPrice() {
        return super.getPrice()*0.61;
    }
}

测试类OpenCloseTest:

public class OpenCloseTest {
    public static void main(String[] args) {
        ICourse iCourse = new JavaDiscountCourse(233,"学院专属课", 8888D);
        JavaDiscountCourse javaCourse = (JavaDiscountCourse) iCourse;
        System.out.println("课程ID:" + javaCourse.getId() +
                "\n课程名称:《" + javaCourse.getName() + "》" +
                "\n原价:" + javaCourse.getOriginPrice() + "元" +
                "\n折后价:" + javaCourse.getPrice() + "元");
    }
}

输出:

课程ID:233
课程名称:《学院专属课》
原价:8888.0元
折后价:5421.68元

1.2 依赖倒置原则

Dependence Inversion Principle,DIP。是指设计代码结构时,高层模块不应该依赖低层模块,二者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。

创建一个Jack类:

public class Jack {
    public void studyJavaCourse() {
        System.out.println("Jack在学习Java的课程");
    }
    public void studyPythonCourse() {
        System.out.println("Jack在学习Python的课程");
    }
}

测试类:

public class DependencyInversionTest {
    public static void main(String[] args) {
        Jack jack = new Jack();
        jack.studyJavaCourse();
        jack.studyPythonCourse();
    }
}

如果Jack还想学习AI的课程,则随着业务的扩展,要从低层到高层依次修改代码。在Jack类中增加studyAICourse()方法,在高层也要追加调用,实际上是很不稳定的,在修改代码的同时也会带来意想不到的风险。接下来优化代码,创建一个课程的抽象ICourse接口:

public interface ICourse {
    void study();
}

然后编写JavaCourse类:

public class JavaCourse implements ICourse {
    @Override
    public void study() {
        System.out.println("Jack在学习Java的课程");
    }
}

再实现PythonCourse类:

public class PythonCourse implements ICourse {
    @Override
    public void study() {
        System.out.println("Jack在学习Python的课程");
    }
}

修改Jack类:

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

调用代码:

public class DependencyInversionTest {
    public static void main(String[] args) {
        Jack jack = new Jack();
        jack.study(new JavaCourse());
        jack.study(new PythonCourse());
    }
}

输出:

Jack在学习Java的课程
Jack在学习Python的课程

如此,无论Jack的兴趣如何暴涨,对于新的课程,只需要新建一个类,通过传参的方式告诉Jack,而不需要修改底层代码。这种方式就叫依赖注入。

注入的方式还有构造器方式和Setter方式。构造器注入方式:

public class Jack {
    private ICourse course;
    public Jack(ICourse course) {
        this.course = course;
    }
    public void study() {
        course.study();
    }
}

调用代码:

public class DependencyInversionTest {
    public static void main(String[] args) {
        Jack jack = new Jack(new JavaCourse());
        jack.study();
    }
}

Jack在学习Java的课程

根据构造器方式注入,在调用时,每次都要创建实例。如果Jack是全局单例,则我们只能选择用Setter方式来注入,继续修改Jack类:

public class Jack {
    private ICourse course;
    public void setCourse(ICourse course) {
        this.course = course;
    }
    public void study() {
        course.study();
    }
}

调用代码:

public class DependencyInversionTest {
    public static void main(String[] args) {
        Jack jack = new Jack();
        jack.setCourse(new JavaCourse());
        jack.study();
        jack.setCourse(new PythonCourse());
        jack.study();
    }
}

Jack在学习Java的课程
Jack在学习Python的课程

1.3 单一职责原则

Simple Responsibility Principe,SRP:是指不要存在多于一个导致类变更的原因。一个类、接口或方法只负责一项职责。

以直播课和录播课为例子,创建一个Course类:

public class Course {
    public void study(String courseName) {
        if ("直播课".equals(courseName)) {
            System.out.println(courseName + "不能快进");
        } else {
            System.out.println(courseName + "可以反复回看");
        }
    }
}

调用代码:

public class SingleResponsibilityTest {
    public static void main(String[] args) {
        Course course = new Course();
        course.study("直播课");
        course.study("录播课");
    }
}

直播课不能快进
录播课可以反复回看

从代码来看,Course类承担了两种处理逻辑。如果现在需要对课程进行加密,直播课程和录播课程的加密逻辑不一样,必须修改代码。而修改代码的逻辑势必会一你会相互影响,容易带来不可控的风险。

需要对职责进行解耦,分别创建两个类:LiveCourse和ReplayCourse。

public class LiveCourse {
    public void study(String courseName) {
        System.out.println(courseName + "不能快进看");
    }
}
public class ReplayCourse {
    public void study(String courseName) {
        System.out.println(courseName + "可以反复回看");
    }
}

调用代码:

public class SingleResponsibilityTest {
    public static void main(String[] args) {
        LiveCourse liveCourse = new LiveCourse();
        liveCourse.study("直播课");

        ReplayCourse replayCourse = new ReplayCourse();
        replayCourse.study("录播课");
    }
}

直播课不能快进看
录播课可以反复回看

如果业务继续发展,课程要做权限。未付费学员可以获取基本信息,已付费学员可以获得学习权限。那么在控制课程层面至少有两个职责。我们可以将展示职责和管理职责分离开来,都实现同一个抽象依赖。设计一个顶层接口,创建ICourse接口:

public interface ICourse {
    // 获得基本信息
    String getCourseName();

    // 获得视频流
    byte[] getCourseVideo();

    // 学习课程
    void studyCourse();
    // 退款
    void refundCourse();
}

也可以将这个接口拆成两个接口:ICourseInfo和ICourseManager

public interface ICourseInfo {
    String getCourseName();
    byte[] getCourseVideo();
}
public interface ICourseManager {
    void studyCourse();
    void refundCourse();
}

1.4 接口隔离原则

Interface Segregation Principle,ISP:是指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。

对一个动物行为进行抽象描述:

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

Bird类:

public class Bird implements IAnimal {
    @Override
    public void eat() {}

    @Override
    public void fly() {}

    @Override
    public void swim() {}
}

Dog类:

public class Dog implements IAnimal {
    @Override
    public void eat() {}

    @Override
    public void fly() {}

    @Override
    public void swim() {}
}

可以看出,Bird的swim()方法只能空着,Dog的fly()方法显然也是不可能的。这时需要对不同动物行为来设计不同的接口,分别设计IEatAnimal、IFlyAnimal和ISwimAnimal接口。代码如下:

public interface IEatAnimal {
    void eat();
}
public interface IFlyAnimal {
    void fly();
}
public interface ISwimAnimal {
    void swim();
}

Dog只实现了IEatAnimal和ISwimAnimal接口,代码如下:

public class Dog implements IEatAnimal, ISwimAnimal {
    @Override
    public void eat() {}

    @Override
    public void swim() {}
}

1.5 迪米特原则

Law of Demeter,LoD:是指一个对象应该对其他对象保持最小的了解,又叫最少知道原则(Least Knowledge Principle,LKP),尽量降低类与类之间的耦合度。

设计一个权限系统,Boss需要查看目前发布到线上的课程数量。

public class Course {
}
public class TeamLeader {
    public void checkNumberOfCourses(List courseList) {
        System.out.println("目前已发布的课程数量是:" + courseList.size());
    }
}
public class Boss {
    public void commandCheckNumber(TeamLeader teamLeader) {
        // 模拟Boss一页一页往下翻页,TeamLeader实时统计
        List courseList = new ArrayList();
        for (int i = 0; i < 20; i++) {
            courseList.add(new Course());
        }
        teamLeader.checkNumberOfCourses(courseList);
    }
}
public class LawOfDemeterTest {
    public static void main(String[] args) {
        Boss boss = new Boss();
        TeamLeader teamLeader = new TeamLeader();
        boss.commandCheckNumber(teamLeader);
    }
}

目前已发布的课程数量是:20

根据迪米特原则,Boss只想要结果,不需要跟Course直接交流。改造如下:

public class TeamLeader {
    public void checkNumberOfCourses() {
        List courseList = new ArrayList();
        for (int i = 0; i < 20; i++) {
            courseList.add(new Course());
        }
        System.out.println("目前已发布的课程数量是:" + courseList.size());
    }
}
public class Boss {
    public void commandCheckNumber(TeamLeader teamLeader) {
        teamLeader.checkNumberOfCourses();
    }
}

1.6 里氏替换原则

Liskov Substitution Principle,LSP:是指如果对每一个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有的对象O1都替换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。

使用里氏替换原则有以下优点:

  • 约束继承泛滥,是开闭原则的一种体现
  • 加强程序的健壮性,同时变更时也可以做到非常好的兼容性,提高程序的可维护性和扩展性,降低需求变更时引入的风险。
public interface Quadangle {
    long getWidth();
    long getHeight();
}
public class Rectangle implements Quadangle {
    private long height;
    private long width;

    @Override
    public long getWidth() {
        return 0;
    }

    @Override
    public long getHeight() {
        return 0;
    }

    public void setHeight(long height) {
        this.height = height;
    }

    public void setWidth(long width) {
        this.width = width;
    }
}
public class Square extends Rectangle {
    private long length;
    public long getLength() {
        return length;
    }
    public void setLength(long length) {
        this.length = length;
    }

    @Override
    public long getWidth() {
        return getLength();
    }

    @Override
    public long getHeight() {
        return getLength();
    }

    @Override
    public void setHeight(long height) {
        setLength(height);
    }

    @Override
    public void setWidth(long width) {
        setLength(width);
    }
}
public class LiskovSubstiutionTest {
    public static void resize(Rectangle rectangle) {
        while (rectangle.getWidth() >= rectangle.getHeight()) {
            rectangle.setHeight(rectangle.getHeight()+1);
            System.out.println("width:" + rectangle.getWidth() +
                    "\nheight:" + rectangle.getHeight());
        }
        System.out.println("resize方法结束:" +
                "\nwidth:" + rectangle.getWidth() +
                "\nheight:" + rectangle.getHeight());

    }

    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        rectangle.setWidth(20);
        rectangle.setHeight(10);
        resize(rectangle);
    }
}

1.7 合成复用原则

Composite/Aggregate Reuse Principle,CARP:是指尽量使用对象组合(has-a)/聚合(contains-a)而不是继承关系达到软件复用的目的。

继承叫作白箱复用,相当于把所有的实现细节暴露给子类。组合/聚合称为黑箱复用,我们无法获取到类以外的对象的实现细节。

以数据库为例:

public class DBConnection {
    public String getConnection() {
        return "MySQL数据库连接";
    }
}
public class ProductDao {
    private DBConnection dbConnection;
    public void setDbConnection(DBConnection dbConnection) {
        this.dbConnection = dbConnection;
    }
    public void addProduct() {
        String conn = dbConnection.getConnection();
        System.out.println("使用" + conn + "增加产品");
    }
}

假设业务发生变化,数据库操作层要支持Oracle数据库,虽然可以在DBConnection中增加对Oracle数据库的支持,但是这违背了开闭原则。更改如下:

public abstract class DBConnection {
    public abstract String getConnection();
}
public class MySQLConnection extends DBConnection {
    @Override
    public String getConnection() {
        return "MySQL数据库连接";
    }
}
public class OracleConnection extends DBConnection {
    @Override
    public String getConnection() {
        return "Oracle数据库连接";
    }
}

具体选择交给应用层。

 

 

 

 

 

 

 

你可能感兴趣的:(设计模式)