【设计模式】软件设计七大原则

文章目录

开闭原则

描述

  • 定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭
  • 强调用抽象构建框架,用实现扩展细节,是所有设计原则的一个基础
  • 优点:可以提高可复用性和可维护性,通过接口或抽象类可以约束类的变化行为,基于指定策略对变化行为进行封装

举例

  • 场景:现有一个原价书籍,需要一个打折价,这个时候不应该修改书籍接口,而应该扩展一个打折书籍类。
  • 正例:
/**
 * 书籍接口
 */
public interface Book {
    
    
    String getName();
    Double getPrice();
}

/**
 * 原价书籍
 */
public class ParityBook implements Book {
    
    
    private String name;
    private Double price;

    ParityBook(String name, Double price) {
    
    
        this.name = name;
        this.price = price;
    }
    @Override
    public String getName() {
    
    
        return this.name;
    }

    @Override
    public Double getPrice() {
    
    
        return this.price;
    }
}
/**
 * 打折后书籍
 */
public class DiscountBook extends ParityBook {
    
    
    DiscountBook(String name, Double price) {
    
    
        super(name, price);
    }
    @Override
    public Double getPrice() {
    
    
        double oldPrice = super.getPrice();
        return oldPrice * 0.8 ;
    }
}

/**
 * 测试类
 */
public class BookPriceDemo {
    
    
    public static void main(String[] args) {
    
    
        ParityBook parityBook = new DiscountBook("Java", 100.00);
        System.out.println(parityBook.getPrice());
    }
}
// ================================== 打印结果 ===================================
80.0
  • UML类图:
    开闭原则UML

依赖倒置原则

描述

  • 定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象
  • 抽象不应该依赖细节,细节应该依赖抽象
  • 针对接口编程,不要针对实现编程
  • 优点:以抽象为基础搭建的架构比以细节为基础的架构要稳定灵活。下层模块尽量都要有抽象类或接口,程序稳定性更好。变量的声明类型尽量是抽象类或接口,这样变量引用和实际对象之间存在一个过渡空间,可以减少类间的耦合性、提高系统稳定性,提高代码可读性和可维护性,可降低修改程序所造成的风险

举例

  • 场景:实现一个农场饲养的动物有哪些
  • 正例:
/**
 * 定义一个动物接口,以及Dog和Pig实现类
 */
public interface Animal {
    
    
    String getAnimalName();
}
public class Dog implements Animal {
    
    
    @Override
    public String getAnimalName() {
    
    
        return "哈士奇";
    }
}
public class Pig implements Animal {
    
    
    @Override
    public String getAnimalName() {
    
    
        return "佩奇";
    }
}
/**
 * 定义一个农场接口,声明接口方法breed,接收Animal接口参数,以及创建其实现类Farming
 */
public interface FarmFactory {
    
    
    void breed(Animal animal);
}
public class Farming implements FarmFactory {
    
    
    @Override
    public void breed(Animal animal) {
    
    
        System.out.println("农场饲养:" + animal.getAnimalName());
    }
}

/**
 * 创建农场类,调用breed方法,分别传入Animal子类
 */
public class FarmFactoryDemo {
    
    
    public static void main(String[] args) {
    
    
        FarmFactory farm = new Farming();
        farm.breed(new Dog());
        farm.breed(new Pig());
    }
}
// ================================== 打印结果 ===================================
农场饲养:哈士奇
农场饲养:佩奇
  • UML类图:可以发现,Farming和具体的实现是解耦的,和Animal接口是耦合的,以后扩展的鸡鸭鱼实现Animal接口,也不用和Farming耦合在一起。
    依赖倒置原则UML
  • 反例:将所有饲养的动物全部写在Farming类,会发现这个实现类需要经常修改,扩展性很差。典型的面向实现编程,非面向接口编程。
public class Farming {
    
    
    public void breedDog() {
    
    
        System.out.println("正在饲养:哈士奇");
    }
    public void breedPig() {
    
    
        System.out.println("正在饲养:佩奇");
    }
}

单一职责原则

描述

  • 定义:不要存在多于一个导致类变更的原因
  • 一个类、接口、方法只负责一项职责
  • 优点:降低类的复杂度、提高类的可读性,提高系统的可维护性、降低变更引起的风险

举例

  • 场景:定义动物的叫声
  • 反例:
/**
 * 一个动物声音类既有狗叫声,又有牛叫声
 */
