02 软件的设计原理
08丨软件设计的方法论:软件为什么要建模?
- 软件开发中的两个客观存在:
- 要解决的领域问题,对客观的领域问题的抽象就是各种功能及其关系、各种模型对象及其关系、各种业务处理流程
- 最终开发出来的软件系统
- 4+1视图模型(建模方法论):
- 逻辑视图: 描述软件的功能逻辑,组成模块、模块中的类及其依赖关系
- 开发视图: 包括系统架构层面的层次划分,包的管理,依赖的系统和第三方程序包
- 过程视图: 描述程序运行期的进程、线程、对象实例,以及与此相关的并发、同步、通信等问题
- 物理视图: 描述软件如何安装并部署到物理的服务上,以及不同服务器之间如何关联、通信
- 场景视图: 针对具体用例场景,关联上述四个视图,一方面从业务角度描述功能流程如何完成,一方面从软件角度描述相关组成部分如何互相依赖、调用
09丨软件设计实践:如何使用UML完成一个设计文档?
- 类图: 最常见的UML图形,用于描述类的特性和类之间的静态关系
- 类的六种静态关系: 关联、依赖、组合、聚合、继承、泛化
- 序列图: 用于描述参与者之间的动态调用关系
- 组件图: 组件是比类粒度更大的设计元素,一个组件中通常包含多个类,主要用于描述组件之间的静态关系(可以使用组件序列图,以组件作为参与者,描述组件之间的消息调用关系)
- 部署图: 描述软件系统的最终部署情况
- 用例图: 主要用在需求分析阶段,通过反映用户和软件的交互,描述系统的功能需求,可配以文字说明形成需求文档
- 状态图: 用于展示单个对象生命周期的状态变迁
- 活动图: 主要用来描述过程逻辑和业务流程
泳道: 根据活动的范围,将活动根据领域、系统和角色等划分到不同的泳道中,使流程边界更加清晰
10丨软件设计的目的:糟糕的程序员比优秀的程序员差在哪里?
- 略
11丨软件设计的开闭原则:如何不修改代码却能实现需求变更?
- 开闭原则: 软件实体(模块、类、函数等等)应该对扩展是开放的,对修改是关闭的,通俗的说,软件功能可以被扩展,但是软件实体不可以被修改
- 例子(按钮和拨号器):
- 策略模式: 一种行为模式,多个策略实现同一个策略接口,编程是client程序依赖策略接口,运行期根据不同上下文向client程序传入不同的策略实现
Button 和 Dailer 之间增加了一个抽象接口 ButtonServer,Button 依赖ButtonServer,而 Dailer 实现 ButtonServer - 适配器模式: 一种结构模式,用于将两个不匹配的接口适配起来,使其能够正常工作
不要由 Dailer 类直接实现 ButtonServer 接口,而是增加两个适配器DigitButtonDailerAdepter、SendButtonDailerAdepter,由适配器实现ButtonServer接口,在适配器的 buttonPressed 方法中调用 Dailer 的enterDigit 方法和 dail 方法,而Dailer 类保持不变,Dailer 类实现开闭原则 - 观察者模式: 一种行为模式,解决一对多的对象依赖关系,将被观察者对象的行为通知到多个观察者,也就是监听者对象
ButtonServer 被改名为 ButtonListener,表示这是一个监听者接口,其实这个改名不重要,仅仅是为了便于识别。因为接口方法 buttonPressed 不变,ButtonListener 和ButtonServer 本质上是一样的 - 模板方法: 在父类中用抽象方法定义计算的骨架和过程,而抽象方法的实现则留在子类中
在 Button 类中定义抽象方法 onPress,具体类型的按钮,比如 SendButton 实现这个方法。Button 类中增加抽象方法 onPress,并在 press 方法中调用 onPress 方法
- 策略模式: 一种行为模式,多个策略实现同一个策略接口,编程是client程序依赖策略接口,运行期根据不同上下文向client程序传入不同的策略实现
- 实现开闭原则的关键是抽象,开闭原则可以说是软件设计原则的原则,是软件设计的核心原则,其他的设计原则更偏向于技术性,具有技术性的指导意义,而开闭原则是方向性的
12丨软件设计的依赖倒置原则:如何不依赖代码却可以复用它的功能?
- 依赖倒置原则:
- 高层模块不应该依赖低层模块,二者都应该依赖抽象
- 抽象不应该依赖具体实现,具体实现应该依赖抽象
- 依赖倒置的关键是接口所有权的倒置
- 习惯上的层次依赖
利用依赖倒置的设计原则,每个高层模块都为它所需要的服务声明一个抽象接口,而低层模块则实现这些抽象接口,高层模块通过抽象接口使用低层模块的功能
- 习惯上的层次依赖
- 贫血模型(Anemic Domain Model): 将数据与操作分离,只包含数据,不包含业务逻辑的类,破坏了面向对象的封装特性,是一种典型的面向过程的编程风格
- 充血模型(Rich Domain Model): 数据和对应业务封装到同一个类中,满足面向对象的的封装特性,是一种典型的面向对象的编程风格,DDD开发模式便基于充血模型
13丨软件设计的里氏替换原则:正方形可以继承长方形吗?
- 里氏替换原则: 子类型必须能够替换掉它们的基类型,也就是程序中所有使用基类的地方,都应该可以用子类代替
- 子类不能比父类更严格,通常来说,子类比父类契约更严格,都是违反里氏替换原则的
- 当继承是为了复用父类方法时,可能就离错误继承不远了,如果一个类不是为了被继承而设计的,就最好不要继承它,粗暴一点的讲,如果不是抽象类或者接口,最好就不要继承它
- 如果确实需要使用一个类的方法,最好的办法是组合这个类而不是继承这个类,也就是组合优于继承
14丨软件设计的单一职责原则:为什么说一个类文件打开最好不要超过一屏?
- 软件设计的两个基本准则: 低耦合和高内聚
- 单一职责原则: 设计类的时候,我们应该把强相关的元素放在一起,弱相关的元素放在类的外面,保持类的高内聚行,一个类应该只有一个引起它变化的原因
15丨软件设计的接口隔离原则:如何对类的调用者隐藏类的公有方法?
- 接口隔离原则: 不应该强迫用户依赖他们不需要的方法,可以将一个实现类的不同方法包装在不同的接口中对外暴露
16丨设计模式基础:不会灵活应用设计模式,你就没有掌握面向对象编程
- 面向对象编程的本质是多态
17丨设计模式应用:编程框架中的设计模式
- 框架是对某一类架构方案可复用的设计和实现,框架一般应该满足开闭原则和依赖倒置原则
- Web容器中的设计模式:
- Web容器: 主要使用了策略模式,多个策略实现同一个策略接口,编程时Tomcat依赖策略接口,在运行期根据不同上下文装载不同的策略实现
- HttpServlet: 实现了Servlet接口,使用了模板方法,在父类中用抽象方法定义计算的骨架和过程,抽象方法的实现则留在子类中
- JUnit中的设计模式:
- 使用了模板方法模式,测试用例的方法执行顺序被固定在JUnit框架的模板方法里
18丨反应式编程框架设计:如何使程序调用不阻塞等待,立即响应?
- 略
19丨组件设计原则:组件的边界在哪里?
- 略
20丨领域驱动设计:35岁的程序员应该写什么样的代码?
- 领域模型模式和事务脚本模式
- 六边形架构