前言
为什么我们需要设计模式呢?我们日常的设计应该对手头的问题有针对性,同时对将来的问题也要有足够的通用性,同时我们也需要避免重复设计或者尽可能的减少重复设计。但是想以一下子就得到复用性和灵活性好的设计,其实是相当困难的。 而设计模式,就是将对面向对象软件的设计经验作为设计模式记录下来。
设计模式的四个基本要素
- 模式名称 一个助记的名称
- 问题 描述了应该在什么时候使用设计模式
- 解决方案 描述了设计的组成成分,它们之间的相互关系和各自的职责和协作方式
- 效果 描述了模式应用的效果以及使用模式应该权衡的问题
设计模式的三大分类
我们主要通过两个准则对各种设计模式进行分类:
- 目的准则 这个模式是用来完成什么工作的
- 范围准则 这个模式用于类还是用于对象的
面向对象的六大原则
- 单一功能原则 一个类只做一件事情
- 开闭原则 一个软件实体类,模块和函数应该对扩展开放,对修改关闭
- 里氏替换原则 子类可以扩展父类的功能,但不能改变父类原有的功能
- 依赖倒置原则 抽象不应该依赖细节,细节应该依赖于抽象,换一句话说,就是要针对接口编程,不要对实现编程
- 接口分离原则 系统解开耦合,从而容易重构,更改和重新部署
- 迪米特原则 一个类应该对自己需要耦合或者调用的类知道得最少,这有点类似于接口隔离原则中的最小接口的概念
Strategy 策略模式
意图
定义一系列的算法,把他们一个个封装起来,并且使他们可以互相替换。同时本模式可以使得算法可以独立于使用它的客户变化而变化
别名
Policy 政策模式
动机
当我们需要对一个正文流进行处理,需要使用许多不同的算法。这个时候,将这些算法那硬编码进使用它们的类当中是不可取的,原因如下:
- 客户程序异常庞大同时难以维护
- 不同的时候需要不同的算法,我们不想支持我们并不使用的算法
- 当其中某些算法是客户程序难以分割的算法的时候,增加新的算法或者改变现有的算法将十分困难
适用性
- 许多相关的类只是行为有不同 可以使用多个行为中的一个行为来配置一个类
- 需要使用一个算法的不同变体 可能会定义一些反映了不同时间、空间等因素权衡的算法,将这些变体实现为类层次
- 算法使用了客户不应该知道的数据 避免暴露负责的、与算法相关的数据结构
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类当中来代替这些条件语句
结构
参与者
- Strategy
- 定义所有支持的算法的公共接口。Context使用这个接口来调用某个ConcreteStrategy定义的算法
- ConcreteStrategy
- 以Strategy为策略是实现某具体的算法
- Context
- 用一个ConcreteStrategy对象来配置
- 维护一个对Strategy对象的引用
- 可定义一个接口来让Strategy访问它的数据
协作
- Strategy和Context相互作用以实现选定的算法
- Context将算法所需要的数据传递给Strategy
- Context也可以将自身作为一个参数传递给Strategy,Strategy就可以在需要的时候回调Context
- Context将客户的请求转发给它的Strategy。通常创建并传递一个ConcreteStrategy对象给Context。客户只需要和Context交互。
效果
优点
- 相关算法系列 Strategy类层次为Context定义了一系列可供重用的算法或者行为。有助于析取出这些算法的公共功能
- 一个替代继承的方法 可以使用继承生成Context的子类,给予它们不同的行为,但是这就会将行为硬编码到Context当中,将算法的实现和Context的实现混淆,使得Context难以理解、难以维护、难以扩展,还不能动态改变算法。将算法封装在独立的Strategy类中,使得我们可以独立于Context改变它,易于切换、易于理解、易于扩展
- 消除了一些条件语句 将不同行为堆砌在一个类当中的时候,很难避免使用条件语句来选择合适的行为。将行为封装成一个个独立的Strategy类中可以消除这些条件语句(含有许多条件语句的代码通常意味着需要使用策略模式)
- 实现的选择 可以为相同行为提供不同的实现,可以根据不同的时间、空间权衡取舍要求从不同的策略中进行选择
缺点
- 客户必须要了解不同的Strategy 本模式一个潜在的缺点,客户选择一个Strategy必须要知道这些Strategy有何不同,此时可能不得不向客户暴露实现。因此只有不同行为变体与客户相关的行为时,才需要使用Strategy
- Strategy和Context的通信开销 无论具体的ConcreteStrategy是简单还是负责,都共享Strategy定义的接口。因此很可能某一些简单的ConcreteStrategy可能不会使用所有通过这个接口传递给他们的信息。
- 增加了对象的数目 Strategy增加了一个应用中对象的数目。有时候可以将Strategy实现为可供各个Context共享的无状态对象来减少这一个开销。其余任何状态都由Context来维护
实现
- 定义Strategy接口和Context接口 必须传递所有实现需要的数据,也可以将Context自身作为参数进行传递
- 将Strategy作为模板参数 (Go里没有,这就不展开了)
- 让Strategy对象成为可选的 访问前先判断是否存在,如果存在,就使用它;如果没有,就执行缺省的行为
代码示例
- 回调事件处理
相关模式
Flyweight(享元模式): Strategy对象通常是很好的轻量级对象
总结
- 代码基于接口而非基于实现编程
- 为系统提供新的扩展点,定义一类算法类,需要扩展的时候,只需要增加对应策略的算法实现即可
- 控制代码复杂度,添加新策略的时候,最小化、集中化代码改动,减少引入 bug 的风险
- 能够消除大量成片的if-else和switch代码
- 策略模式能够起到解耦的作用,主要是三个方面 解耦策略的定义、创建、和使用
设计的思想和原则比设计模式更加普适和重要