设计模式读书笔记01

软件建模与设计过程可以拆分**成需求分析、概要设计和详细设计(类图)**三个阶段。UML 规范包含了十多种模型图,常用的有 7 种:类图、序列图、组件图、部署图、用例图、状态图和活动图(UML中没有流程图,用这个代替)

类以及类之间的关系:。类之间有 6 种静态关系:关联、依赖、组合、聚合、继承、泛化。

主要是通过用例图来描述系统的功能与使用场景

对于关键的业务流程,可以通过活动图描述

程序员的差距一方面体现在编程能力、另一方面体现在程序设计方面,好的设计和坏的设计最大的差别就体现在应对需求变更的能力上

一个设计腐坏的例子,例如输入、输出。;各种输入设备、输出设备。----面向接口变成

我们在**开始设计的时候就需要考虑程序如何应对需求变更,并因此指导自己进行软件设计,在开发过程中,需要敏锐地察觉到哪些地方正在变得腐坏,然后用设计原则去判断问题是什么,再用设计模式去重构代码解决问题**。

软件设计的开(拓展)闭(关闭修改)原则:如何不修改代码却能实现需求变更?

开闭原则说:软件实体(模块、类、函数等等)应该对扩展是开放的,对修改是关闭的。当需求变更时拓展

以通过按钮拨号的电话,0~9,增加按钮类型时,,,似乎对 Button 类做任何的功能扩展,都要修改 Button 类

粗暴一点说,当我们在代码中看到 else 或者 switch/case 关键字的时候,基本可以判断违反开闭原则了
设计模式中很多模式其实都是用来解决软件的扩展性问题的,也是符合开闭原则的。我们用策略模式对上面的例子重新进行设计。
策略模式是一种行为模式,多个策略(拨号器、密码锁)实现同一个策略接口,ButtonService编程的时候 client 程序依赖策略接口,运行期根据不同上下文向 client 程序传入不同的策略实现。

利用策略模式来避免冗长的 if-else 或 switch 分支判断,别的模式也能移除分支判断。在工厂中查表法。策略模式实例,文件排序:内排序、外部排序、多线程外部排序、利用MapReduce多机排序。

策略模式的步骤:1. 将策略的定义分离出来。—复用。每种排序类都是无状态的–没有必要每次使用时创建一个新的对象,使用工厂模式封装。修改工厂,违背了开闭原则,此时通过反射读取被 annotation 标注的策略类

策略工厂类读取配置文件或者搜索被 annotation 标注的策略类,然后通过反射了动态地加载这些策略类、创建策略对象

适配器模式是一种结构模式,用于将两个不匹配的接口适配起来,使其能够正常工作一个按钮控制多个设备----观察者模式,监听者接口,一对多的对象依赖关系

所谓模板方法模式,就是在父类中用抽象方法定义计算的骨架和过程,而抽象方法的实现则留在子类中。

实现开闭原则的关键是抽象,就是接口。依赖接口–就可以随意对这个抽象接口进行拓展,这个时候不需要对现有代码进行任何修改,利用接口的多态性,通过增加一个新的实现该接口的类,就能完成需求变更。

当需求变更的时候,现在的设计能否不修改代码就可以实现功能的扩展?如果不是,那么就应该进一步使用其他的设计原则和设计模式去重新设计。
关键还是看场景

软件设计的依赖倒置原则:如何不依赖代码却可以复用它的功能?接口的所有权是被倒置的

高层模块不应该依赖低层模块,二者都应该依赖抽象。抽象不应该依赖具体实现,具体实现应该依赖抽象

依赖倒置原则的应用:1.JDBC,2.J2EE规范,不需要依赖Tomcat这种web容器。在框架设计时被用的很多,

利用依赖倒置的设计原则,每个高层模块都为它所需要的服务声明一个抽象接口,而低层模块则实现这些抽象接口,高层模块通过抽象接口使用低层模块而实现这一特性的前提就是应用程序必须实现框架的接口规范,比如实现 Servlet 接口
对具体类的继承是一种强依赖关系,维护的时候难以改变

可以把接口看作一组抽象的约定

软件设计的里氏替换原则(子类型必须能够替换掉它们的基类型):正方形可以继承长方形吗?

绝大多数设计模式其实都是利用多态的特性玩的把戏

通俗地说就是:子类型必须能够替换掉它们的基类型所有使用基类的地方,都应该可以用子类代替

实例,作为子类的白马可以替换掉基类马,但是小马不能替换马,因此小马继承马就不太合适了,违反了里氏替换原则