public class AnimalVoice {
    
    
    public void dogVoice() {
    
    
        System.out.println("狗叫声:汪汪");
    }
    public void cowVoice() {
    
    
        System.out.println("牛叫声:哞哞");
    }
}
  • 正例:
/**
 * 直接将两个动物分开,保证职责的单一性(当然此处可以创建AnimalVoice接口,接口中声明声音方法,所有动物都实现该接口,扩展细节)
 */
 public class DogVoice {
    
    
    public void dogVoice() {
    
    
        System.out.println("狗叫声:汪汪");
    }
}
 public class CowVoice {
    
    
    public void cowVoice() {
    
    
        System.out.println("牛叫声:哞哞");
    }
}

接口隔离原则

描述

  • 定义:用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口
  • 一个类对一个类的依赖应该建立在最小的接口上
  • 建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少
  • 注意适度原则,一定要适度,接口中的方法也不应过少
  • 优点:符合高内聚低耦合的设计思想,从而使得类具有很好的可读性、可扩展性和可维护性

举例

  • 场景:实现博主(Admin)和读者(User)分别对博客拥有的权限
  • 正例:
/**
 * 管理员权限接口:增加、修改、删除
 */
public interface AdminBlog {
    
    
    Boolean insertBlog();
    Boolean updateBlog();
    Boolean deleteBlog();
}
/**
 * 读者权限接口:阅读
 */
public interface ReadBlog {
    
    
    String getBlog();
}
/**
 * 博主拥有博客的所有权限
 */
public class AdminUser implements ReadBlog, AdminBlog {
    
    
    @Override
    public Boolean insertBlog() {
    
    
        return null;
    }
    @Override
    public Boolean updateBlog() {
    
    
        return null;
    }
    @Override
    public Boolean deleteBlog() {
    
    
        return null;
    }
    @Override
    public String getBlog() {
    
    
        return null;
    }
}
/**
 * 读者只开放博客阅读接口
 */
public class Reader implements ReadBlog {
    
    
    @Override
    public String getBlog() {
    
    
        return null;
    }
}
  • UML类图:
    接口隔离原则UML
  • 反例:将所有博客权限都写在一个接口

迪米特原则

描述

  • 定义:一个对象对其他对象保持最少的了解,又叫最少知道原则
  • 强调降低类与类之间的耦合
  • 优点:降低类之间的耦合,强调只和朋友交流,不和陌生人说话。朋友即出现在成员变量、方法的输入、输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。

举例

  • 场景:老板命令组长帮他查一下一共有多少个课程
  • 反例:
/**
 * 课程类
 */
public class Course {
    
    
}
/**
 * 老板类,有个方法命令组长检查课程数量
 */
public class Boss {
    
    
    public void commandCheckNumber(TeamLeader teamLeader) {
    
    
        // 这里模拟数据库查出20个课程
        List<Course> courseList = new ArrayList<Course>();
        for (int i = 0; i < 20; i++) {
    
    
            courseList.add(new Course());
        }
        teamLeader.checkNumberOfCourses(courseList);
    }
}
/**
 * 组长类,打印课程数量
 */
public class TeamLeader {
    
    
    public void checkNumberOfCourses(List<Course> courseList){
    
    
        System.out.println("在线课程的数量是:" + courseList.size());
    }
}
/**
 * 测试类,老板命令组长检查课程数量
 */
public class Test {
    
    
    public static void main(String[] args) {
    
    
        Boss boss = new Boss();
        TeamLeader teamLeader = new TeamLeader();
        boss.commandCheckNumber(teamLeader);
    }
}
  • UML类图:
    迪米特原则反例UML
  • 说明:这里看起来能实现课程数量的查询,其实发现是老板查询的课程数量,然后命令组长说出(打印)这个数量,问题在于课程不该出现在老板命令方法中,而应该和组长有关系。
  • 正例:
/**
 * 老板只负责命令组长检查数量
 */
public class Boss {
    
    
    public void commandCheckNumber(TeamLeader teamLeader) {
    
    
        teamLeader.checkNumberOfCourses();
    }
}
/**
 * 组长负责数课程,然后打印数量
 */
public class TeamLeader {
    
    
    public void checkNumberOfCourses(){
    
    
        List<Course> courseList = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
    
    
            courseList.add(new Course());
        }
        System.out.println("在线课程的数量是:" + courseList.size());
    }
}
public class Test {
    
    
    public static void main(String[] args) {
    
    
        Boss boss = new Boss();
        TeamLeader teamLeader = new TeamLeader();
        boss.commandCheckNumber(teamLeader);
    }
}
  • UML类图:
    迪米特原则UML

