复习一遍软件设计七原则

一、开闭原则

开闭原则是指软件实体如类、模块和函数应对扩展开放,对修改关闭,强调的是抽象构建框架,用实现扩展细节。可以提高软件的可复用性几可维护性。

例如

//设计一个课程的接口
public interface ICourse {
    Integer getId();

    String getName();

    Double getPrice();
}
//设计一个java书实体类
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;
    }

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

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

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

//现在我们有一个优惠活动,价格进行优惠
//如果直接修改JavaCourse中的getPrice()方法会存在一些风险,会影响其他地方调用结果
//下面我们在不更改原有代码的前提下,实现价格优惠
//写一个类JavaDiscountCourse
public class JavaDiscountCourse extends JavaCourse {
    public JavaDiscountCourse(Integer id, String name, Double price) {
        super(id, name, price);
    }

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

    public Double getPrice() {
        return super.getPrice() * 0.61;
    }
}

通过这种继承复用父类的办法优点是易于修改和扩展复用逻辑的办法,但是缺点也很明显。

继承复用的优点是:

  1. 子类可以调用或重写父类的方法,易于修改或扩展被复用的方法

继承复用的缺点是:

  1. 子类于父类的关系是强耦合关系,如果父类的方法更改时,必然导致子类发生变化。

  2. 父类的内部细节对于子类来说是可见的。比如父类的成员。

  3. 子类在运行时,就是实例化后,正在使用未被销毁时,无法改变从父类继承的方法的行为。

组合复用的方法

一个类可以把对象作为自己的成员变量,也就是说如果一个对象a包含成员b对象,那么a就可以委托b调用b的方法。即a以组合的方式复用b的对象。

通过组合对象复用的优点是:

  1. 对象b方法细节对于a是不可见的,比如b中的成员对于a是不可见的。

  2. 对象a与组合对象b是耦合关系,如果修改b的代码,不必修改a中的代码。

  3. 对象a在运行时可以动态指定所包含的对象

例如

//Com是个接口,它有个方法print(),在Computer类中的com接口变量可以调用SetCom(Com com)方法将其中的com对象存放在Com引用中
public class Computer {
    private Com com;

    public void setCom(Com com) {
        this.com = com;
    }

    public void execute() {
        com.print();
    }
}

多用组合,少用继承

在设计中,我们往往希望系统的类之间尽量是低耦合的关系,而不是强耦合的关系,所以在许多情况下要避开继承的缺点,利用组合的有点。

二、依赖倒置原则

依赖倒置原则就是指,程序要依赖抽象接口,而不是依赖具体实现,简单来讲就是面向接口编程,而不是面向程序编程;

//编写一个Tom类,他可以学习java和python
public class Tom {
    public void studyJavaCourse() {
        System.out.println("Tom 在学习 Java 的课程");
    }

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

//看调用方法
public static void main(String[] args) {
    Tom tom = new Tom();
    tom.studyJavaCourse();
    tom.studyPythonCourse();
}

//如果Tom要学习其他课程,那么需要更改实现方Tom的代码,和使用方的代码
//这样在追加新功能时,需要修改原底层Tom的代码非常不友好,可能会带来意想不到的风险
//下面我们对其进行优化,不修改底层代码
//新增一个抽象接口,Tom的学习行为
public interface ICourse {
    void study();
}

//在新增每个课程学习方法的实现
public class JavaCourse implements ICourse {
    @Override
    public void study() {
        System.out.println("Tom 在学习 Java 课程");
    }
}

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

//修改Tom类,Tom会学习
public class Tom {
    private ICourse course
      
    public Tom(ICourse course){
      this.course = course
    }
    public void study() {
        course.study();
    }
}

//来看调用
public static void main(String[] args) {
    Tom tom = new Tom(new JavaCourse());
    tom.study();
}
//这样看来,Tom如果想学习其他课程时,只要新增ICourse课程实现类,将新增的课程传给Tom即可,不用在修改底层代码了
//这其实也是大家熟悉的依赖注入

以抽象为基准比以细节为基准搭建起来的架构要稳固的多,不用修改底层代码,也避免了修改程序造成的风险;所以大家在拿到新需求的时候,要面向接口编程,先把抽象层搭建起来,再来实现细节

三、单一职责

单一职责是指,不要存在多个导致类变更的原因;一个类或模块只负责一个职责,这个类如果变化,那么变化的原因只有一个。

假设一个Class有两个职责,那么修改其中一个职责可能会造成另一个职责发生故障,所以还需要修改另一个职责的代码;这样以来,一个类就存在两个导致类变更的原因。

如何解决呢,我们可以对着这个Class的两个职责,拆分成两个类,后期需求变更的维护互不影响。

下面看例子

//我们的课程有直播课和录播课,直播课不能快进,录播课可以反复观看
public class Course {
    public void study(String courseName) {
        if ("直播课".equals(courseName)) {
            System.out.println(courseName + "不能快进");
        } else {
            System.out.println(courseName + "可以反复回看");
        }
    }
}

//代码调用
public static void main(String[] args) {
    Course course = new Course();
    course.study("直播课");
    course.study("录播课");
}

//Course承担了两个职责,假如要对课程加密,那么直播和录播的加密逻辑不一样必须修改代码,修改代码可能会造成互相影响造成的风险
//现在我们对职责进行分离解耦,分别创建两个类ReplayCourse 和 LiveCourse
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 static void main(String[] args) {
    LiveCourse liveCourse = new LiveCourse();
    liveCourse.study("直播课");
    ReplayCourse replayCourse = new ReplayCourse();
    replayCourse.study("录播课");
}

//业务发展,课程要做权限;没有付费的学院可以获取课程基本信息,已付费的学院可以获取视频流
//那么对于控制层至少含有两个职责,我们可以把展示职责和管理职责分离开来,创建两个接口
public interface ICourseInfo {
    String getCourseName();
    byte[] getCourseVideo();
}

public interface ICourseManager {
    void studyCourse();
    void refundCourse();
}
//这样修改后,职责分明,开发起来简单,维护也比较容易
//所以我们在写代码的过程,尽量让接口和方法保持单一职责,对项目后期维护也方便

四、接口隔离原则

接口隔离原则是指客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上,在这一原则知道下我们设计接口时候应注意一下几点:

