title: 设计思想及原则tags: 设计思想
categories: 设计模式
cover: >-
https://image.imxyu.cn/file/design_idea.webp
sticky: 5
swiper_index: 5
description: 一切模式的基础
abbrlink: f016668f
date: 2021-10-13 21:28:43

设计思想及原则

设计思想

对接口编程而不是对实现编程

接口位于上层(抽象层),面向接口编程而不是面向实现编程,可以将接口和实现分离,封装不稳定的实现,暴露稳定的接口,这样当实现类发生变化时,上游的代码基本上不需要改动,以此来降低耦合性。

因为我们依赖的实现类可能需要经常变化

  1. 如果某个功能有多种实现方式,我们应该把他们抽象出来,通过接口实现或者抽象类的方式继承(例如上传图片到阿里云/私有云,这里就有多种方式,此时应该抽离出一个ImageStore存储的接口,让阿里云和私有云都实现这个接口,然后我们使用调用接口的方法)

  2. 有了接口,上层直接调用接口,我们底层实现类可以随意更换,而不会影响上层的代码(例如service中注入dao接口,而dao接口实现类我们可以通过Mybatis或者hibernate(任意一个实现dao接口的实现类)操作数据库,我们都不需要修改service中的代码)

多用组合少用继承

继承的缺点:

  1. 当我们在父类进行修改,对下面的子类都会产生影响(例如鸟要添加fly方法,可能子类鸵鸟等不具备。会出现很多问题)

  2. java继承只能是单继承,很不灵活

接口的缺点:但是如果我们使用接口的话,很多类都要去实现这个接口,可能有很多重复的实现。没办法复用

此时我们就可以使用组合的方式,先抽象出一个接口/抽象类(例如Flyable接口),然后让一个类去继承/实现它(FlyAbility 类实现了Flyable接口),我们通过在燕子(会飞的鸟)中通过组合的方式来调用FlyAbility.fly()的方式,采用组合的方式这样我们既避免了继承,又可以不用实现接口去重复写相应的实现。

设计模式总览

设计思想及原则 - 图1

组件的生命周期

设计思想及原则 - 图2

当我们制作一个功能时:

  1. 首先需要定义一个类:这就对应图中的组件定义(可以在结构型模式中找哪种模式适合我们定义一个类)
  2. 定义好类之后,我们需要创建对象:对应图组件的创建(在创建型模式中找)
  3. 接下来对象的使用:对应图组件的服役行为型模式中找)
  4. java会自动帮我们回收对象,此时对应组件的销毁。

设计的7大原则

经典设计原则包括,SOLID、KISS、YAGNI、DRY、LOD 等

不一定要遵循所有,需要根据实际情况来实行

单一职责原则(SRP)

一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分

每个类只负责自己的事情,而不是定义一个超级大类,所有事情都调用这个类的方法

开闭原则(OCP)

软件实体应当对扩展开放,对修改关闭

当我们需要一个新的功能时,扩展新类而不是在原类上修改。

遇到需要扩展的,应该看能否先抽象出相应的接口,然后让新的类去实现相应的接口,而不是直接在类原逻辑中修改代码

里式替换原则(LSP)

子类对象(object of subtype/derived class)能够替换程序(program)中父类对象(object of base/parent class)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。

  • 继承父类而不是去改变父类,子类继承父类的功能,而不去修改原有的方 法。
  • 应该在父类提供扩展的方法上修改 。这样可以保证了在其他地方调用B的时候,同时可以替换为B的父类,逻辑不会改变

违背里式替换原则:

  1. 子类违背父类声明要实现的功能

父类中提供的 sortOrdersByAmount() 订单排序函数,是按照金额从小到大来给订单排序的,而子类重写这个 sortOrdersByAmount() 订单排序函数之后,是按照创建日期来给订单排序的。那子类的设计就违背里式替换原则。

  1. 子类违背父类对输入、输出、异常的约定
  • 在父类中,某个函数约定:运行出错的时候返回 null;获取数据为空的时候返回空集合(empty collection)。而子类重载函数之后,实现变了,运行出错返回异常(exception),获取不到数据返回 null。那子类的设计就违背里式替换原则。

  • 在父类中,某个函数约定,输入数据可以是任意整数,但子类实现的时候,只允许输入数据是正整数,负数就抛出,也就是说,子类对输入的数据的校验比父类更加严格,那子类的设计就违背了里式替换原则。

  • 在父类中,某个函数约定,只会抛出 ArgumentNullException 异常,那子类的设计实现中只允许抛出 ArgumentNullException 异常,任何其他异常的抛出,都会导致子类违背里式替换原则。

  1. 子类违背父类注释中所罗列的任何特殊说明

接口隔离原则(ISP)

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

各个类建立自己的专用接口,而不是建立万能接口

依赖倒置原则(DIP)

控制反转(IOC)

  1. 这里的“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程可以通过框架来控制。流程的控制权从程序员“反转”到了框架。

  2. 框架提供了一个可扩展的代码骨架,用来组装对象、管理整个执行流程。程序员利用框架进行开发的时候,只需要往预留的扩展点上,添加跟自己业务相关的代码,就可以利用框架来驱动整个程序流程的执行。

依赖注入(DI)

不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。(参数一般相应的接口类型,我们可以通过多态传入相应的实现类)

依赖注入框架

  1. 虽然我们采取了依赖注入之后在实现类中不用new对象,但是我们在外部还是需要new对象来传入,当类多了我们进行类的创建和依赖注入会很复杂

  2. 使用依赖注入框架我们只需要通过依赖注入框架提供的扩展点,简单配置一下所有需要创建的类对象、类与类之间的依赖关系,就可以实现由框架来自动创建对象、管理对象的生命周期。(比如spring一个@Autowired注解就可以帮我们将对象通过依赖注入的方式装载到另一个类中,不需要我们手动去new对象)

依赖反转原则

高层模块和低层模块应该通过抽象来相互依赖。也就是:面向接口编程,而不是面向实现类编程

我们要调用实现类的接口,而不是直接使用实现类

(例如Controller中注册使用Service接口,Service中使用Dao接口, 这样无论Dao底层使用Mybatis还是Hibernate实现类操作数据库都不影响上层的访问)

迪米特法则

最少知识原则,只与你的直接朋友交谈,不跟“陌生人”说话

  • 无需直接交互的两个类,如果需要交互,使用中间者
  • 过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低

合成复用原则

软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现

优先组合,其次继承