里氏替换原则

描述

  • 定义:如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型
  • 里氏替换原则是对开闭原则的补充
  • 定义扩展:一个软件实体如果适用一个父类的话,那一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变
  • 引申意义:子类可以扩展父类的功能,但不能改变父类原有的功能
    • 含义一:子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
    • 含义二:子类中可以增加自己特有的方法
    • 含义三:当子类方法重载父类的方法时,方法的前置条件(即方法的入参)要比父类方法的输入参数更宽松
    • 含义四:当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或相等。
  • 优点:
    • 优点一:约束继承泛滥,开闭原则的一种体现
    • 优点二:加强程序的健壮性,同时变更时也可以做到非常好的兼容性,提高程序的维护性、扩展性。降低需求变更时引入的风险。

举例

  • 场景:
public class Calculate {
    
    
}
public class BaseCalculate extends Calculate {
    
    
    public int add(int a, int b) {
    
    
        return a + b;
    }
}
/**
 * 这里使用组合的方式完成计算
 */
public class BizCalculate extends Calculate {
    
    
    private BaseCalculate baseCalculate = new BaseCalculate();

    public int add(int a, int b) {
    
    
        return this.baseCalculate.add(a, b);
    }
}
public class CalculateDemo {
    
    
    public static void main(String[] args) {
    
    
        BizCalculate bizCalculate = new BizCalculate();
        System.out.println(bizCalculate.add(2, 3));
    }
}
  • UML类图:
    里氏替换原则UML

合成(组合)聚合原则

描述

  • 定义:尽量使用对象组合/聚合,而不是继承关系达到软件复用的目的
  • 聚合(has-A)和组合(contains-A)
  • 优点:可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少
  • 组合/聚合缺点:通过组合/聚合建造的系统会有较多的对象需要管理,比如A类里面有B类也有C类,也就是说管理的对象较多,而继承复用的优点是扩展性比较容易实现,因为继承父类,父类的所有功能通过继承都会自动进入子类,修改和扩展相对容易些
  • 继承的缺点:会破坏包装,因为继承会将父类的实现细节暴露给子类
  • 何时使用组合/聚合、继承:聚合是 has-A,组合是 contains-A,而继承是 is-A

举例

  • 场景:获取数据库连接
  • 反例:
public class DBConnection {
    
    
    public String getConnection() {
    
    
        return "MySQL数据库连接";
    }
}
public class ProductDao extends DBConnection {
    
    
    public void addProduct() {
    
    
        String conn = super.getConnection();
        System.out.println("使用" + conn + "增加产品");
    }
}
public class Test {
    
    
    public static void main(String[] args) {
    
    
        ProductDao productDao = new ProductDao();
        productDao.addProduct();
    }
}
  • 说明:代码很简单,Dao层获取父类Mysql连接进行操作,现在要增加第二个数据库连接,继承方式就不好实现了,所以可以改成组合方式
  • 正例:
public abstract class DBConnection {
    
    
    public abstract String getConnection();
}
public class MySQLConnection extends DBConnection {
    
    
    @Override
    public String getConnection() {
    
    
        return "MySQL数据库连接";
    }
}
/**
 * PostgreSQL数据库
 */
public class PostgreSQLConnection extends DBConnection {
    
    
    @Override
    public String getConnection() {
    
    
        return "PostgreSQL数据库连接";
    }
}
/**
 * 使用组合的方式
 */
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+"增加产品");
    }
}
public class Test {
    
    
    public static void main(String[] args) {
    
    
        ProductDao productDao = new ProductDao();
        productDao.setDbConnection(new PostgreSQLConnection());
        productDao.addProduct();
    }
}
  • UML类图:
    组合/聚合UML

说在最后

其实在实际开发过程中,讲究的是一个平衡,不应该只为了程序的扩展性而一味的去遵循这些设计原则,还要考虑人力、时间、成本、质量、Deadline,如果一开始就把扩展性做的特别完美的话,成本又上来了,所以不应该过度,在适当的场景去遵循设计原则,体现的是一个取舍的问题,就像有些设计模式可能遵循两样三样而破坏一样两样,最重要的是合适的业务场景,所以设计原则不是强行遵守的,而是要讲究一个度,讲究一个取舍。

猜你喜欢

转载自blog.csdn.net/qq_36221788/article/details/113957196
今日推荐