软件设计的七大原则

生活中,最使人疲惫的往往不是道路的遥远,而是心中的郁闷;最使人痛苦的往往不是生活的不幸,而是希望的破灭;最使人颓废的往往不是前途的坎坷,而是自信的丧失;最使人绝望的往往不是挫折的打击,而是心灵的死亡。所以我们要有自己的梦想,让梦想的星光指引着我们走出落漠,走出惆怅,带着我们走进自己的理想。

导读:本篇文章讲解 软件设计的七大原则,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

软件设计原则概述

学习设计原则是学习设计模式的基础。

实际开发过程中,并不一定要求所有代码都遵循设计原则,只需要在适当的场景遵循设计原则,就可以帮助开发者设计出更加优雅的代码结构。

软件设计原则一共有七个:

开闭原则:对扩展开放,对修改关闭。

依赖倒置原则:通过抽象使各个类或者模块不相互影响,实现松耦合。

单一职责原则:一个类、接口、方法只做一件事。

接口隔离原则:尽量保证接口的纯洁性,客户端不应该依赖不需要的接口。

迪米特法则:又叫最少知道原则,一个类对其所依赖的类知道得越少越好。

里氏替换原则:子类可以扩展父类的功能但不能改变父类原有的功能。

合成复用原则:尽量使用对象组合、聚合,而不使用继承关系达到代码复用的目的。

具体详细参考:软件设计原则思维导图
在这里插入图片描述

1.开闭原则

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

实现开闭原则的核心思想就是面向抽象编程。强调用抽象构建框架,用实现扩展细节,提高软件系统的可复用性及可维护性。

假设动物都有跑的行为

public interface Animal {
    void run();
}

public class Dog implements Animal{
    @Override
    public void run() {
        System.out.println("Dog is running");
    }
}

若此时要给Dog添加Run的修饰,在不修改原有代码前提前下,可根据开闭原则,用拓展的方式实现

public class NewDog extends Dog{
  public void newRun(){
        System.out.println("NewDog is happy running");
    }
}

2.依赖倒置原则

依赖倒置原则(Dependence Inversion Principle,DIP)是程序要依赖于抽象接口,不要依赖于具体实现。

高层模块不应该依赖底层模块,二者都应该依赖其抽象。

通过依赖倒置,可以减少类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并能够降低修改程序所造成的风险。

汽车工厂生产BM和BC

public class CarFactory {
   void generateBM(){
        System.out.println("CarFactory is generateBM");
    }
    
    void generateBC(){
        System.out.println("CarFactory is generateBC");
    }
}

 public static void main(String[] args) {
        CarFactory carFactory = new CarFactory();
        carFactory.generateBM();
        carFactory.generateBC();
    }

若此时汽车工厂还想生成AD,则除了在CarFactory类(低层)添加generateAD()方法,还需要在调用的地方(高层)调用。

解决方案:创建抽象接口,不同的车型实现相同接口

public interface IGenerate {
    void generate();
}

public class BM implements IGenerate{
    @Override
    public void generate() {
        System.out.println("CarFactory is generateBM");
    }
}

public class BC implements IGenerate{
    @Override
    public void generate() {
        System.out.println("CarFactory is generateBC");
    }
}

public class AD implements IGenerate{
    @Override
    public void generate() {
        System.out.println("CarFactory is generateAD");
    }
}

这是一种常用的方式,叫依赖注入。注入的方式还有构造器方式和 setter 方式

public class CarFactory {
    void generate(IGenerate generate){
        generate.generate();
    }
}

 public static void main(String[] args) {
        CarFactory carFactory = new CarFactory();
        carFactory.generate(new BM());
        carFactory.generate(new BC());
        carFactory.generate(new AD());
}

-------------------------------------------------------------------------------------------

public class CarFactory2 {
    private static IGenerate generate;

    public CarFactory2(IGenerate iGenerate) {
        this.generate = iGenerate;
    }

    public void generate() {
        generate.generate();
    }

    public static void main(String[] args) {
        CarFactory2 bm = new CarFactory2(new BM());
        bm.generate();
        CarFactory2 bc = new CarFactory2(new BC());
        bc.generate();
        CarFactory2 ad = new CarFactory2(new AD());
        ad.generate();

    }
}

-------------------------------------------------------------------------------------------

public class CarFactory3 {
    private static IGenerate generate;