我们判断一个继承是否合理?会使用“IS A”进行判断,类 B 可以继承类 A,我们就说类 B IS A 类 A,比如白马 IS A 马,轿车 IS A 车。
子类不能比父类更严格,否则替换时会因为更严格的契约而失败,反例,在 JDK 中,类 Properties 继承自类 Hashtable,类 Stack 继承自 Vector。

实践中,当你继承一个父类仅仅是为了复用父类中的方法的时候,那么很有可能你离错误的继承已经不远了。一个类如果不是为了被继承而设计,那么最好就不要继承它。粗暴一点地说,如果不是抽象类或者接口,最好不要继承它。如果你确实需要使用一个类的方法,最好的办法是组合这个类而不是继承这个类,这就是人们通常说的组合优于继承

内聚性主要研究组成一个模块或者类的内部元素的功能相关性
一个类,应该只有一个引起它变化的原因。—单一职责,WEB框架的演进。

如何判断一个类的职责是否单一,就是看**这个类是否只有一个引起它变化的原因**。

Rectangle 类的设计就违反了单一职责原则。Rectangle 承担了两个职责,一个是几何形状的计算,一个是在屏幕上绘制图形。也就是说,Rectangle 类有两个引起它变化的原因,这种不必要的耦合不仅会导致科学计算应用程序庞大,而且当图形界面应用程序不得不修改 Rectangle 类的时候,还得重新编译几何计算应用程序。

软件设计的接口隔离原则:如何对类的调用者隐藏类的公有方法–拆分接口,实现多个接口
接口隔离原则说:不应该强迫用户依赖他们不需要的方法。

通过使用接口隔离原则,我们可以将一个实现类的不同方法包装在不同的接口中对外暴露。应用程序只需要依赖它们需要的方法,而不会看到不需要的方法。
我们开发的绝大多数软件都是用来解决现实问题的

软件建模比较知名的是 4+1 视图模型,即建模方法论。

针对具体的用例场景,领域,将上述 4 个视图关联起来,一方面从业务角度描述,功能流程如何完成,一方面从软件角度描述,相关组成部分如何互相依赖、调用。

领域驱动设计:业务逻辑围绕领域模型设计,主要是对象的设计,包含领域相关知识,而不是只有getter,setter等

如果你对自己要开发的**业务领域没有清晰的定义和边界,没有设计系统的领域模型,而仅仅跟着所谓的需求不断开发功能,一旦需求来自多个方面,就可能发生需求冲突**,这个需求可能是伪需求
领域模型是合并了行为和数据的领域的对象模型

如何用领域模型模式设计一个完整而复杂的系统,有没有完整的方法和过程指导整个系统的设计?领域驱动设计,即 DDD 就是用来解决这一问题的。

实体设计是 DDD 的核心所在,首先通过业务分析,识别出实体对象,然后通过相关的业务逻辑设计实体的属性和方法。这里最重要的,是要把握住实体的特征是什么,实体应该承担什么职责,不应该承担什么职责,分析的时候要放在业务场景和界限上下文中,而不是想当然地认为这样的实体就应该承担这样的角色。

设计模式的精髓在于对面向对象编程特性之一——多态的灵活应用,而多态正是面向对象编程的本质所在。
多态的好处:软件编程时的实现无关性,程序针对接口和抽象类编程,而不需要关心具体实现是什么
设计模式:模式是**可重复的解决方案**

装饰模式最大的特点是,通过类的构造函数传入一个同类对象,OR接口,也就是每个类实现的接口和构造函数传入的对象是同一个接口。设计模式是一个非常注重实践的编程技能。模板和策略

设计模式应用:编程框架中的设计模式

框架通常规定了一个软件的主体结构

当你设计一个框架的时候,你实际上是在设计一类软件的通用架构,并通过代码的方式实现出来。如果仅仅是提供功能接口供程序调用,是无法支撑起软件的架构的,也无法规范软件的结构。
Servlet实际上是一个接口。

JUnit 是一个 Java 单元测试框架,开发者只需要继承 JUnit 的 TestCase,开发自己的测试用例类,通过 JUnit 框架执行测试,就得到测试结果。
当我们从树的根节点遍历树,就可以执行所有这些测试用例。传统上进行树的遍历需要递归编程的,而使用组合模式,无需递归也可以遍历树

反应式编程框架设计:如何使程序调用不阻塞等待,立即响应?–异步–回调

软件设计的核心目标就是高内聚、低耦合

组件是软件复用和发布的最小粒度软件单元

The default value of the class path is “.”, meaning that only the current directory is searched.