  1. 一个类对另一个类的依赖应建立在最小的接口上
  2. 建立单一接口,不要建立庞大臃肿的接口
  3. 尽量细化接口,接口中的方法尽力那个少,不是越少越好,一定要适度

接口隔离原则符合我们常说的高内聚低耦合的设计思想,从而使得类具有很好的可读性、 可扩展性和可维护性;我们在设计接口的时候要去思考,考虑业务模型,包括以后可能发生变更的地方做一些预判,所以对于抽象,对于业务模型的理解是非常重要的

下面看例子

//描述动物的行为
public interface IAnimal {
    void eat();
    void fly();
    void swim();
}

//鸟的实现
public class Bird implements IAnimal {
    @Override
    public void eat() {
    }

    @Override
    public void fly() {
    }

    @Override
    public void swim() {
    }
}

//狗的实现
public class Dog implements IAnimal {
    @Override
    public void eat() {
    }

    @Override
    public void fly() {
    }

    @Override
    public void swim() {
    }
}

//可以看出,Bird 的 swim()方法可能只能空着,Dog 的 fly() 也只能时空的
//下面我们进行优化,分别设计不同行为的接口
public interface IEatAnimal {
    void eat();
}

public interface IFlyAnimal { 
  void fly();
}

public interface ISwimAnimal { 
  void swim();
}

//Dog 只实现 IEatAnimal 和 ISwimAnimal 接口
public class Dog implements ISwimAnimal, IEatAnimal {
    @Override
    public void eat() {
    }

    @Override
    public void swim() {
    }
}

五、迪米特法则

迪米特法则是指,一个对象,应该保持对其他对象最少了解,也成最少知道原则,尽量降低类与类之间的耦合。

通俗的讲,就是一个类对自己依赖的类知道的越少越好

比如:

//设计一个权限系统,Boss要知道已经发布的课程数量,这时候Boss要找TeamLeader去统计一下,TeamLeader 再把统计结果告诉 Boss
public class Course {
}

public class TeamLeader {
    public void checkNumberOfCourses(List<Course> courseList) {
        System.out.println("目前已发布的课程数量是:" + courseList.size());
    }
}

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.checkNumberOfCourses(courseList);
    }
}

//根据迪米特法则,Boss只需要知道课程统计结果,不依赖Course对象;TeamLeader统计需要饮用Course对象,Boss不需要知道Course
//来看改造后的代码
public class TeamLeader {
    public void checkNumberOfCourses() {
        List < Course > courseList = new ArrayList < Course > ();
        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.子类可以实现父类的抽象方法,在不能覆盖父类非抽象的方法

2.子类可以增加自己的方法

3.子类重载父类的方法时,方法的参数要比父类更为宽松

4.子类实现父类的方法时(重写、重载、实现),要比父类的返回值更为严谨或相等

例如

//在开闭原则一节时,我们通过子类覆盖父类方法实现的改变,这其实是不符合里氏替换原则的
//我们在子类重新写一个方法 getDiscountPrice 来达到不覆盖父类非抽象方法
public class JavaDiscountCourse extends JavaCourse {
    public JavaDiscountCourse(Integer id, String name, Double price) {
        super(id, name, price);
    }

    public Double getDiscountPrice() {
        return super.getPrice() * 0.61;
    }
}

//里氏替换原则的优点:
//1.约束继承泛滥,开闭原则的一种体现
//2.

七、合成复用原则

合成服用原则是指,尽量使用对象组合,而不是继承关系达到软件复用的目的

//比如获取数据库连接
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;但这违背了开闭原则
//此时我们把 DBConnection 抽象化,后面需要再接入新种类的数据库时,只需要实现getConnection方法即可
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 数据库连接";
    }
}

总结

在设计开发中,比一定要求所有代码都要遵循这几个原则,我们考虑实际情况,不用刻意追求完美,要适当的场景遵循软件设计原则,帮助我们写出优雅的代码。

不要为了套用设计模式而使用设计模式,而是在业务上遇到问题时,很自然地想到设计模式为一种解决方案。

高内聚-低耦合原则

什么是高内聚

高内聚指模块或者方法,完成了某个功能,它的内部实现不依赖于其他的模块,自己内部就可以搞定,使自己可以单独使用。言外之意就是封装的很好

什么是低耦合

对于低耦合,粗浅的理解是:一个完整的系统,模块与模块之间,尽可能的使其独立存在。也就是说,让每个模块,尽可能的独立完成某个特定的子功能。模块与模块之间的接口,尽量的少而简单。如果某两个模块间的关系比较复杂的话,最好首先考虑进一步的模块划分。这样有利于修改和组合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值