    public static void setGenerate(IGenerate generate) {
        CarFactory3.generate = generate;
    }

    public void generate() {
        generate.generate();
    }

    public static void main(String[] args) {
        BM bm = new BM();
        CarFactory3 bmFc = new CarFactory3();
        bmFc.setGenerate(bm);
        bmFc.generate();

        BC bc = new BC();
        CarFactory3 bcFc = new CarFactory3();
        bcFc.setGenerate(bc);
        bcFc.generate();

        AD ad = new AD();
        CarFactory3 adFc = new CarFactory3();
        adFc.setGenerate(ad);
        adFc.generate();

    }
}

3.单一职责原则

单一职责(Simple Responsibility Pinciple,SRP)是指不要存在多于一个导致类变更的原因。

比如一个 Class负责两个职责,一旦发生需求变更,修改其中一个职责的逻辑代码,有可能会导致另一个职责的功能发生故障。于是可以给两个职责分别用两个 Class 来实现,进行解耦,即使以后需求变更维护也互不影响。

这样做可以降低类的复杂度,提高类的可读性,提高系统的可维护性,降低变更引起的风险。

单一职责原则就是一个Class/Interface/Method只负责一项职责。

CarFactory类既要生成small Car又要生成big Car,承担了两种处理逻辑

public class CarFactory {
    void generate(Integer type) {
        if (type == 1) {
            System.out.println("generate small Car");
        } else {
            System.out.println("generate big Car");
        }
    }

    public static void main(String[] args) {
        CarFactory carFactory = new CarFactory();
        carFactory.generate(1);
        carFactory.generate(2);
    }
}

此时,若要生产不同类型Car,则需修改代码,造成不可控的风险。

解决方案:对职责进行分离解耦

public class SmallCar {
    void generate() {
        System.out.println("generate small Car");
    }
}

public class BigCar {
    void generate() {
        System.out.println("generate big Car");
    }
}


public static void main(String[] args) {
    SmallCar smallCar = new SmallCar();
    smallCar.generate();

    BigCar bigCar = new BigCar();
    bigCar.generate();
}

4.接口隔离原则

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

接口隔离原则符合常说的高内聚低耦合的设计思想,从而使得类具有很好的可读性、可扩展性和可维护性。

注意点:

1.一个类对一类的依赖应该建立在最小的接口之上

2.建立单一接口,不要建立庞大臃肿的接口

3.尽量细化接口,接口中的方法尽量少(要适度)

Bird不具备run的行为,Dog不具备fly的行为

public interface IAnimal {
    void eat();

    void fly();

    void run();
}

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


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

    @Override
    public void fly() {
    }

    @Override
    public void run() {
    }
}

此时,应该针对不同动物行为来设计不同的接口

public interface IEatAnimal {
	void eat();
}

public interface IFlyAnimal {
	void fly();
}

public interface IRunAnimal {
	void run();
}

不同的动物选择不同的接口实现,从而达到接口隔离原则,符合高内聚低耦合的设计思想

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


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

5.迪米特法则

迪米特原则(Law of Demeter LoD)是指一个对象应该对其他对象保持最少的了解,又叫最少知道原则(Least Knowledge Principle,LKP),尽量降低类与类之间的耦合。主要强调只和朋友交流,不和陌生人说话。

朋友的定义:

出现在成员变量、方法的输入、输出参数中的类都可以称之为成员朋友类

陌生人的定义:

出现在方法体内部的类不属于朋友类

举例:校长需要知道学生的信息,就需要由老师进行统计报告

Principal类做了Teacher类做的事情,与Student 产生关联关系

class Student {
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

class Teacher {
  /**
   * 报告学生信息
   */
    void report(List<Student> studentLists) {
        studentLists.stream().forEach(student -> {
            System.out.println("name :" + student.name + " age : " + student.age);
        });
    }
}

class Principal {
    void statistics(Teacher teacher) {
        ArrayList<Student> students = new ArrayList<>();
        students.add(new Student("张三", 18));
        students.add(new Student("李四", 19));

        teacher.report(students);
    }