为何说要多用组合少用继承?如何决定该用组合还是继承?

类的继承层次会越来越深、继承关系会越来越复杂,会导致:1.可读性差,搞清楚类的用途可能需要追溯父类,2.将父类的实现细节暴露给了子类,高度耦合,父类代码的修改,就会影响所有子类的逻辑。

public class Ostrich implements Tweetable, EggLayable {// 鸵鸟
    private TweetAbility tweetAbility = new TweetAbility(); // 组合
    private EggLayAbility eggLayAbility = new EggLayAbility(); // 组合
    //... 省略其他属性和方法...
    @Override
    public void tweet() {
        tweetAbility.tweet(); // 委托
    }
    @Override
    public void layEgg() {
        eggLayAbility.layEgg(); // 委托
    }
}

继承主要有三个作用:表示 is-a 关系支持多态特性代码复用

比如 is-a 关系,我们可以通过组合和接口的 has-a 关系来替代多态特性我们可以利用接口来实现代码复用我们可以通过组合和委托来实现。所以,从理论上讲,通过组合、接口、委托三个技术手段,我们完全可以替换掉继承,在项目中不用或者少用继承关系,特别是一些复杂的继承关系。
仅仅为了代码复用,生硬地抽象出一个父类出来,会影响到代码的可读性,做法:组合,即成员变量是另一个类。

Repository 层负责数据访问,Service 层负责业务逻辑,Controller 层负责暴露接口

public class UserBo {// 省略其他属性、get/set/construct 方法
    private Long id;
    private String name;
    private String cellphone;
}//是一个存储的数据结构,只包含数据,不包含任何业务逻辑,业务逻辑在UserService类中

像 UserBo 这样,只包含数据,不包含业务逻辑的类,就叫作贫血模型(Anemic Domain Model)。

在贫血模型中,数据和业务逻辑被分割到不同的类中。充血模型(Rich Domain Model)正好相反,数据和对应的业务逻辑被封装到同一个类中。因此,这种充血模型满足面向对象的封装特性,是典型的面向对象编程风格。

DDD是伴随着微服务而盛行的。微服务还有另外一个更加重要的工作,那就是针对公司的业务,合理地做微服务拆分。而领域驱动设计恰好就是用来指导划分服务的。所以,微服务加速了领域驱动设计的盛行。
做好领域驱动设计的关键是,看你对自己所做业务的熟悉程度,而并不是对领域驱动设计这个概念本身的掌握程度。即便你对领域驱动搞得再清楚,但是对业务不熟悉,也并不一定能做出合理的领域设计。所以,不要把领
域驱动设计当银弹,不要花太多的时间去过度地研究它

​ 实际上,基于充血模型的 DDD 开发模式实现的代码,也是按照 MVC 三层架构分层的。Controller 层还是负责暴露接口,Repository 层还是负责数据存取,Service 层负责核心业务逻辑。它跟基于贫血模型的传统开发模式的区别主要在 Service 层

我们先来回忆一下,**我们平时基于贫血模型的传统的开发模式,都是怎么实现一个功能需求的。**不夸张地讲,我们平时的开发,大部分都是 SQL 驱动(SQL-Driven)的开发模式,—长得差不多、区别很小的SQL语句满天飞。

在这种开发模式(DDD)下,我们需要事先理清楚所有的业务,定义领域模型所包含的属性和方法。领域模型相当于可复用的业务中间层。新功能需求的开发,都基于之前定义好的这些领域模型来完成,不会出现需求保证的情况

简单系统(不太关注可复用性)用贫血模型,复杂系统用,基于充血模型的 DDD 开发模式

因为交易流水有两个功能:一个是业务功能,比如,提供用户查询交易流水信息;另一个是非业务功能,保证数据的一致性。这里主要是指支付操作数据的一致性。

不涉及复杂业务概念,职责单一、功能通用。

并且将原来在 Service 类中的部分业务逻辑移动到 VirtualWallet 类(领域对象)中,让 Service 类的实现依赖 VirtualWallet 类。

理解OOP,我们就不难理解DDD:
DDD第一原则:将数据和操作结合。(贫血模型将数据和操作分离,违反OOP的原则。)
DDD第二原则:界限上下文。这是将“单一指责”应用于我们的领域模型。
DDD is nothing more than OOP applied to business models. DDD其实就是把OOP…

上一篇:Excel 单元格自定义格式,输入ddd.mmss格式的度分秒,转化显示成ddd mm ss格式


下一篇:领域驱动设计(DDD)在百度爱番番的实践