设计模式-行为型模式
观察者模式 发布/订阅模式
观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被完成业务的更新。
一个对象(被观察者,subject)的状态发生改变,所有的依赖对象(观察者,Observer)都将得到通知(以此开始执行提前定义好的业务)。
subject, n. 主体,被观察者,(实验中的)观察对象(主体) v.受
可以用spring、guava实现。
菜鸟教程中的实现方式要点:
- subject保存观察者的列表List<Observer>,并提供向列表中add操作的公有方法;
- Observer中定义Subject成员变量;
- 在构造函数中通过两步来完成对观察者的绑定:
this.subject = subject; this.subject.attach(this);
- 在构造函数中通过两步来完成对观察者的绑定:
- 利用了java方法的引用传参(浅拷贝,传递了对象的引用,因此可以直接操作到对应的观察者对象),将观察者放进了Subject的列表中;
- 在setter方法中、改变属性后,调用Subject#NotifyAll方法,来遍历所有观察者,并调用他们的相应方法。
通常情况下,在几次开发后,Subject相当于“老代码”,对它要注意开闭原则,不要随便修改;因此,上面这种设计的好处在于,日后添加新的
- 这种一对多关系是否和orm有相通之处?
同步阻塞方式的观察者模式
直接可以通过spring
的ApplicationContextAware
,初始化观察者列表,然后用户注册成功,通知观察者即可。
虽然解决了开闭原则的问题,但同步阻塞的话,一步异常可能全部失败。
异步非阻塞的观察者模式
另起线程 or 消息队列
发布订阅
登录注册应该是最常见的业务场景了
假如把注册后的自动登录、通知等关联业务都放在注册的方法内,那么将来增加业务(如发新人优惠券、限时活动)也需要修改注册方法的代码,违反开闭原则。(也违反单一职责?)
如果相关业务太多,可以改成异步的方式,通过消息队列实现发布、订阅
生产者消费者也是一种发布、订阅?
监听器Listener也算是一种
mq那个解耦的叫发布订阅模式,和观察者有区别的。观察者是穷举一个集合遍历的?
策略模式
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。
在策略模式中,我们创建表示各种策略的对象
和一个行为随着策略对象改变而改变的 context 对象
。策略对象改变 context 对象的执行算法。
可以解决if-else太多的问题
策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的的客户。
策略模式针对一组算法,将每一个算法封装到实现共同接口的不同独立的类中,从而使得它们可以相互替换。
- 一个接口或者抽象类,里面两个方法(一个方法匹配类型,一个可替换的逻辑实现方法)
- 不同策略的差异化实现(就是说,不同策略的实现类)
**关键代码:**实现同一个接口。
**注意事项:**如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
多类型处理器策略模式的结构如图所示。
**策略模式:**是一种行为型模式,能定义一系列算法,并将每种算法分别放入独立的类中,从而使算法的对象能够互相替换。
**场景介绍:**在MyBatis 处理JDBC 执行后返回的结果时,需要按照不同的类型获取对应的值,这样就可以避免大量的if 判断。所以,这里基于TypeHandler 接口对每个参数类型分别做了自己的策略实现。
**同类场景:**PooledDataSource、UnpooledDataSource、BatchExecutor、ResuseExecutor、SimpleExector、CachingExecutor、LongTypeHandler、StringTypeHandler 和DateTypeHandler。
案例:
使用策略模式消除冗长的if-else|记一次smart-auto重构总结
是否用策略模式代替if else,个人觉得:决定的关键不是看你有if else的数量是否过多,而且看你if else的条件数是否是动态的,如果你的条件数量是恒定不变的,哪怕有20个,都不建议改为策略模式
举个两个场景:
如果你需要判断的条件就是定死的条件,比如一周的7天之类的,这种条件的数量就是恒定不变的,后面无论你的业务怎么更新,一周还是只有7天,这种情况下,直接用if else或者switch更直观更合适。
如果你的条件是动态的,比如传入的命令字符串,那你最好还是用策略模式更新下你的if else,因为这些你就不用每次要支持新的命令而去更好原逻辑的代码
责任链模式
责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。
在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
**意图:**避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
**主要解决:**职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
**何时使用:**在处理消息时有许多处理步骤
**如何解决:**拦截的类都实现统一接口。然后根据这一接口拦截?
**关键代码:**Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。
- 这个聚合自己是什么意思?怎么实现?
应用实例:
已有应用:
2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,jsp servlet 的 Filter;
可以应用的场景:
不同级别的日志记录;参数校验
优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。
缺点: 1、不能保证请求一定被接收。?什么意思,,,怎么就不能接收了, 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。因为拆解了步骤所以性能有损耗么,, 3、可能不容易观察运行时的特征,有碍于除错。
使用场景: 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求。
比如审批流程,有一系列处理步骤,而且每一步都有可能打回;
责任链模式与if…else…相比,他的耦合性要低一些,因为它把条件判定都分散到了各个处理类中,并且这些处理类的优先处理顺序可以随意设定。责任链模式也有缺点,这与if…else…语句的缺点是一样的,那就是在找到正确的处理类之前,所有的判定条件都要被执行一遍,当责任链比较长时,性能问题比较严重。
- 看来还有能有索引一样直接找到条件的模式,,不知道那种模式能不能改造if-else
实现案例
一个抽象类,几个实现类。
抽象类结构类似链表,有next指向下一个,setNext()方法设置下一项;
demo里都是一个一个setNext,hhh,,
另一个demo
├── impl
│ ├── Level1AuthLink.java
│ ├── Level2AuthLink.java
│ └── Level3AuthLink.java
├── AuthInfo.java
└── AuthLink.java
其他技巧
抽象类可以使用泛型
模板模式
SQL 执行模板模式如图9所示。
图9
**模板模式:**是一种行为型模式,在超类中定义了一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤。场景介绍:存在一系列可被标准定义的流程,并且流程的步骤大部分采用通用逻辑,只有一小部分是需要子类实现的,通常采用模板模式来定义这个标准的流程。就像MyBatis 的BaseExecutor 就是一个用于定义模板模式的抽象类,在这个类中把查询、修改的操作都定义为一套标准的流程。
**同类场景:**BaseExecutor、SimpleExecutor 和BaseTypeHandler。
迭代器模式
拆解字段解析实现的结构如图11所示。
迭代器模式是一种行为型模式,能在不暴露集合底层表现形式的情况下遍历集合中的所有元素。
场景介绍:PropertyTokenizer 用于MyBatis 的MetaObject 反射工具包下,用来解析对象关系的迭代操作。这个类在MyBatis 中使用得非常频繁,包括解析数据源配置信息并填充到数据源类上,同时参数的解析、对象的设置都会使用这个类。
迭代器是一种用于分离数据结构和遍历方式的设计模式,多在非线性数据结构定义不同遍历方式时使用(例如可以为一个二叉树提供 dfs 和 bfs 两种迭代方式,而这两种迭代方式的实现可以和二叉树自身实现分离)。
状态模式 State Pattern
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。
我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
通常context对象就是我们的实体,如订单等;它拥有一个表示状态和状态相关操作的成员变量对象。不同的状态实现统一的状态父类,作为实体的成员变量。
因为有些行为是和某种状态绑定的
在调用该状态不该进行的方法的时候抛出异常。避免了写一大堆if-else.直接用然后catch异常就行?
意图:允许对象在内部状态发生改变时改变它的行为
理解为,允许对象的行为依赖于他的状态,自动随状态更改而更改。
主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
状态模式和命令模式一样,也可以用于消除 if…else 等条件选择语句。(主要是先判断状态再做某种操作的形况吧)
何时使用:代码中包含大量与对象状态有关的条件语句。
如何解决:将各种具体的状态抽象出来,以一层父类管理。
关键代码:通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。实现类的方法有不同的功能,覆盖接口中的方法。
优点:
1、封装了转换规则。
2、枚举可能的状态,在枚举状态之前需要确定状态种类。
3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点:
1、状态模式的使用必然会增加系统类和对象的个数。
2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码*(看下哪里负责状态转换)*,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。
应用实例:
if-else判断订单的状态(有这步骤?好吧我们的创建订单中间环节确实有)然后做某种操作
注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。
- 初始意见:context的行为方法调用状态对象的切换方法
- 似乎是从状态的角度看流程控制,,状态机的思想?会有状态太多的NP问题吗? - 思想理解:根据OOP之类的编程思想,context作为实体,具有“行为”和“属性”;当我们讨论“状态”这一属性的时候,正如之前分析,行为导致状态切换,状态影响可以进行的行为(如未付款订单有被付款行为没有发货行为,该行为导致转换为已付款状态,然后才可以有发货行为)
- why5个?
- 写法的争论
- 菜鸟案例:用状态对象的doAction方法反过来去控制context的状态
- 一种改进:把状态对象改变context状态的方法包装进context的行为方法;以此达到仍是实体的方法改变控制状态改变,不需要倒反天罡、显示得用状态对象来改变实体;
- 这一类写法的要点在于,把“设置状态”这一步放在状态对象里。抽象出的状态对象,其方法其实就是实体的所有状态有关的行为,统一在状态对象父类/接口中管理,在行为中实现改变状态操作;而区别在于,要不要通过实体包装状态行为方法来实现实体的行为方法,这将导致实体能否通过调用自己的行为方法 来调用状态对象中抽象出的行为方法 来实现状态限制行为、行为改变状态的流程。
- 父类/接口中统一的方法,在不同状态的状态对象中实现不同(多为允许或不允许),这不正好是一种多“态”?
- 其他人案例:
- 在实体的行为方法中设置自己的状态,而不是在状态对象的表示状态的方法中
- 在上述基础上,在调用方法前判断是否为某一状态
- 这样的话,把“是否进行某一行为”这一依赖于状态的控制放在实体的行为中,是不是导致“状态决控制行为”被脱钩了?需要手动在成员方法里维护?
- 菜鸟案例:用状态对象的doAction方法反过来去控制context的状态
观点:
状态即使抽象出来,也只是computer的属性,方便扩展不写switch而已,状态的变化肯定是源于computer自身的动作的,用oo的思想来看,调用者对于状态state的api应该是不可见的。(后半句不太懂)
new computer时,会设置一个默认状态,我们的例子应该是关机中。然后关机中的computer只能有一个有效操作,就是开机,至于其他两种操作是设置为无响应,还是log提示无法操作,那也随便了。截止到这句,我的理解是“关机中”状态调用休眠方法,这种当前状态调用不该进行操作的行为,期望的正确效果应该是无效操作(第一种写法达到的,在关机状态类中实现休眠方法就是什么也不做;实际应用也可以考虑抛出异常之类的),而不是第二种方法那样,改变自身状态去做这件事,或是第二种-进一步的写法那样,加一大堆判断自身状态,然后考虑要不要做这件事:这不是回归原始的一堆判断了嘛!
再考虑面向过程语言通过函数改变用户状态(实际上现在应用中很多OOP语言也用面向过程思想实现的业务,比如service层不是默认处理自己实体的业务而是接收参数做一些实际上是面向过程的操作)最常想到的方法还是if-else判断传出对象的状态进行操作吧最后,状态切换本身就是每个状态的内禀属性,关机中通过开机操作切换到开机中的代码,应该写在CloseState里,至于说耦合问题,这些状态本身互相之间就是有联系的,怎么可能互相独立?
看到这里进一步理解了。关机-》开机操作,原先面向过程是if-else判断状态是否关机->开机操作,现在是可以直接调用实体对象的开机操作,如果状态对象是关机,他其中的状态实现类会自己展示出这种状态面对开机应有的操作的(该状态实现类重写方法)。本身正确的开机操作就因该是由“关机”这一状态来维护的嘛!
状态之间不是解耦的,所以才说状态模式对“开闭原则”的支持并不好。
状态模式中类有状态,状态的修改会改变整个类行为。
策略没有状态,策略的选择由客户端决定。
- 还不是很懂“开闭原则”,这里的意思是,本来类内部的内容修补修改都由类控制,但现在状态类被改了也会导致由实体控制的行为被修改?那不是所有有内置对象的都会遇到的问题?还是说要看是不是保留的共有方法会被从外部通过改内置对象来修改?
- 其余思考:如果有一状态可以从两种状态达到,是不是应该注意下两张状态实现两遍的重复代码问题?感觉有种一对多事件的味道,也许我应该学学事件驱动编程面对多个影响事件应该怎么处理。面对受多个事件影响又有什么区别。