    public static void main(String[] args) {
        Principal principal = new Principal();
        Teacher teacher = new Teacher();
        principal.statistics(teacher);
    }
}

Principal类只关心Teacher类反馈的结果,不需要与Student产生关系

class Student {
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}


class Teacher {
  /**
   * 报告学生信息
   */
    void report() {
      ArrayList<Student> students = new ArrayList<>();
      students.add(new Student("张三", 18));
      students.add(new Student("李四", 19));

      students.stream().forEach(student -> {
            System.out.println("name :" + student.name + " age : " + student.age);
        });
    }
}

class Principal {
    void statistics(Teacher teacher) {
        teacher.report();
    }

    public static void main(String[] args) {
        Principal principal = new Principal();
        Teacher teacher = new Teacher();
        principal.statistics(teacher);
    }
}

6.里氏替换原则

里氏替换原则(Liskov Substitution Principle,LSP)是指一个软件实体如果适用一个父类的话,那一定是适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。

子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。

注意点:

1.子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法

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

3.当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松

4.当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或相等

假设动物都有跑的行为,后来需要对该行为进行修饰,进行了重新父类方法

public interface Animal {
    void run();
}

public class Dog implements Animal{
    @Override
    public void run() {
        System.out.println("Dog is running");
    }
}
public class NewDog extends Dog{
  public void run(){
        System.out.println("NewDog is happy running");
    }
}

正确的做法是增加方法而不是覆盖父类的方法

public class NewDog extends Dog{
  public void newRun(){
        System.out.println("NewDog is happy running");
    }
}

7.合成复用原则

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

可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少。

继承又叫白箱复用,相当于把所有的实现细节暴露给子类

组合/聚合也称为黑箱复用,对类以外的对象是无法获取到实现细节的

使用继承

public class DBConnection {
    public String getConnection(){
        return "connection";
    }
}

public class MyLDao extends DBConnection {
    public void save(Object obj) {
        String connection = super.getConnection();
        System.out.println("获取连接:" + connection + " ,进行保存");
    }
}

改造为合成复用原则。使用构造注入,也可以使用Setter注入

public class DBConnection {
    public String getConnection(){
        return "connection";
    }
}

public class MyLDao {
    private DBConnection dbConnection;

    public MyLDao (DBConnection dbConnection) {
        this.dbConnection = dbConnection;
    }

    public void save(Object obj) {
        String connection = dbConnection.getConnection();
        System.out.println("获取连接:" + connection + " ,进行保存");
    }
}

    public static void main(String[] args) {
        MyLDao myLDao = new MyLDao(new DBConnection());
        myLDao.save(new Object());
    }
-----------------------------------------------------------------------------

public class MyLDao {
    private DBConnection dbConnection;

    public void setDbConnection(DBConnection dbConnection) {
        this.dbConnection = dbConnection;
    }

    public void save(Object obj) {
        String connection = dbConnection.getConnection();
        System.out.println("获取连接:" + connection + " ,进行保存");
    }
}

    public static void main(String[] args) {
        MyLDao myLDao = new MyLDao();
        myLDao.setDbConnection(new DBConnection());
        myLDao.save(new Object());
    }

假如业务变化,要求使用Oracle,可以创建新的Oracle数据库连接继承原有连接,原有代码无须进行修改,而且还可以很灵活地增加新的数据库连接方式。

public class OracleDBConnection extends DBConnection {
    public String getConnection() {
        return "Oracle Connection";
    }
}

public class MyLDao {
    private DBConnection dbConnection;

    public void setDbConnection(DBConnection dbConnection) {
        this.dbConnection = dbConnection;
    }

    public void save(Object obj) {
        String connection = dbConnection.getConnection();
        System.out.println("获取连接:" + connection + " ,进行保存");
    }
}

    public static void main(String[] args) {
        MyLDao myLDao = new MyLDao();
        myLDao.setDbConnection(new OracleDBConnection());
        myLDao.save(new Object());
    }

其实DBConnection还不是一种抽象,不便于系统扩展,在设计之初可将DBConnection变成抽象类,若有新的需求只需要实现具体逻辑。

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";
	}
}



public class MyLDao {
    private DBConnection dbConnection;

    public void setDbConnection(DBConnection dbConnection) {
        this.dbConnection = dbConnection;
    }

    public void save(Object obj) {
        String connection = dbConnection.getConnection();
        System.out.println("获取连接:" + connection + " ,进行保存");
    }
}

    public static void main(String[] args) {
        MyLDao myLDao = new MyLDao();
        myLDao.setDbConnection(new OracleConnection());
        myLDao.save(new Object());
    }

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/136917.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!