设计是有限度的,不能无限的考虑未来的变更情况,否则会陷入射击的泥潭而不能自拔
面向对象思想设计原则
- 单一职责原则(高内聚,低耦合)
- 每个类(也适用于方法)应该只有一个职责,对外只能提供一种功能,而引起类变化的原因应该只有一个
+ 职责和变化原因都是不可度量的,因项目和环境而异
+ 会引起类的剧增
- Why
- 可维护性:仅在一个模块或类中需要进行更改。
- Why
- 最佳实践:接口一定要做到单一职责,类尽量做到单一职责
- 应用Curly’s Law
- Why
- 每个类(也适用于方法)应该只有一个职责,对外只能提供一种功能,而引起类变化的原因应该只有一个
+ 职责和变化原因都是不可度量的,因项目和环境而异
+ 会引起类的剧增
- 开闭原则
- 一个对象对扩展开放,对修改关闭,对类的改动是通过增加代码进行的,而不是修改现有代码,一个实体可以允许它的行为被修改而不改变它的源代码。
- Why
- 通过最小化对现有代码的更改来提高可维护性和稳定性。
- How
- 编写可以扩展的类(与可以修改的类相对)。
- 仅暴露需要更改的移动部件,隐藏其他所有部件。
- 借助于抽象和多态,即把可能变化的内容抽象出来,从而使抽象的部分是相对稳定的,而具体的实现则是可以改变和扩展
- 抽象约束
- 通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现再接口或抽象类中不存在的public方法
- 参数类型、引用对象尽量使用接口或抽象类
- 抽象层尽量保持稳定,一旦确定即不允许修改
- 封装变化
- 将相同的变化封装到同一个接口或抽象类中
- 将不同的变化封装到不同的接口或抽象类中的,不应该有两个不同的变化出现再同一个接口或抽象类中
- 里氏替换原则
- 在任何父类出现的地方都可以用它的子类来替代,程序中的对象应与其子类型的实例一起替换,而不更改该程序的正确性。 + 子类必须完全实现父类的方法 + 子类可以有自己的个性 + 覆盖或实现父类的方法时输入参数可以被放大(不建议适用,子类不应对父类方法不应该重载,违背父类意图) * 子类中方法的前置条件必须与超类中的方法的前置条件相同或者更宽松(子类重载时参数要求要比父类方法宽泛) + 覆写或实现父类的方法时输出结果可以被缩小
- 最佳实践:避免子类个性化,如果必要个性化,重新提取一个父类
- 依赖倒置原则
- 要依赖于抽象,不要依赖于具体实现 + 编程的时候针对抽象类或者接口编程,而不是针对具体实现编程。 + 高层模块不应该依赖低层模块,两者都应该依赖其抽象 * 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口和抽象类产生的 + 抽象不应该依赖细节 * 接口或抽象类不依赖于实现类 + 细节应该依赖抽象 * 实现类依赖接口或抽象类
- 最佳实践 + 每个类尽量都有接口或抽象类,或者兼备 + 变量的表面类型尽量是接口或抽象类 * Util类一般不需要,如果使用类的clone方法,必须使用实现类,这是JDK提供的一个规范 + 尽量避免从具体类派生 + 尽量不要覆写基类的方法 + 结合里氏替换原则使用
- 接口分离原则
- 将多功能的接口减少到多个更小、更具体的客户端特定接口。应该依赖接口而不是依赖实现。
- Why
- 如果类实现不需要的方法,调用方需要知道该类的方法实现。
- 例如,如果类实现一个引发异常的方法,那么调用方需要知道此方法实际上不应该被调用。
- 如果类实现不需要的方法,调用方需要知道该类的方法实现。
- 不应该强迫程序依赖它们不需要使用的方法 + 一个接口不需要提供太多的行为,一个接口应该只提供一种对外的功能,不应该把所有的操作都封装到一个接口中 + 客户端不应该依赖它不需要的接口 + 类间的依赖关系应该建立在最小的接口上
- 最佳实践:
- 类不应该实现违反单一责任原则的方法。
- 一个接口只服务与一个子模块和业务逻辑
- 通过业务逻辑压缩接口中的pulic方法
- 已经被污染了的接口,尽量去修改,若变更的风险很大,则采用适配器模式去转化处理
- 类不应该实现违反单一责任原则的方法。
- 迪米特原则(最少知识原则)
- 一个对象应当对其他对象尽可能少的了解 + 降低各个对象之间的耦合,提高系统的可维护性。在模块之间应该只通过接口编程,而不理会模块的内部工作原理,它可以使各个模块耦合度降到最低,促进软件的复用
- 最佳实践: + 是否可以减少public方法和属性,修改未private、package-private、protected等访问权限,是否可以加上final关键字等 + 如果一个方法在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置再本类中 + 对象的方法只能调用以下方法: + 对象本身。 + 该方法的参数。 + 在方法中创建的任何对象。 + 对象的任何直接属性/字段。
- KISS (Keep It Simple Stupid)
- 如果保持简单而不是复杂,大多数系统都能发挥最佳性能。
- Why
- 更少的代码花费更少的时间来编写,具有更少的错误,并且更容易修改。
- 简单就是极致成熟。
- 而不是没有什么可补充的代码不是完美的代码,不可精简的代码才是完美的代码,
- YAGNI (you aren’t gonna need it)
- 在必要之前不要实施。不要做过度设计和实现。
- Why
- 任何仅用于明天需要的功能的工作意味着从当前迭代需要完成的功能中失去工作量。
- 这会导致代码膨胀,软件变得更大、更复杂。
- How
- 当你真正需要的时候总是去实现它们,而不是当你只是预见到你需要它们的时候。
- Do The Simplest Thing That Could Possibly Work
- 使用最简单的事情解决问题
- Why
- 如果我们只是致力于解决真正的问题,那么对抗真正问题的进展就会最大化。
- How
- Ask yourself: “What is the simplest thing that could possibly work?”
- Separation of Concerns
- 关注点分离。分离关注点是将计算机程序分离成不同的部分的设计原则。这样每个部分都解决了一个单独的关注点。
- 例如,应用程序的业务逻辑是一个关注点,而用户界面则是另一个关注点。更改用户界面不应要求更改业务逻辑,反之亦然。
- 即使不完全可能,它仍然是我有效排序一个人思想的唯一可用技术。“将注意力集中在某些方面”:它并不意味着忽视其他方面,它只是公正地从这个方面来看,另一方面是不相关的。
- Why
- 简化软件应用程序的开发和维护。
- 当关注点被很好地分开时,单独的部分可以被重用,也可以独立地开发和更新。
- How
- 将程序功能分解为尽可能少重叠的单独模块。
- 关注点分离。分离关注点是将计算机程序分离成不同的部分的设计原则。这样每个部分都解决了一个单独的关注点。
- Keep Things DRY
- 每一段知识都必须在系统中具有单一,明确,权威的表示。
- 程序中的每个重要功能都应该在源代码中的一个位置实现。 在通过不同的代码片段执行类似的功能的情况下,通过抽象出变化的部分将它们组合成一个通常是有益的。
- Why
- 重复(无意中或有目的重复)可能导致维护噩梦、分解错误和逻辑矛盾。
- 对系统中任何单个元素的修改不需要更改其他逻辑上无关的元素。
- 此外,逻辑上相关的元素都可以预测和一致地进行更改,从而保持同步。
- How
- 只在一个地方放置业务规则,长表达式,if语句,数学公式,元数据等。
- 确定系统中使用的每一条知识的单一,权威来源,然后使用该源生成该知识的适用实例(代码,文档,测试等)。
- 应用三次法则
- 每一段知识都必须在系统中具有单一,明确,权威的表示。
- Code For The Maintainer
- 编码时考虑可维护性
- Why
- 维护是任何项目中最昂贵的阶段。
- How
- 成为维护者。
- 编码的时候想着最终维护你代码的人是一个暴力的精神病患者,他知道你住在哪里。
- 注意编写代码和注释,以便一个初学者拿起你的代码乐于去学习和阅读。
- 不要让维护者思考。
- 应用Principle of least astonishment(最少惊讶原则)
- If a necessary feature has a high astonishment factor, it may be necessary to redesign the feature.
- 如果必要的特征具有意料之外的表现,则可能需要重新设计该特征
- Avoid Premature Optimization
- 避免过早优化,理解代码是否足够成熟是至关重要的
- 程序员浪费了大量时间来思考或担心程序中非关键部分的速度,而这些效率尝试实际上在考虑调试和维护时会产生很大的负面影响。
- 我们应该忘记小的效率,大约97%的场景:过早的优化是所有邪恶的根源。
- 但是,我们不应该放弃那个关键的3%的优化机会。
- Why
- 事先不知道瓶颈在哪里。
- 优化后,可能更难以阅读并因此维护。
- How
- 当“正确”也意味着“快”。
- 在您需要之前不要进行优化,并且只有在分析后才发现瓶颈才能优化。
- 避免过早优化,理解代码是否足够成熟是至关重要的
- Minimise Coupling
- 最小化耦合
- 模块/组件之间的耦合是它们相互依赖的程度; 较低的耦合效果更好。
- Why
- 一个模块的更改通常会强制其他模块中的更改产生连锁反应。
- 由于模块间依赖性增加,模块的组装可能需要更多的努力和/或时间。
- 特定模块可能更难以重用和/或测试,因为必须包含依赖模块。
- 开发人员可能害怕更改代码,因为他们不确定可能会受到什么影响。
- How
- 消除,最小化并减少必要关系的复杂性。
- 通过隐藏实现细节,减少了耦合。
- 应用迪米特法则
- 最小化耦合
- Composition Over Inheritance
- 组合重于继承
- Why
- 类之间的耦合较少。
- 使用继承,子类很容易做出假设,并破坏LSP(里氏替换原则(Liskov Substitution principle))。
- How
- 测试LSP(可替代性)以决定何时继承。
- “has a” (or “uses a”)使用组合, “is a” 实用继承。
- Orthogonality(正交)
- 正交的基本思想是,在概念上不相关的事物不应在系统中相关。
- 它与简单性相关;设计正交越多,异常越少。这使得使用编程语言学习、读取和编写程序变得更加容易。正交特征的含义与上下文无关;关键参数是对称性和一致性。
- Robustness Principle(鲁棒性原则)
- 在你所做的事情上要保守,在你从别人那里接受的东西上要开明。
- 传参要符合规范,在你从别人那里接受的参数时必须通过制造错误的或不可预期的输入来验证程序的健壮性。
- Why
- 为了能够发展服务,您需要确保提供商可以进行更改以支持新需求,同时最大限度地减少对现有客户端的破坏。
- How
- 将命令或数据发送到其他计算机(或同一台计算机上的其他程序)的代码应完全符合规范,但接收输入的代码应处理不符合要求的输入,只要含义明确。
- 在你所做的事情上要保守,在你从别人那里接受的东西上要开明。
- Inversion of Control(控制反转)
- Why
- 控制反转用于增加程序的模块性并使其可扩展。
- 将任务的执行与实现分离。
- 将模块专注于它的设计任务。
- 使模块免于假设其他系统如何做他们所做的事情,而是依赖合同。
- 更换模块时防止副作用。
- How
- 使用工厂模式
- Using Service Locator pattern 使用服务定位器模式
- 使用依赖注入
- Using contextualized lookup 使用上下文查找
- 使用模板方法模式
- 使用策略模式
- Why
- Maximise Cohesion(高内聚)
- 单个模块/组件的凝聚力是其职责构成有意义单元的程度; 更高的凝聚力更好。
- Why
- 模块越多理解模块的难度增加。
- 维护系统的难度增加,因为域中的逻辑更改会影响多个模块,并一个模块中的更改需要更改相关模块。
- 由于大多数应用程序不需要模块提供的部分操作集,因此重用模块的难度增加。
- How
- 相关的功能组合在一个类中。
- Hide Implementation Details(隐藏实现细节)
- 软件模块通过提供接口来隐藏信息(即实现细节),而不泄漏任何不必要的信息。
- Why
- 当实现发生变化时,客户端使用的接口不必更改。
- How
- 最小化类和成员的可访问性。
- 不要公开公开成员数据。
- 避免将私有实现细节放入类的接口中。
- 减少耦合以隐藏更多实现细节。
- Curly’s Law(科里定律)
- Curly定律是为任何特定的代码选择一个明确定义的目标:做一件事。
- 一个变量应该代表一样东西,并且只能代表一样东西。它不应该在一种情况下代表这个意思,而在另一种情况下又代表不同的意思。它不能一次代表两样东西。它应该只有一个含义,并且自始至终只有一个含义。
- Curly定律是为任何特定的代码选择一个明确定义的目标:做一件事。
- Encapsulate What Changes(封装更改)
- 一个好的设计可识别最有可能更改的热点,并将它们封装在 API 后面。当预期更改发生时,修改将保持本地。
- Why
- 在发生更改时最小化所需的修改
- How
- 封装API背后不同的概念
- 可能将不同的概念分成它自己的模块
- Boy-Scout Rule(童子军原则)
- 童子军有一条规则:“让营地比你刚来时更干净。”如果看到地上有垃圾,不管是谁扔的,都要清理。这样你就有意地为下一批来宿营的人改善了环境。事实上,这条规则的最初说法是“让世界比你刚来时更美好”,出自罗伯特·贝登堡,童子军之父。尝试让模块在提交时比你检出时更干净。
- Why
- 在对现有代码库进行更改时,代码质量往往会降低,从而累积技术债务。 按照boyscout规则,我们应该考虑每次提交的质量。 技术债务受到连续重构的抵制,无论多么小。
- How
- 每次提交都要确保它不会降低代码库质量。
- 每当有人看到一些不太清晰的代码时,他们应该抓住机会在那里修复它。
- Command Query Separation(命令查询分离)
- 命令查询分离原则指出,每种方法应是执行操作的命令,或者是将数据返回给调用方而不是两者行的调用。提出问题不应修改答案。应用这一原理后,程序员可以更有信心地编写代码。查询方法可以按任意顺序在任何地方使用,因为它们不会更改状态。使用命令时,必须更加小心。
- Why
- 通过将方法明确地分离为查询和命令,程序员可以在不知道每个方法的实现细节的情况下进行编码。
- How
- 将每个方法实现为查询或命令
- 将命名约定应用于方法名称,该方法名称暗示该方法是查询还是命令
设计原则
- 找出程序中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混合在一起
- 针对接口编程,而不是针实现编程
- 多用组合,少用继承
- 为了交互对象之前的松耦合设计而努力
- 松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低
- 类应该对扩展开放,对修改关闭
- 要依赖抽象,不要依赖具体类(依赖倒置原则)
- 变量不可以持有具体类的引用
- 不要让类派生自具体类
- 不要覆盖基类中已实现的方法
- 最少知识原则(墨忒耳法则)
- 减少对象之间的交互(减少对象之间耦合)
- 只调用以下范围的方法
- 该对象本身
- 被当做方法的参数而传递进来的对象
- 此方法所创建或实例化的任何对象
- 对象的任何组件(被实例变量的引用)
- 会增加封装用于和其他对象交互,复杂度和开发时间上升,并降低了运行时性能
- 好莱坞原则
- 高层组件对低层组件的依赖方式是:别调用我们,我们会调用你
- 一个类应该只有一个引起变化的原因,尽量让每个类保持单一职责
- 当一个模块或一个类被设计成只支持一组相关的功能时,我们说他具有高内聚,反之,当被设计成支持一组不相关的功能时,我说他具有低内聚
复用机制
- 继承
- 白箱复用
- 破坏了封装性
- 父类子类依赖紧密,父类改变会影响子类
- 对象组合
- 黑箱复用
- 不破坏封装性
- 依赖接口而解耦
- 有助于保持每个类被封装,并集中在单个任务上
- 动态的参数化的软件比静态的软件难于理解
- 运行时效率低(主要)
- 委托是对象组合的特例
优先使用对象组合,而不是类继承
设计模式总览
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
模式是在某情境下,针对某问题的某种解决方案
设计模式分类
- 创建型模式(对象的创建)(5个)
- 简单工厂模式(静态工厂方法模式)(Simple Factory)
- 简单工厂模式不是GoF总结出来的23种设计模式之一
- 工厂方法模式(Factory Method)
- 注重整体对象的创建方法
- 抽象工厂模式(Abstract Factory)
- 创建具有不同分类的产品组合
- 建造者模式(Builder)
- 旨在一步步的精确构造出一个复杂对象
- 原型模式(Prototype)
- 单例模式(Singleton)
- Converter
- Dependency Injection
- Factory Kit
- MonoState
- Multiton
- Object Mother
- Object Pool
- Property
- Step Builder
- Value Object
- 简单工厂模式(静态工厂方法模式)(Simple Factory)
- 结构型模式(处理类或对象的组合)(7个)
- Facade(外观模式)
- 封装和隔离内部业务
- Adapter/Wrapper(适配器模式)
- 不同对象之间相互转换
- Proxy(代理模式)
- 对代理对象具有控制权
- Decorator(装饰模式)
- 对类功能的增强或减弱
- Bridge(桥接模式)
- 解耦抽象与实现
- 两个维度的排列组合问题
- Composite(组合模式)
- 屏蔽部分与整体间的差别
- Flyweight(享元模式)
- Abstract Document
- Ambassador
- Delegation
- Event Aggregator
- Servant
- Service Locator
- Marker Interface
- Module
- Role Object
- Twin
- Facade(外观模式)
- 行为型模式 Behavioral(类或对象的交互和职责分配)(11个)
- 模版方法模式(Template Method)
- 观察者模式(Observer)
- 状态模式(State)
- 封装状态变化,暴漏行为
- 职责链模式(Chain of Responsibility)
- 命令模式(Command)
- 使用命令将调用者和执行者解耦
- 访问者模式(Visitor)
- 策略模式(Strategy)
- 封装算法,实现算法自由替换
- 备忘录模式(Memento)
- 迭代器模式(Iterator)
- 解释器模式(Interpreter)
- 中介者模式(Mediator)
- 协调同事类之间关系
- Acyclic Visitor
- Bytecode
- Extension objects
- Feature Toggle
- Intercepting Filter
- Null Object
- Pipeline
- Priority Queue Pattern
- Specification
- Throttling
- Type-object
- Caching
- Circuit Breaker
- Data Locality
- Dirty Flag
- Double Buffer
- Game Loop
- Leader Election
- Poison Pill
- Retry
- Sharding
- Spatial Partition
- Subclass Sandbox
- Update Method
- Architectural
- Api Gateway
- Aggregator Microservices
- CQRS
- Data Bus
- Data Transfer Object
- Event Driven Architecture
- Event Sourcing
- Hexagonal Architecture
- Layers
- Naked Objects
- Partial Response
- Serverless
- Service Layer
- Unit Of Work
- Business Tier
- Business Delegate
- Concurrency
- Async Method Invocation
- Balking
- Double Checked Locking
- Event Queue
- Event-based Asynchronous
- Guarded Suspension
- Half-Sync/Half-Async
- Mutex
- Producer Consumer
- Promise
- Queue based load leveling
- Reactor
- Saga
- Reader Writer Lock
- Semaphore
- Thread Pool
- Commander
- Master-Worker
- Integration
- EIP Aggregator
- EIP Message Channel
- EIP Publish Subscribe
- EIP Splitter
- EIP Wire Tap
- Tolerant Reader
- Presentation Tier
- Data Access Object
- Data Mapper
- Repository
- Presentation Tier
- Flux
- Front Controller
- Model-View-Controller
- Model-View-Presenter
- Testing
- Page Object
- Functional
- Collection Pipeline
- Fluent Interface
- Monad
- Idiom
- Callback
- Combinator
- Double Dispatch
- Execute Around
- Lazy Loading
- Mute Idiom
- Private Class Data
- Resource Acquisition Is Initialization
- Thread Local Storage
创建型 | 结构性 | 行为型 | |
---|---|---|---|
用于类 | 工厂方法 | 适配器 (类) | 解释器 |
模板方法 | |||
用于对象 | 抽象工厂 | 适配器(对象) | 责任链 |
创建者 | 桥接 | 命令 | |
原型 | 组合 | 迭代器 | |
单例 | 装饰 | 中介者 | |
外观 | 备忘录 | ||
享元 | 观察者 | ||
代理 | 状态 | ||
策略 | |||
访问者 |
- 类模式用户处理类和子类之间的关系,这些关系通过继承建立,是静态的,编译时就确定下来了
- 对象模式处理对象之间的关系,这些关系具有动态性,运行时可变化
如何选用设计模式
- 考虑设计模式是怎样解决问题的
- 浏览模式的意图部分
- 研究模式怎样互相关联
- 研究目的相似的模式
- 检查重新设计的原因
- 考虑设计中哪些是可以改变的
目的 | 设计模式 | 可变的方面 |
---|---|---|
创建 | Abstract Factory | 产品对象家族 |
Builder | 创建一个组合对象 | |
Factory Method | 被实例化的子类 | |
Prototype | 被实例化的类 | |
Singleton | 一个类的唯一实例 | |
结构 | Adapter | 对象的接口 |
Bridge | 对象的实现 | |
Composite | 一个对象的结构和组成 | |
Decorator | 一个对象的职责,不生成子类 | |
Facade | 一个子系统的接口 | |
Flyweight | 对象的存储开销 | |
Proxy | 如何访问一个对象;对象的位置 | |
行为 | Chain of Responsibility | 满足一个请求的对象 |
Command | 何时、怎样满足一个请求 | |
Interpreter | 一个语言的问法及解释 | |
Iterator | 如何编译、访问一个聚合的各元素 | |
Mediator | 对象间怎样交互、和谁交互 | |
Memento | 一个对象中那些私有信息存放在对象之外,什么时候存储 | |
Observer | 多个多想依赖于另外一个对象,而这些对象又如何保持一致 | |
State | 对象的状态 | |
Strategy | 算法 | |
Template Method | 算法中的某些步骤 | |
Visitor | 某些可作用于一个(组)对象上的操作,但不修改这些对象的类 |
如何使用设计模式
- 大致浏览一遍模式
- 回头去研究结构部分、参与者部分和写作部分
- 看代码示例部分、看看这个模式代码形式的具体例子
- 选择模式参与者的名字,使他们自应用上下文中有意义
- 定义类
- 定义模式中专用于应用的操作名称
- 实现执行模式中责任和写作的操作
设计模式详解
简单工厂模式(静态工厂方法模式)
所有工厂模式都用来封装对象的创建
简单工厂模式又叫静态工厂方法模式,它定义一个具体的工厂类负责创建一些类的实例
- 优点
- 客户端不需要在负责对象的创建,从而明确了各个类的职责
- 缺点
- 这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护
public class AnimalFactory {
private AnimalFactory() {
}
// public static Dog createDog() {
// return new Dog();
// }
//
// public static Cat createCat() {
// return new Cat();
// }
public static Animal createAnimal(String type) {
if ("dog".equals(type)) {
return new Dog();
} else if ("cat".equals(type)) {
return new Cat();
} else {
return null;
}
}
}
Factory Method/Virtual Constructor(工厂方法模式)
意图
定义用于创建对象的接口,但让子类决定实例化哪个类。Factory方法允许类将实例化延迟到子类。
它提供了一种将实例化逻辑委托给子类的方法。 在基于类的编程中,factory方法模式是一种创建模式,它使用factory方法来处理创建对象的问题,而不必指定要创建的对象的确切类。 这是通过调用在接口中指定并由子类实现的工厂方法来创建对象,或者在基类中实现并可选地由派生类重写,而不是通过调用构造函数来实现的。
适用性
- 类不能预期它必须创建的对象的类
- 类希望其子类指定它创建的对象
- 类将责任委托给几个helper子类中的一个,并且您希望本地化哪个helper子类是委托的知识
工厂方法模式定义了创建对象的接口,但由子类决定要实例化的是哪一个,工厂方法让类把实例化推迟到子类
- 优点
- 客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性
- 缺点
- 需要额外的编写代码,增加了工作量
案例
- java.util.Calendar
- java.util.ResourceBundle
- java.text.NumberFormat
- java.nio.charset.Charset
- java.net.URLStreamHandlerFactory
- java.util.EnumSet
- javax.xml.bind.JAXBContext
- Design Patterns: Elements of Reusable Object-Oriented Software
public abstract class AbstractFactory {
public abstract <T extends Product> T createProduct(Class<T> clazz);
}
public class ConcreteFactory extends AbstractFactory {
@Override
public <T extends Product> T createProduct(Class<T> clazz) {
try {
return (T) Class.forName(clazz.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
public abstract class Product {
public void method1() {
System.out.println("method1");
}
public abstract void method2();
}
public class ConcreteProduct1 extends Product {
@Override
public void method2() {
System.out.println("method2");
}
}
AbstractFactory abstractFactory = new ConcreteFactory();
ConcreteProduct1 product = abstractFactory.createProduct(ConcreteProduct1.class);
product.method1();
product.method2();
/**
* Weapon interface.
*/
public interface Weapon {
WeaponType getWeaponType();
}
/**
* WeaponType enumeration.
*/
public enum WeaponType {
SHORT_SWORD("short sword"), SPEAR("spear"), AXE("axe"), UNDEFINED("");
private String title;
WeaponType(String title) {
this.title = title;
}
@Override
public String toString() {
return title;
}
}
/**
* ElfWeapon.
*/
public class ElfWeapon implements Weapon {
private WeaponType weaponType;
public ElfWeapon(WeaponType weaponType) {
this.weaponType = weaponType;
}
@Override
public String toString() {
return "Elven " + weaponType;
}
@Override
public WeaponType getWeaponType() {
return weaponType;
}
}
/**
* OrcWeapon.
*/
public class OrcWeapon implements Weapon {
private WeaponType weaponType;
public OrcWeapon(WeaponType weaponType) {
this.weaponType = weaponType;
}
@Override
public String toString() {
return "Orcish " + weaponType;
}
@Override
public WeaponType getWeaponType() {
return weaponType;
}
}
/**
* The interface containing method for producing objects.
*/
public interface Blacksmith {
Weapon manufactureWeapon(WeaponType weaponType);
}
/**
* Concrete subclass for creating new objects.
*/
public class ElfBlacksmith implements Blacksmith {
private static Map<WeaponType, ElfWeapon> ELFARSENAL;
static {
ELFARSENAL = new HashMap<>(WeaponType.values().length);
for (WeaponType type : WeaponType.values()) {
ELFARSENAL.put(type, new ElfWeapon(type));
}
}
@Override
public Weapon manufactureWeapon(WeaponType weaponType) {
return ELFARSENAL.get(weaponType);
}
}
/**
* Concrete subclass for creating new objects.
*/
public class OrcBlacksmith implements Blacksmith {
private static Map<WeaponType, OrcWeapon> ORCARSENAL;
static {
ORCARSENAL = new HashMap<>(WeaponType.values().length);
for (WeaponType type : WeaponType.values()) {
ORCARSENAL.put(type, new OrcWeapon(type));
}
}
@Override
public Weapon manufactureWeapon(WeaponType weaponType) {
return ORCARSENAL.get(weaponType);
}
}
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
private final Blacksmith blacksmith;
/**
* Creates an instance of <code>App</code> which will use <code>blacksmith</code> to manufacture
* the weapons for war.
* <code>App</code> is unaware which concrete implementation of {@link Blacksmith} it is using.
* The decision of which blacksmith implementation to use may depend on configuration, or
* the type of rival in war.
*
* @param blacksmith a non-null implementation of blacksmith
*/
public App(Blacksmith blacksmith) {
this.blacksmith = blacksmith;
}
/**
* Program entry point.
*
* @param args command line args
*/
public static void main(String[] args) {
// Lets go to war with Orc weapons
App app = new App(new OrcBlacksmith());
app.manufactureWeapons();
// Lets go to war with Elf weapons
app = new App(new ElfBlacksmith());
app.manufactureWeapons();
}
private void manufactureWeapons() {
Weapon weapon;
weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR);
LOGGER.info(weapon.toString());
weapon = blacksmith.manufactureWeapon(WeaponType.AXE);
LOGGER.info(weapon.toString());
}
}
Abstract Factory/Kit(抽象工厂模式)
意图
提供一个接口,用于创建相关或从属对象的族,而不指定其具体类。
工厂的工厂;将单独的但相关的/依赖的工厂组合在一起而不指定其具体类别的工厂。 抽象工厂模式提供了一种方法来封装一组具有公共主题的单个工厂,而不指定它们的具体类。
适用性
- 一个系统应该独立于它的产品是如何创建、组合和表示的
- 一个系统应该配置多个产品系列中的一个
- 相关产品对象的族被设计为一起使用,您需要强制执行此约束
- 你想提供一个产品类库,你想展示的只是它们的接口,而不是它们的实现
- 依赖项的生存期在概念上比使用者的生存期短。
- 您需要一个运行时值来构造特定的依赖项
- 您要决定在运行时从族中调用哪个产品。
- 在解析依赖项之前,需要提供一个或多个仅在运行时已知的参数。
- 当你需要产品之间的一致性时
- 在向程序添加新产品或产品系列时,不希望更改现有代码。
- java中的依赖项注入隐藏了服务类依赖项,这些依赖项可能导致在编译时捕获的运行时错误。
- 虽然在创建预定义对象时模式很好,但是添加新对象可能很有挑战性。
- 代码可能会变得比应该的更复杂,因为许多新的接口和类都是随模式一起引入的。
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类
- 有N个产品族,在抽象工厂类中就应该有N个创建方法
- 有M个产品等级,就应该有M个实现工厂类,在每个实现工厂中,实现不同产品族的生产任务
- 产品族扩展困难,而产品等级扩展容易
案例
- Abstract Factory Pattern Tutorial
- javax.xml.parsers.DocumentBuilderFactory
- javax.xml.transform.TransformerFactory
- javax.xml.xpath.XPathFactory
- Design Patterns: Elements of Reusable Object-Oriented Software
public interface AbstractFactory {
public abstract Product createProductA();
public abstract Product createProductB();
}
public interface Product {
public abstract void method();
}
public class Product1Factory implements AbstractFactory {
@Override
public Product createProductA() {
return new ConcreteProduct1();
}
@Override
public Product createProductB() {
return new ConcreteProduct2();
}
}
public class Product2Factory implements AbstractFactory {
@Override
public Product createProductA() {
return new ConcreteProduct3();
}
@Override
public Product createProductB() {
return new ConcreteProduct4();
}
}
AbstractFactory f = new ConcreteProduct1Factory();
Product a = f.createProductA();
a.method();
f = new ConcreteProduct2Factory();
a = f.createProductB();
a.method();
工厂方法模式和抽象工厂模式
- 工厂方法模式通过子类创建对象,抽象工厂通过实现类(对象组合)创建对象
- 抽象工厂的方法常以工厂方法模式实现
- 抽象工厂和工厂方法用于实例化具体类的解耦
- 抽象工厂用于创建产品家族和想让制造的相关产品集合起来
所有的工厂模式都是用来封装对象的创建
简单工厂,虽然不是真正的设计模式,但仍不失为一个简单的方法,可以将客户程序从具体类解耦
工厂方法使用继承:把对象的创建委托给子类,子类实现工厂方法来创建对象
抽象工厂使用组合:对象的创建被实现在工厂接口所暴漏出来的方法中
所有工厂模式都通过减少应用程序和具体类之间的依赖促进松耦合
工厂方法允许类将实例化延迟早子类进行
抽象工厂创建相关的对象家族,而不需要依赖他们的具体类
依赖倒置原则,知道我们避免依赖具体类型,而要尽量依赖抽象
工厂是很有威力的技巧,帮助我们针对抽象编程,而不要针对具体编程
Builder(创建者模式/生成器模式)
意图
将复杂对象的构造与其表示分离,以便相同的构造过程可以创建不同的表示。
允许您创建不同风格的对象,同时避免构造函数污染。当一个对象可能有多种类型时很有用。或者当创建一个对象涉及很多步骤时。 builder模式是一种对象创建软件设计模式,旨在找到伸缩构造函数反模式的解决方案。
适用性
- 创建复杂对象的算法应该独立于组成对象的部分以及它们是如何组装的
- 构造过程必须允许对构造的对象进行不同的表示
创建者模式封装一个产品的构造过程,并允许按步骤构造
将一个复杂对象的创建与他的表示分离,使得同样的构建过程可以创建不同的表示
- 优点:
- 将一个复杂对象的创建过程封装起来
- 允许通过多个步骤来创建,并且可以改变过程(这和只有一个步骤的工厂模式不同)
- 向客户隐藏产品内部的实现
- 产品的实现可以被替换,因为客户只看到一个抽象的接口
- 缺点:
- 采用创建者模式创建对象的客户,需要具备更多的领域知识
案例
- java.lang.StringBuilder
- java.nio.ByteBuffer as well as similar buffers such as FloatBuffer, IntBuffer and so on.
- java.lang.StringBuffer
- All implementations of java.lang.Appendable
- Apache Camel builders
- Apache Commons Option.Builder
- Design Patterns: Elements of Reusable Object-Oriented Software
- Effective Java (2nd Edition)
public interface CarBuilder {
CarBuilder buildWheel();
CarBuilder buildEngine();
CarBuilder buildSeats(int number);
CarBuilder buildContainer();
Car getCar();
}
public class Car {
List<String> config = new ArrayList<>();
public void addConfig(String config) {
this.config.add(config);
}
@Override
public String toString() {
return config.stream().reduce("", (x, y) -> x.concat(y).concat("\r\n"));
}
}
public class TrunkBuilder implements CarBuilder {
private Car car = new Car();
@Override
public CarBuilder buildWheel() {
car.addConfig("car has a big wheel");
return this;
}
@Override
public CarBuilder buildEngine() {
car.addConfig("car has a little engine");
return this;
}
@Override
public CarBuilder buildSeats(int number) {
car.addConfig(MessageFormat.format("car has a {0} seats", number));
return this;
}
@Override
public CarBuilder buildContainer() {
car.addConfig("car has a big containers");
return this;
}
@Override
public Car getCar() {
return car;
}
}
Car car = new TrunkBuilder().buildContainer()
.buildEngine()
.buildSeats(3)
.buildWheel()
.getCar();
System.out.println(car);
car = new TrunkBuilder()
.buildEngine()
.buildWheel()
.getCar();
System.out.println(car);
Prototype(原型模式)
意图
指定要使用原型实例创建的对象的类型,并通过复制此原型创建新对象。
通过克隆来创建基于现有对象的对象。 原型模式是软件开发中一种创造性的设计模式。当要创建的对象类型由一个原型实例确定时,使用该实例,该实例被克隆以生成新对象。
适用性
当系统应独立于其产品的创建、组合和表示方式时,使用原型模式;以及
- 当要实例化的类在运行时指定时,例如,通过动态加载
- 避免构建与产品的类层次结构平行的工厂类层次结构
- 当类的实例只能有几个不同的状态组合中的一个时。可能更方便的做法是安装相应数量的原型并克隆它们,而不是每次都以适当的状态手动实例化类
- 当对象创建比克隆昂贵时
在创建给定类的实例的过程很昂贵或复杂时,使用原型模式
原型模式常用来代替抽象工厂
- 用途:
- 在一个复杂的类层次中,当系统不惜从其中的许多类型创建新对象时,可以考虑原型
- 优点:
- 向客户隐藏制造新实例的复杂性
- 提供让客户能够产生位置类型对象的选项
- 在某些环境下,复制对象比创建新对象更有效
- 性能优良
- 缺点:
- 对象复制有时相当复杂
- 逃避了构造函数的约束(也可做优点)
- 注意事项:
- 构造函数不会被执行
- 深拷贝和浅拷贝问题
- clone和final冲突(手动拷贝时无法赋值,下面的方式无视)
案例
public interface Prototype {
Object clone();
}
public class ConcretePrototype implements Prototype, Cloneable, Serializable {
private static final long serialVersionUID = 7231405363797237036L;
@Override
public Object clone() {
// 使用json序列化
// Gson gson = new Gson();
// return gson.fromJson(gson.toJson(this), this.getClass());
// 使用流
try (
//将对象写到流里
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
) {
oos.writeObject(this);
try (//从流里读回来
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
) {
return ois.readObject();
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
public class PrototypeManager {
// Singleton implements
private Map<String, Prototype> prototypeMap = new HashMap<>();
public void addPrototype(String name, Prototype prototype) {
prototypeMap.put(name, prototype);
}
public void removePrototype(String name) {
prototypeMap.remove(name);
}
public Prototype getPrototype(String name) {
return (Prototype) prototypeMap.get(name).clone();
}
}
public class Client {
public static void main(String[] args) {
PrototypeManager prototypeManager = new PrototypeManager();
prototypeManager.addPrototype("first",new ConcretePrototype());
Prototype first = prototypeManager.getPrototype("first");
}
}
Singleton(单例模式)
意图
确保一个类只有一个实例,并提供一个全局访问点
单例模式就是要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供。
在软件工程中,单例模式是一种软件设计模式,它将类的实例化限制在一个对象上。当需要一个对象来协调整个系统中的操作时,这非常有用。
适用性
- 一个类必须只有一个实例,并且客户端必须可以从已知的访问点访问它
- 当唯一的实例应该通过子类化来扩展时,客户端应该能够使用扩展实例而不修改其代码
- 通过控制它们自己的创建和生命周期来违反单一责任原则(SRP)。
- 鼓励使用全局共享实例,该实例可防止释放此对象使用的对象和资源。
- 创建紧密耦合的代码。Singleton的Client变得难以测试。
-
几乎不可能子类化一个单例。
- 优点
- 在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
- 可以延迟实例化0
- 缺点
- 没有抽象层,因此扩展很难。
- 职责过重,在一定程序上违背了单一职责
单例模式确保程序中一个类最多只有一个实例
单例模式也提供访问这个实例的全局点
在Java中实现单例模式需要私有的狗仔器、一个静态方法和一个静态变量
确定在性能和资源上的限制,然后小心的选择适当的方案来实现单例,以解决多线程的问题(我们必须认定所有的程序都是多线程)
如果不采用第五版以后的Java2,双重检查加锁的实现会失效
小心,如果你使用多个类加载器,可能导致单例失效而产生多个实例
如果使用JVM1.2或之前的版本,你必须建立单例注册表,以免垃圾回收器将单例回收
案例
- The logging class
- Managing a connection to a database
- File manager
- java.lang.Runtime#getRuntime()
- java.awt.Desktop#getDesktop()
- java.lang.System#getSecurityManager()
- Design Patterns: Elements of Reusable Object-Oriented Software
- Effective Java (2nd Edition)
设计思想
保证类在内存中只有一个对象
如何实现类在内存中只有一个对象呢?
- 构造私有
- 本身提供一个对象
- 通过公共的方法让外界访问
饿汉式(开发)
public class Singleton {
// 构造私有
private Singleton() {
}
// 自己造一个
// 静态方法只能访问静态成员变量,加静态
// 为了不让外界直接访问修改这个值,加private
private static Singleton s = new Singleton();
// 提供公共的访问方式
// 为了保证外界能够直接使用该方法,加静态
public static Singleton getSingleton() {
return s;
}
}
懒汉式
线程安全问题
懒加载思想(延迟加载)
public class Singleton {
private Singleton() {
}
// 使用volatile禁止指令重排
private volatile static Singleton t = null;
// 锁范围较大,如果可以接受重复初始化,可以不加锁,如基本类型的延迟加载
public synchronized static Singleton getSingleton() {
Singleton result = t;// 局部变量保证在t被初始化的情况下读取一次,可以提高性能
if (result == null) {// 这里有线程安全问题 所以要加锁
t = result = new Singleton();
}
return result;
}
// 缩小锁范围,但是要做双重检查 jdk 1.5以后有效
public static Singleton getSingleton() {
Singleton result = t;// 局部变量保证在t被初始化的情况下读取一次,可以提高性能
if (result == null) {
synchronized(Singleton.class){
result = t;
if (result == null) {// 双重检查
t = result = new Singleton();
}
}
}
return result;
}
}
内部类方式(static holder)
既支持了延迟加载,也是线程安全的,较完美的解决方案
多线程缺省同步锁的知识: 解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制,但是在某些情况下,JVM已经隐含的为您执行了同步,不用自己再来进行同步控制。
- 由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
- 访问final字段时
- 在创建线程之前创建对象时
- 线程可以看见它将要处理的对象时
要想很简单的实现线程安全,可以采用静态初始化器的方式,它可以由JVM来保证线程的安全性。比如前面的饿汉式实现方式。但是会浪费一定的时间空间?因为这种实现方式,会在类装载的时候就初始化对象。
一种可行的方式就是采用类级内部类,在这个类级内部类里面去创建对象实例。这样一来,只要不使用到这个类级内部类,那就不会创建对象实例,从而同步实现延迟加载和线程安全。
public class Singleton {
private static class StaticHolder {
private static Singleton instance;
static {
instance = new Singleton();
}
}
private Singleton(){
}
public static Singleton getInstance() {
return StaticHolder.instance;
}
}
枚举式(推荐)
好处
- 线程安全(枚举底层是线程安全的)
- 不会因为序列化而产生新的实例(因为它自己实现了readResolve方法)
- 防止反射攻击。(因为enum实际上是abstract的)
// 普通单例需要使用transient和readResolve方法保证反序列化后使用一个实例
private static final transient Singleton INSTANCE = new Singleton();
private Object readResolve() throws ObjectStreamException {
// instead of the object we're on,
// return the class variable INSTANCE
return INSTANCE;
}
// 枚举实现
public enum Singleton {
INSTANCE;
Singleton() {
}
public void operate() {
}
}
类加载器和jdk1.2垃圾回收问题
多个类加载器会在各自的命名空间下加载同一个类,造成存在一个类的多一个实例
解决:指定同一个类加载器加载
JDK1.2之前会将单例模式认为是没有引用而回收,再次getInstance()又创建一个对象
解决:建立单例注册表…使用全局引用指向单例?
Facade(外观模式/门面模式)
意图
门面模式为复杂子系统提供了简化的接口
提供了一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层接口,让子系统更容易使用
外观不只是简化接口,也将客户端从组件的子系统中解耦
- 使用场景:
- 为一个复杂的模块或子系统提供一个供外界访问的接口
- 子系统相对独立,外界黑箱操作
- 优点:
- 减少系统的相互依赖
- 提高了灵活性
- 提高安全性
- 缺点:
- 违反开闭原则,有问题要修改门面类
- 最佳实践:
- 门面十分庞大或子系统可以提供不同的访问路径时,使用多个门面
- 门面不应参与业务,如果需要就多封装一层,防止子系统业务对门面的依赖
适用性
- 想为复杂的子系统提供简单的接口。 子系统随着它们的发展变得越来越复杂。 大多数模式在应用时会导致更多更小的类。 这使得子系统更易于重复使用并且更易于自定义,但对于不需要自定义它的客户端也变得更难使用。 外观可以提供子系统的简单默认视图,对大多数客户端来说足够好。 只有需要更多可定制性的客户才需要超越外观。
- 客户端和抽象的实现类之间存在许多依赖关系。 引入外观以将子系统与客户端和其他子系统分离,从而提升子系统的独立性和可移植性。
- 想分层你的子系统。 使用外观来定义每个子系统级别的入口点。 如果子系统是依赖的,那么您可以通过使它们仅通过它们的外观相互通信来简化它们之间的依赖关系。
案例
public abstract class DwarvenMineWorker {
private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenMineWorker.class);
public void goToSleep() {
LOGGER.info("{} goes to sleep.", name());
}
public void wakeUp() {
LOGGER.info("{} wakes up.", name());
}
public void goHome() {
LOGGER.info("{} goes home.", name());
}
public void goToMine() {
LOGGER.info("{} goes to the mine.", name());
}
private void action(Action action) {
switch (action) {
case GO_TO_SLEEP:
goToSleep();
break;
case WAKE_UP:
wakeUp();
break;
case GO_HOME:
goHome();
break;
case GO_TO_MINE:
goToMine();
break;
case WORK:
work();
break;
default:
LOGGER.info("Undefined action");
break;
}
}
public void action(Action... actions) {
for (Action action : actions) {
action(action);
}
}
public abstract void work();
public abstract String name();
static enum Action {
GO_TO_SLEEP, WAKE_UP, GO_HOME, GO_TO_MINE, WORK
}
}
public class DwarvenTunnelDigger extends DwarvenMineWorker {
private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenTunnelDigger.class);
@Override
public void work() {
LOGGER.info("{} creates another promising tunnel.", name());
}
@Override
public String name() {
return "Dwarven tunnel digger";
}
}
public class DwarvenGoldDigger extends DwarvenMineWorker {
private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenGoldDigger.class);
@Override
public void work() {
LOGGER.info("{} digs for gold.", name());
}
@Override
public String name() {
return "Dwarf gold digger";
}
}
public class DwarvenCartOperator extends DwarvenMineWorker {
private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenCartOperator.class);
@Override
public void work() {
LOGGER.info("{} moves gold chunks out of the mine.", name());
}
@Override
public String name() {
return "Dwarf cart operator";
}
}
To operate all these goldmine workers we have the facade
public class DwarvenGoldmineFacade {
private final List<DwarvenMineWorker> workers;
public DwarvenGoldmineFacade() {
workers = new ArrayList<>();
workers.add(new DwarvenGoldDigger());
workers.add(new DwarvenCartOperator());
workers.add(new DwarvenTunnelDigger());
}
public void startNewDay() {
makeActions(workers, DwarvenMineWorker.Action.WAKE_UP, DwarvenMineWorker.Action.GO_TO_MINE);
}
public void digOutGold() {
makeActions(workers, DwarvenMineWorker.Action.WORK);
}
public void endDay() {
makeActions(workers, DwarvenMineWorker.Action.GO_HOME, DwarvenMineWorker.Action.GO_TO_SLEEP);
}
private static void makeActions(Collection<DwarvenMineWorker> workers,
DwarvenMineWorker.Action... actions) {
for (DwarvenMineWorker worker : workers) {
worker.action(actions);
}
}
}
Now to use the facade
DwarvenGoldmineFacade facade = new DwarvenGoldmineFacade();
facade.startNewDay();
// Dwarf gold digger wakes up.
// Dwarf gold digger goes to the mine.
// Dwarf cart operator wakes up.
// Dwarf cart operator goes to the mine.
// Dwarven tunnel digger wakes up.
// Dwarven tunnel digger goes to the mine.
facade.digOutGold();
// Dwarf gold digger digs for gold.
// Dwarf cart operator moves gold chunks out of the mine.
// Dwarven tunnel digger creates another promising tunnel.
facade.endDay();
// Dwarf gold digger goes home.
// Dwarf gold digger goes to sleep.
// Dwarf cart operator goes home.
// Dwarf cart operator goes to sleep.
// Dwarven tunnel digger goes home.
// Dwarven tunnel digger goes to sleep.
Adapter/Wrapper(适配器模式)
意图
将类的接口转换为客户期望的另一个接口。 适配器允许类一起工作,否则由于不兼容的接口。
将一个类的接口转换成另外一个客户希望的接口。从而使原来不能直接调用的接口变的可以调用。
在软件工程中,适配器模式是一种软件设计模式,它允许现有类的接口用作另一个接口。它通常用于使现有类与其他类一起工作而不修改它们的源代码。
适配器有两种形式:对象适配器和类适配器,对象适配器使用组合,类适配器需要多继承
- 类适配器
- 通过承诺具体的Adapter类来使适配器适应一个类。 因此,当要适配一个类及其所有子类适配器将无法正常工作。
- 让Adapter覆盖Adaptee的一些行为,Adapter是Adaptee的子类。
- 只引入一个对象,并且不需要额外的指针指向Adaptee。
- 对象适配器
- 让一个Adapter与许多Adaptee一起工作 - 即Adaptee本身及其所有子类(如果有的话)。 Adapter还可以立即向所有Adaptee添加功能。
- 使得覆盖Adaptee行为变得更加困难。 它将需要子类化Adaptee并使Adapter引用子类而不是Adaptee本身。
适用性
- 想使用现有的类,其Interface与需要的Interface不匹配
- 想创建一个可重用的类,它与不相关或不可预见的类合作,即不一定具有兼容接口的类
- 需要使用几个现有的子类,但通过对每个子类进行子类化来调整它们的接口是不切实际的。 对象适配器可以调整其父类的接口。
-
大多数使用第三方库的应用程序使用适配器作为应用程序和第三方库之间的中间层,以将应用程序与库分离。 如果必须使用另一个库,则只需要新库的适配器,而无需更改应用程序代码。
- 优点
- 让本来不适合使用的接口变得适合使用
- 缺点
- 一次只能适配一个类,使用有一定的局限性
案例
- java.util.Arrays#asList()
- java.util.Collections#list()
- java.util.Collections#enumeration()
- javax.xml.bind.annotation.adapters.XMLAdapter
- Design Patterns: Elements of Reusable Object-Oriented Software
- J2EE Design Patterns
public interface RowingBoat {
void row();
}
public class FishingBoat {
private static final Logger LOGGER = LoggerFactory.getLogger(FishingBoat.class);
public void sail() {
LOGGER.info("The fishing boat is sailing");
}
}
public class Captain {
private RowingBoat rowingBoat;
// default constructor and setter for rowingBoat
public Captain(RowingBoat rowingBoat) {
this.rowingBoat = rowingBoat;
}
public void row() {
rowingBoat.row();
}
}
public class FishingBoatAdapter implements RowingBoat {
private static final Logger LOGGER = LoggerFactory.getLogger(FishingBoatAdapter.class);
private FishingBoat boat;
public FishingBoatAdapter() {
boat = new FishingBoat();
}
@Override
public void row() {
boat.sail();
}
}
var captain = new Captain(new FishingBoatAdapter());
captain.row();
接口实现类适配
/*
* 针对用户操作的四种功能接口
*/
public interface UserDao {
public abstract void add();
public abstract void delete();
public abstract void update();
public abstract void find();
}
//适配器类
public abstract class UserAdapter implements UserDao {
@Override
public void add() {
}
@Override
public void delete() {
}
@Override
public void update() {
}
@Override
public void find() {
}
}
// 适配器模式实现类
public class UserDaoImpl2 extends UserAdapter {
@Override
public void add() {
System.out.println("添加功能");
}
}
不同接口转换适配
public class EnumerationIteratorAdaptor implements Iterator {
private Enumeration enumeration;
public EnumerationIteratorAdaptor(Enumeration enumeration) {
this.enumeration = enumeration;
}
public boolean hasNext() {
return enumeration.hasMoreElements();
}
public Object next() {
return enumeration.nextElement();
}
public void remove() {
throw new UnsupportedOperationException("adapter not implement method");
}
}
适配器模式和外观模式
外观和适配器可以包装许多类,但是外观的意图是简化接口,而是配置的意图是将接口转换成不同接口
当需要使用一个现有的类而其接口并不符合你的需要时,就使用适配器
当需要简化并统一一个很大的接口或者一群复杂的接口是,使用外观
适配器改变接口以符合客户的期望
外观将客户从一个复杂的子系统中解耦
实现一个适配器可能需要一番功夫,也可能不费功夫,视目标接口的大小与复杂度而定
实现一个外观,需要将子系统组合进外观中,然后将工作委托给子系统执行
适配器模式有两种形式:对象适配器和类适配器,类适配器需要用到多重继承
你可以为一个子系统实现一个以上的外观
适配器将一个对象包装起来以改变其接口;装饰者将一个对象包装起来以新增行为和责任;而外观将一群对象包装起来以简化其接口
Ambassador(大使)
意图
在客户端上提供帮助程序服务实例,并将常见功能从共享资源中卸载。客户端连接本地代理做某事,达到服务端下线的目的
使用ambassador模式,可以实现来自客户机的不太频繁的轮询以及延迟检查和日志记录。
大使服务可以被视为与客户共处一地的进程外代理。 此模式可用于以语言无关的方式卸载常见的客户端连接任务,如监视,日志记录,路由,安全性(如TLS)和弹性模式。 它通常与遗留应用程序或其他难以修改的应用程序一起使用,以扩展其网络功能。 它还可以使专业团队实现这些功能。
适用性
在使用无法修改或极难修改的旧式远程服务时,大使适用。可以在客户端上实现连接功能,从而无需更改远程服务。
- 大使提供远程服务的本地接口。
- 大使在客户端提供记录,断路,重试和安全性。
一般用途
- 控制对另一个对象的访问
- 实施日志记录
- 实现断路
- 卸载远程服务任务
- 使网络连接更容易
案例
- Kubernetes-native API gateway for microservices
- Ambassador pattern
- Designing Distributed Systems: Patterns and Paradigms for Scalable, Reliable Services
interface RemoteServiceInterface {
long doRemoteFunction(int value) throws Exception;
}
A remote services represented as a singleton.
public class RemoteService implements RemoteServiceInterface {
private static final Logger LOGGER = LoggerFactory.getLogger(RemoteService.class);
private static RemoteService service = null;
static synchronized RemoteService getRemoteService() {
if (service == null) {
service = new RemoteService();
}
return service;
}
private RemoteService() {}
@Override
public long doRemoteFunction(int value) {
long waitTime = (long) Math.floor(Math.random() * 1000);
try {
sleep(waitTime);
} catch (InterruptedException e) {
LOGGER.error("Thread sleep interrupted", e)
}
return waitTime >= 200 ? value * 10 : -1;
}
}
A service ambassador adding additional features such as logging, latency checks
public class ServiceAmbassador implements RemoteServiceInterface {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAmbassador.class);
private static final int RETRIES = 3;
private static final int DELAY_MS = 3000;
ServiceAmbassador() {}
@Override
public long doRemoteFunction(int value) {
return safeCall(value);
}
private long checkLatency(int value) {
RemoteService service = RemoteService.getRemoteService();
long startTime = System.currentTimeMillis();
long result = service.doRemoteFunction(value);
long timeTaken = System.currentTimeMillis() - startTime;
LOGGER.info("Time taken (ms): " + timeTaken);
return result;
}
private long safeCall(int value) {
int retries = 0;
long result = -1;
for (int i = 0; i < RETRIES; i++) {
if (retries >= RETRIES) {
return -1;
}
if ((result = checkLatency(value)) == -1) {
LOGGER.info("Failed to reach remote: (" + (i + 1) + ")");
retries++;
try {
sleep(DELAY_MS);
} catch (InterruptedException e) {
LOGGER.error("Thread sleep state interrupted", e);
}
} else {
break;
}
}
return result;
}
}
A client has a local service ambassador used to interact with the remote service:
public class Client {
private ServiceAmbassador serviceAmbassador;
Client() {
serviceAmbassador = new ServiceAmbassador();
}
long useService(int value) {
long result = serviceAmbassador.doRemoteFunction(value);
LOGGER.info("Service result: " + result)
return result;
}
}
Delegation(委托)
意图
它是一种技术,在这种技术中,一个对象向外部表达某种行为,但实际上将实现该行为的责任委托给一个关联的对象。
适用性
- 减少方法与类的耦合
- 行为相同但意识到这种情况在未来可能会改变的组件。
案例
/**
* Interface that both the Controller and the Delegate will implement.
*/
public interface Printer {
/**
* Method that takes a String to print to the screen. This will be implemented on both the
* controller and the delegate allowing the controller to call the same method on the delegate
* class.
*
* @param message to be printed to the screen
*/
void print(final String message);
}
/**
* Specialised Implementation of {@link Printer} for a Canon Printer, in this case the message to be
* printed is appended to "Canon Printer : ".
*/
public class CanonPrinter implements Printer {
private static final Logger LOGGER = LoggerFactory.getLogger(CanonPrinter.class);
/**
* {@inheritDoc}
*/
@Override
public void print(String message) {
LOGGER.info("Canon Printer : {}", message);
}
}
/**
* Specialised Implementation of {@link Printer} for a HP Printer, in this case the message to be
* printed is appended to "HP Printer : ".
*/
public class HpPrinter implements Printer {
private static final Logger LOGGER = LoggerFactory.getLogger(HpPrinter.class);
/**
* {@inheritDoc}
*/
@Override
public void print(String message) {
LOGGER.info("HP Printer : {}", message);
}
}
/**
* Delegator Class to delegate the implementation of the Printer. This ensures two things: - when
* the actual implementation of the Printer class changes the delegation will still be operational -
* the actual benefit is observed when there are more than one implementors and they share a
* delegation control
*/
public class PrinterController implements Printer {
private final Printer printer;
public PrinterController(Printer printer) {
this.printer = printer;
}
/**
* This method is implemented from {@link Printer} however instead on providing an implementation,
* it instead calls upon the class passed through the constructor. This is the delegate, hence the
* pattern. Therefore meaning that the caller does not care of the implementing class only the
* owning controller.
*
* @param message to be printed to the screen
*/
@Override
public void print(String message) {
printer.print(message);
}
}
PrinterController hpPrinterController = new PrinterController(new HpPrinter());
PrinterController canonPrinterController = new PrinterController(new CanonPrinter());
PrinterController epsonPrinterController = new PrinterController(new EpsonPrinter());
hpPrinterController.print(MESSAGE_TO_PRINT);
canonPrinterController.print(MESSAGE_TO_PRINT);
epsonPrinterController.print(MESSAGE_TO_PRINT);
Proxy/Surrogate(代理模式)
意图
代理模式为另一个对象提供一个替身或占位符以控制这个对象的访问
使用代理模式,一个类表示另一个类的功能。 代理,在其最一般的形式中,是一个类,作为其他对象的接口。代理是一个包装器或代理对象,客户端正在调用它来在后台访问真正的服务对象。 代理的使用可以简单地转发到真实对象,也可以提供额外的逻辑。 在代理中,可以提供额外的功能,例如在实际对象上的操作是资源密集型的时进行缓存,或者在调用实际对象上的操作之前检查前提条件。
使用代理模式创建代表(representative)对象,让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象
分类
- 远程代理
- Java的RMI调用
- 虚拟代理(必要时创建被代理对象)
- 懒加载
- 保护代理
- 决定对象访问
- 防火墙代理
- 防火墙系统
- 智能引用代理
- 缓存代理
- Web服务器代理
- 同步代理
- JavaSpaces
- 复杂隐藏代理
- 写入时复制代理
- CopyOnWriteArrayList
- 动态代理
- java.util.reflect包提供Proxy类实现接口代理
- cglib的继承代理
适用性
当需要一个比简单指针更通用或更复杂的对象引用时,代理是适用的。
- 远程代理为不同地址空间中的对象提供本地代理。
- 虚拟代理根据需要创建昂贵的对象。
- 保护代理控制对原始对象的访问。当对象应具有不同的访问权限时,保护代理非常有用。
- 控制对其他对象的访问
- 延迟初始化
- 实现日志记录
- 方便网络连接
- 对对象的引用计数
代理模式为另一个对象提供代表,以遍控制对对象的访问,管理访问的形式有许多种
远程代理管理客户和远程对象之间的交互
虚拟代理控制访问实例化开销大的对象
保护代理基于调用者控制对对象方法的访问
代理模式有许多变体,如:缓存代理、同步代理、防火墙代理和写入时复制代理
代理在结构上类似装饰者,但是目的不同:装饰者模式为对象加上行为,而代理是控制访问
Java内置支持代理,可以根据需要建立动态代理,并将所有调用分配到所选处理器
就和其他的包装着(wrapper)一样,代理会造成你的设计中类的数目增加
案例
- Controlling Access With Proxy Pattern
- Proxy
- java.lang.reflect.Proxy
- Apache Commons Proxy
- Mocking frameworks Mockito, Powermock, EasyMock
- Design Patterns: Elements of Reusable Object-Oriented Software
public interface WizardTower {
void enter(Wizard wizard);
}
public class IvoryTower implements WizardTower {
private static final Logger LOGGER = LoggerFactory.getLogger(IvoryTower.class);
public void enter(Wizard wizard) {
LOGGER.info("{} enters the tower.", wizard);
}
}
@ToString
@NoArgsConstructor
public class Wizard {
private final String name;
}
public class WizardTowerProxy implements WizardTower {
private static final Logger LOGGER = LoggerFactory.getLogger(WizardTowerProxy.class);
private static final int NUM_WIZARDS_ALLOWED = 3;
private int numWizards;
private final WizardTower tower;
public WizardTowerProxy(WizardTower tower) {
this.tower = tower;
}
@Override
public void enter(Wizard wizard) {
if (numWizards < NUM_WIZARDS_ALLOWED) {
tower.enter(wizard);
numWizards++;
} else {
LOGGER.info("{} is not allowed to enter!", wizard);
}
}
}
WizardTowerProxy proxy = new WizardTowerProxy(new IvoryTower());
proxy.enter(new Wizard("Red wizard")); // Red wizard enters the tower.
proxy.enter(new Wizard("White wizard")); // White wizard enters the tower.
proxy.enter(new Wizard("Black wizard")); // Black wizard enters the tower.
proxy.enter(new Wizard("Green wizard")); // Green wizard is not allowed to enter!
proxy.enter(new Wizard("Brown wizard")); // Brown wizard is not allowed to enter!
动态代理
参见《反射和动态代理部分》
Decorator/Wrapper(装饰者模式)
意图
修饰器模式允许您通过在修饰器类的对象中包装对象来动态更改对象在运行时的行为。 动态地将额外的职责附加到对象。装饰器提供了一种灵活的替代子类来扩展功能。
在面向对象编程中,decorator模式是一种设计模式,它允许静态或动态地将行为添加到单个对象中,而不影响来自同一类的其他对象的行为。 decorator模式对于坚持单一责任原则通常是有用的,因为它允许在具有独特关注区域的类之间划分功能。
动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的方案
装饰模式就是使用被装饰类的一个子类的实例,在客户端将这个子类的实例交给装饰类。是继承的替代方案
- 优点
- 使用装饰模式,可以提供比继承更灵活的扩展对象的功能,它可以动态的添加对象的功能,并且可以随意的组合这些功能
- 缺点
- 正因为可以随意组合,所以就可能出现一些不合理的逻辑
- 多层装饰的复杂性
- 继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式
- 在我们的设计中,应该允许行为被拓展,而无需修改现有的代码
- 组合和委托可用于在运行时动态的加上行为
- 除了继承,装饰者模式也可以让我们扩展行为
- 装饰者模式意味着一群装饰者类,这些类用来包装具体组件
- 装饰者类反映出被装饰者的组件类型(事实上,他们拥有相同的类型,都经过接口或继承实现)
- 装饰者可以在被装饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者整个替代掉,而达到特定的
- 你可以用无数个装饰者包装一个组件
- 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型
- 装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂
适用性
- 动态、透明地向单个对象添加职责,即,不影响其他对象
- 可撤销的增强
- 当通过子类进行扩展不可行时。
- 有时大量的独立扩展是可能的,并且会产生子类爆炸式的扩展来支持每个组合。
- 或者类定义可能被隐藏,或者无法用于子类化。
案例
- Decorator Pattern Tutorial
- java.io.InputStream, java.io.OutputStream,java.io.Reader and java.io.Writer
- java.util.Collections#synchronizedXXX()
- java.util.Collections#unmodifiableXXX()
- java.util.Collections#checkedXXX()
- Design Patterns: Elements of Reusable Object-Oriented Software
- Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions
- J2EE Design Patterns
public interface Phone {
public abstract void call();
}
public class IPhone implements Phone {
@Override
public void call() {
System.out.println("手机可以打电话了");
}
}
// 装饰抽象类
public abstract class PhoneDecorate implements Phone {
private Phone p;
public PhoneDecorate(Phone p) {
this.p = p;
}
@Override
public void call() {
this.p.call();
}
}
public class MusicPhoneDecorate extends PhoneDecorate {
public MusicPhoneDecorate(Phone p) {
super(p);
}
@Override
public void call() {
super.call();
System.out.println("手机可以听音乐");
}
}
public class RingPhoneDecorate extends PhoneDecorate {
public RingPhoneDecorate(Phone p) {
super(p);
}
@Override
public void call() {
System.out.println("手机可以听彩铃");
super.call();
}
}
Phone p = new IPhone();
p.call();
System.out.println("------------");
// 需求:我想在接电话前,听彩铃
PhoneDecorate pd = new RingPhoneDecorate(p);
pd.call();// 听彩铃打电话
// 需求:我想在接电话后,听音乐
pd = new MusicPhoneDecorate(p);
pd.call();// 打电话听音乐
// 需求:我要想手机在接前听彩铃,接后听音乐
// 自己提供装饰类,在打电话前听彩铃,打电话后听音乐或者嵌套使用装饰类
pd = new RingPhoneDecorate(new MusicPhoneDecorate(p));
pd.call();// 同时听彩铃打电话听音乐
// Jdk中的装饰模式
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
Scanner sc = new Scanner(System.in);
Event Aggregator(事件聚合器)
意图
当客户想要订阅事件时,具有大量对象的系统可能导致复杂性。 客户端必须单独查找和注册每个对象,如果每个对象有多个事件,则每个事件都需要单独订阅。 Event Aggregator充当许多对象的单个事件源。 它注册了许多对象的所有事件,允许客户端只注册到聚合器。
适用性
- 当有许多可能是事件源的对象时,事件聚合器是一个很好的选择。可以将注册逻辑集中到事件聚合器中,而不是让观察者全部注册。除了简化注册,事件聚合器还简化了使用观察器时的内存管理问题。
案例
/**
* Event enumeration.
*/
@ToString
public enum Event {
STARK_SIGHTED("Stark sighted"),
WARSHIPS_APPROACHING("Warships approaching"),
TRAITOR_DETECTED("Traitor detected");
private String description;
Event(String description) {
this.description = description;
}
}
/**
*
* EventEmitter is the base class for event producers that can be observed.
*
*/
public abstract class EventEmitter {
private List<EventObserver> observers;
public EventEmitter() {
observers = new LinkedList<>();
}
public EventEmitter(EventObserver obs) {
this();
registerObserver(obs);
}
public final void registerObserver(EventObserver obs) {
observers.add(obs);
}
protected void notifyObservers(Event e) {
for (EventObserver obs : observers) {
obs.onEvent(e);
}
}
public abstract void timePasses(Weekday day);
}
/**
* Observers of events implement this interface.
*/
public interface EventObserver {
void onEvent(Event e);
}
/**
* KingJoffrey observes events from {@link KingsHand}.
*/
public class KingJoffrey implements EventObserver {
private static final Logger LOGGER = LoggerFactory.getLogger(KingJoffrey.class);
@Override
public void onEvent(Event e) {
LOGGER.info("Received event from the King's Hand: {}", e.toString());
}
}
/**
* KingsHand observes events from multiple sources and delivers them to listeners.
*/
public class KingsHand extends EventEmitter implements EventObserver {
public KingsHand() {
}
public KingsHand(EventObserver obs) {
super(obs);
}
@Override
public void onEvent(Event e) {
notifyObservers(e);
}
@Override
public void timePasses(Weekday day) {
// NOP
}
}
/**
* LordBaelish produces events.
*/
public class LordBaelish extends EventEmitter {
public LordBaelish() {
}
public LordBaelish(EventObserver obs) {
super(obs);
}
@Override
public void timePasses(Weekday day) {
if (day.equals(Weekday.FRIDAY)) {
notifyObservers(Event.STARK_SIGHTED);
}
}
}
/**
* LordVarys produces events.
*/
public class LordVarys extends EventEmitter {
public LordVarys() {
}
public LordVarys(EventObserver obs) {
super(obs);
}
@Override
public void timePasses(Weekday day) {
if (day.equals(Weekday.SATURDAY)) {
notifyObservers(Event.TRAITOR_DETECTED);
}
}
}
/**
* Scout produces events.
*/
public class Scout extends EventEmitter {
public Scout() {
}
public Scout(EventObserver obs) {
super(obs);
}
@Override
public void timePasses(Weekday day) {
if (day.equals(Weekday.TUESDAY)) {
notifyObservers(Event.WARSHIPS_APPROACHING);
}
}
}
/**
* Weekday enumeration
*/
@ToString
public enum Weekday {
MONDAY("Monday"),
TUESDAY("Tuesday"),
WEDNESDAY("Wednesday"),
THURSDAY("Thursday"),
FRIDAY("Friday"),
SATURDAY("Saturday"),
SUNDAY("Sunday");
private String description;
Weekday(String description) {
this.description = description;
}
}
KingJoffrey kingJoffrey = new KingJoffrey();
KingsHand kingsHand = new KingsHand(kingJoffrey);
List<EventEmitter> emitters = new ArrayList<>();
emitters.add(kingsHand);
emitters.add(new LordBaelish(kingsHand));
emitters.add(new LordVarys(kingsHand));
emitters.add(new Scout(kingsHand));
for (Weekday day : Weekday.values()) {
for (EventEmitter emitter : emitters) {
emitter.timePasses(day);
}
}
Bridge/Handle/Body(桥接模式/桥梁模式)
意图
使用聚合代替继承,解决两个维度排列组合问题
使用桥接模式,不止改变你的实现,也改变你的抽象,将抽象的部分与它的实现部分分离,使它们都可以独立的变化。
桥接模式是关于优先组合而不是继承。实现细节从一个层次结构推送到另一个具有单独层次结构的对象。
- 用途
- 类的抽象及他的实现都可以通过生成子类的方法加以扩充
- 当需要用不同的方式改变接口和实现时,你会发现桥接模式很好用
- 不希望或不适用使用继承的场景
- 重用性要求较高的场景
- 好处:
- 将实现予以解耦
- 抽象和实现可以独立扩展,不会影响到对方
- 对于“具体的抽象类”所做的改变,不会影响到客户
- 缺点:
- 增加了复杂度
适用性
- 避免抽象及其实现之间的永久绑定。例如,当必须在运行时选择或切换实现时,可能会出现这种情况。
- 抽象及其实现都应该通过子类化进行扩展。在本例中,桥接模式允许您组合不同的抽象和实现,并独立地扩展它们。
- 抽象实现中的更改不应对客户端产生影响;也就是说,不必重新编译他们的代码。
- 有大量的类,类层次结构预示需要将一个对象分成两部分。Rumbaugh使用术语“nested generalizations”来指代此类类层次结构。
- 你想在多个对象之间共享一个实现(可能使用引用计数),这应该对客户端透明。
案例
public interface Weapon {
void wield();
void swing();
void unwield();
Enchantment getEnchantment();
}
public class Sword implements Weapon {
private final Enchantment enchantment;
public Sword(Enchantment enchantment) {
this.enchantment = enchantment;
}
@Override
public void wield() {
LOGGER.info("The sword is wielded.");
enchantment.onActivate();
}
@Override
public void swing() {
LOGGER.info("The sword is swinged.");
enchantment.apply();
}
@Override
public void unwield() {
LOGGER.info("The sword is unwielded.");
enchantment.onDeactivate();
}
@Override
public Enchantment getEnchantment() {
return enchantment;
}
}
public class Hammer implements Weapon {
private final Enchantment enchantment;
public Hammer(Enchantment enchantment) {
this.enchantment = enchantment;
}
@Override
public void wield() {
LOGGER.info("The hammer is wielded.");
enchantment.onActivate();
}
@Override
public void swing() {
LOGGER.info("The hammer is swinged.");
enchantment.apply();
}
@Override
public void unwield() {
LOGGER.info("The hammer is unwielded.");
enchantment.onDeactivate();
}
@Override
public Enchantment getEnchantment() {
return enchantment;
}
}
And the separate enchantment hierarchy
public interface Enchantment {
void onActivate();
void apply();
void onDeactivate();
}
public class FlyingEnchantment implements Enchantment {
@Override
public void onActivate() {
LOGGER.info("The item begins to glow faintly.");
}
@Override
public void apply() {
LOGGER.info("The item flies and strikes the enemies finally returning to owner's hand.");
}
@Override
public void onDeactivate() {
LOGGER.info("The item's glow fades.");
}
}
public class SoulEatingEnchantment implements Enchantment {
@Override
public void onActivate() {
LOGGER.info("The item spreads bloodlust.");
}
@Override
public void apply() {
LOGGER.info("The item eats the soul of enemies.");
}
@Override
public void onDeactivate() {
LOGGER.info("Bloodlust slowly disappears.");
}
}
And both the hierarchies in action
Sword enchantedSword = new Sword(new SoulEatingEnchantment());
enchantedSword.wield();
enchantedSword.swing();
enchantedSword.unwield();
// The sword is wielded.
// The item spreads bloodlust.
// The sword is swinged.
// The item eats the soul of enemies.
// The sword is unwielded.
// Bloodlust slowly disappears.
Hammer hammer = new Hammer(new FlyingEnchantment());
hammer.wield();
hammer.swing();
hammer.unwield();
// The hammer is wielded.
// The item begins to glow faintly.
// The hammer is swinged.
// The item flies and strikes the enemies finally returning to owner's hand.
// The hammer is unwielded.
// The item's glow fades.
Composite(组合模式/部分-整体模式)
意图
将对象组合成树结构以表示部分整体层次结构。组合允许客户机统一处理单个对象和对象的组合。
复合模式允许客户机以统一的方式处理各个对象。
在软件工程中,复合模式是一种划分设计模式。复合模式描述了一组对象将以与单个对象实例相同的方式进行处理。组合的目的是将对象“组合”到树结构中,以表示部分-整体层次结构。 实现复合模式允许客户机统一地处理单个对象和组合。
组合模式允许你将对象组合成树形结构来表现“整体/部分”层次结构,组合能让客户以一致的方式处理个别对象及对象组合
组合模式让我们能使用树形方式创建对象的结构,树里面包含了组合以及个别的对象。
使用组合结构,我们能把相同的操作应用在组合和个别对象上。换句话说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差别
与依赖倒置原则冲突,直接使用了实现类,限制了接口的影响范围
组合包含组件,组件包含两种:组合和叶子节点元素
组合模式提供一个机构,可同时包容个别对象和组合对象
组合模式允许客户对个别对象以及组合对象一视同仁
组合结构内的任意对象称为组件,组件可以是组合,也可以是叶子节点
在实现组合模式时,有许多设计上的折衷,要根据需要平衡透明性和安全性
透明模式和安全模式
透明模式:叶子节点和分支节点有相同的结构,通过判断getChildren()判断(基本遵循了依赖倒置原则) 安全模式:叶子节点和分支节点不相同的结构
组合模式常和迭代器模式和访问者模式一起使用
适用性
- 要表示对象的部分整体层次结构
- 希望客户端能够忽略对象组合和单个对象之间的差异。客户端将统一处理复合结构中的所有对象
案例
- java.awt.Container and java.awt.Component
- Apache Wicket component tree, see Component and MarkupContainer
- Design Patterns: Elements of Reusable Object-Oriented Software
public abstract class LetterComposite {
private List<LetterComposite> children = new ArrayList<>();
public void add(LetterComposite letter) {
children.add(letter);
}
public int count() {
return children.size();
}
protected void printThisBefore() {
}
protected void printThisAfter() {
}
public void print() {
printThisBefore();
for (LetterComposite letter : children) {
letter.print();
}
printThisAfter();
}
}
public class Letter extends LetterComposite {
private char c;
public Letter(char c) {
this.c = c;
}
@Override
protected void printThisBefore() {
System.out.print(c);
}
}
public class Word extends LetterComposite {
public Word(List<Letter> letters) {
for (Letter l : letters) {
this.add(l);
}
}
@Override
protected void printThisBefore() {
System.out.print(" ");
}
}
public class Sentence extends LetterComposite {
public Sentence(List<Word> words) {
for (Word w : words) {
this.add(w);
}
}
@Override
protected void printThisAfter() {
System.out.print(".");
}
}
public class Messenger {
LetterComposite messageFromOrcs() {
List<Word> words = new ArrayList<>();
words.add(new Word(Arrays.asList(new Letter('W'), new Letter('h'), new Letter('e'), new Letter('r'), new Letter('e'))));
words.add(new Word(Arrays.asList(new Letter('t'), new Letter('h'), new Letter('e'), new Letter('r'), new Letter('e'))));
words.add(new Word(Arrays.asList(new Letter('i'), new Letter('s'))));
words.add(new Word(Arrays.asList(new Letter('a'))));
words.add(new Word(Arrays.asList(new Letter('w'), new Letter('h'), new Letter('i'), new Letter('p'))));
words.add(new Word(Arrays.asList(new Letter('t'), new Letter('h'), new Letter('e'), new Letter('r'), new Letter('e'))));
words.add(new Word(Arrays.asList(new Letter('i'), new Letter('s'))));
words.add(new Word(Arrays.asList(new Letter('a'))));
words.add(new Word(Arrays.asList(new Letter('w'), new Letter('a'), new Letter('y'))));
return new Sentence(words);
}
LetterComposite messageFromElves() {
List<Word> words = new ArrayList<>();
words.add(new Word(Arrays.asList(new Letter('M'), new Letter('u'), new Letter('c'), new Letter('h'))));
words.add(new Word(Arrays.asList(new Letter('w'), new Letter('i'), new Letter('n'), new Letter('d'))));
words.add(new Word(Arrays.asList(new Letter('p'), new Letter('o'), new Letter('u'), new Letter('r'), new Letter('s'))));
words.add(new Word(Arrays.asList(new Letter('f'), new Letter('r'), new Letter('o'), new Letter('m'))));
words.add(new Word(Arrays.asList(new Letter('y'), new Letter('o'), new Letter('u'), new Letter('r'))));
words.add(new Word(Arrays.asList(new Letter('m'), new Letter('o'), new Letter('u'), new Letter('t'), new Letter('h'))));
return new Sentence(words);
}
}
LetterComposite orcMessage = new Messenger().messageFromOrcs();
orcMessage.print(); // Where there is a whip there is a way.
LetterComposite elfMessage = new Messenger().messageFromElves();
elfMessage.print(); // Much wind pours from your mouth.
Specification/Filter/Criteria(规格模式)
意图
规格模式将如何匹配候选对象的语句与其匹配的候选对象分离。除了它在选择中的有用性外,它对验证和按顺序构建也很有价值。
适用性
- 需要根据某些条件选择对象的子集,并在不同时间刷新选择。
- 需要检查是否只有合适的对象用于特定角色(验证)。
Pattern | Usage | Pros | Cons |
---|---|---|---|
Hard-Coded Specification | Selection criteria are few and known in advance | + Easy to implement | - Inflexible |
+ Expressive | |||
Parameterized Specification | Selection criteria are a large range of values (e.g. mass, speed,…) | + Some flexibility | - Still requires special-purpose classes |
Composite Specification | There are a lot of selection criteria that can be combined in multiple ways, hence it is not feasible to create a class for each selector | + Very flexible, without requiring many specialized classes | - Somewhat more difficult to comprehend |
+ Supports logical operations | - You still need to create the base classes used as leaves |
案例
public interface Creature {
String getName();
Size getSize();
Movement getMovement();
Color getColor();
Mass getMass();
}
public class Dragon extends AbstractCreature {
public Dragon() {
super("Dragon", Size.LARGE, Movement.FLYING, Color.RED, new Mass(39300.0));
}
}
/**
* Base class for selectors.
*/
public abstract class AbstractSelector<T> implements Predicate<T> {
public AbstractSelector<T> and(AbstractSelector<T> other) {
return new ConjunctionSelector<>(this, other);
}
public AbstractSelector<T> or(AbstractSelector<T> other) {
return new DisjunctionSelector<>(this, other);
}
public AbstractSelector<T> not() {
return new NegationSelector<>(this);
}
}
public class ConjunctionSelector<T> extends AbstractSelector<T> {
private List<AbstractSelector<T>> leafComponents;
@SafeVarargs
ConjunctionSelector(AbstractSelector<T>... selectors) {
this.leafComponents = List.of(selectors);
}
/**
* Tests if *all* selectors pass the test.
*/
@Override
public boolean test(T t) {
return leafComponents.stream().allMatch(comp -> (comp.test(t)));
}
}
public class MovementSelector extends AbstractSelector<Creature> {
private final Movement movement;
public MovementSelector(Movement m) {
this.movement = m;
}
@Override
public boolean test(Creature t) {
return t.getMovement().equals(movement);
}
}
public class MassGreaterThanSelector extends AbstractSelector<Creature> {
private final Mass mass;
public MassGreaterThanSelector(double mass) {
this.mass = new Mass(mass);
}
@Override
public boolean test(Creature t) {
return t.getMass().greaterThan(mass);
}
}
List<Creature> redCreatures = creatures.stream().filter(new ColorSelector(Color.RED)).collect(Collectors.toList());
List<Creature> heavyCreatures = creatures.stream().filter(new MassGreaterThanSelector(500.0).collect(Collectors.toList());
AbstractSelector specialCreaturesSelector = new ColorSelector(Color.RED).and(new MovementSelector(Movement.FLYING)).and(new SizeSelector(Size.SMALL).not());
List<Creature> specialCreatures = creatures.stream().filter(specialCreaturesSelector).collect(Collectors.toList());
扩展组合模式:
public interface ISpecification<T> {
boolean isSatisfiedBy(T candidate);
ISpecification and(ISpecification specification);
ISpecification or(ISpecification specification);
ISpecification not();
}
// 父类依赖子类的场景,只有再非常明确不会变化的场景中存在,它缺乏扩展性
public abstract class CompositeSpecification<T> implements ISpecification<T> {
@Override
public ISpecification and(ISpecification specification) {
return new AndSpecification(this, specification);
}
@Override
public ISpecification or(ISpecification specification) {
return new OrSpecification(this, specification);
}
@Override
public ISpecification not() {
return new NotSpecification(this);
}
}
public class AndSpecification<T> extends CompositeSpecification<T> {
private ISpecification left;
private ISpecification right;
public AndSpecification(ISpecification left, ISpecification right) {
this.left = left;
this.right = right;
}
@Override
public boolean isSatisfiedBy(T candidate) {
return left.isSatisfiedBy(candidate) && right.isSatisfiedBy(candidate);
}
}
public class OrSpecification extends CompositeSpecification {
private ISpecification left;
private ISpecification right;
public OrSpecification(ISpecification left, ISpecification right) {
this.left = left;
this.right = right;
}
@Override
public boolean isSatisfiedBy(Object candidate) {
return left.isSatisfiedBy(candidate) || right.isSatisfiedBy(candidate);
}
}
public class NotSpecification extends CompositeSpecification {
private ISpecification specification;
public NotSpecification(ISpecification specification) {
this.specification = specification;
}
@Override
public boolean isSatisfiedBy(Object candidate) {
return !specification.isSatisfiedBy(candidate);
}
}
public class CustomSpecification extends CompositeSpecification<Integer> {
Integer object;
public CustomSpecification(Integer object) {
this.object = object;
}
@Override
public boolean isSatisfiedBy(Integer candidate) {
return candidate > object;
}
}
// 大于15或者大于20
ISpecification<Integer> integerISpecification15 = new CustomSpecification(15);
ISpecification<Integer> integerISpecification20 = new CustomSpecification(20);
IntStream.range(0, 31)
.filter(
i -> integerISpecification15.or(integerISpecification20).isSatisfiedBy(i)
)
.forEach(System.out::println);
Flyweight(享元模式)
意图
使用共享有效地支持大量细粒度对象。它用于通过尽可能多地与类似对象共享来最小化内存使用或计算开销。
如果想让某个类的一个实例能用来提供许多“虚拟实例”,就使用享元模式(有效的支持大量细粒度的对象)
享元模式采用一个共享来避免大量拥有相同内容对象的开销。这种开销最常见、最直观的就是内存的损耗。
例如IntegerCache和String字符串常量池
- 使用场景:
- 当一个类有许多实例,而这些实例能被同一个方法控制的时候,使用享元模式
- 优点:
- 减少运行时对象实例的个数,节省内存
- 将许多的“虚拟”对象的状态集中管理
- 缺点:
- 一旦你实现了它,那么单个逻辑的实例将无法拥有独立而不同的行为
- 注意事项:
- 线程安全问题
适用性
- 应用程序使用大量对象
- 由于对象的数量庞大,存储成本很高
- 大多数对象状态可以是外在的
- 一旦外部状态被移除,许多对象组可被相对较少的共享对象替换
- 应用程序不依赖于对象标识。 由于可以共享flyweight对象,因此对于概念上不同的对象,身份测试将返回true。
案例
- java.lang.Integer#valueOf(int) and similarly for Byte, Character and other wrapped types.
- Design Patterns: Elements of Reusable Object-Oriented Software
public interface Potion {
void drink();
}
public class HealingPotion implements Potion {
private static final Logger LOGGER = LoggerFactory.getLogger(HealingPotion.class);
@Override
public void drink() {
LOGGER.info("You feel healed. (Potion={})", System.identityHashCode(this));
}
}
public class HolyWaterPotion implements Potion {
private static final Logger LOGGER = LoggerFactory.getLogger(HolyWaterPotion.class);
@Override
public void drink() {
LOGGER.info("You feel blessed. (Potion={})", System.identityHashCode(this));
}
}
public class InvisibilityPotion implements Potion {
private static final Logger LOGGER = LoggerFactory.getLogger(InvisibilityPotion.class);
@Override
public void drink() {
LOGGER.info("You become invisible. (Potion={})", System.identityHashCode(this));
}
}
Then the actual Flyweight object which is the factory for creating potions
public class PotionFactory {
private final Map<PotionType, Potion> potions;
public PotionFactory() {
potions = new EnumMap<>(PotionType.class);
}
Potion createPotion(PotionType type) {
Potion potion = potions.get(type);
if (potion == null) {
switch (type) {
case HEALING:
potion = new HealingPotion();
potions.put(type, potion);
break;
case HOLY_WATER:
potion = new HolyWaterPotion();
potions.put(type, potion);
break;
case INVISIBILITY:
potion = new InvisibilityPotion();
potions.put(type, potion);
break;
default:
break;
}
}
return potion;
}
}
And it can be used as below
PotionFactory factory = new PotionFactory();
factory.createPotion(PotionType.INVISIBILITY).drink(); // You become invisible. (Potion=6566818)
factory.createPotion(PotionType.HEALING).drink(); // You feel healed. (Potion=648129364)
factory.createPotion(PotionType.INVISIBILITY).drink(); // You become invisible. (Potion=6566818)
factory.createPotion(PotionType.HOLY_WATER).drink(); // You feel blessed. (Potion=1104106489)
factory.createPotion(PotionType.HOLY_WATER).drink(); // You feel blessed. (Potion=1104106489)
factory.createPotion(PotionType.HEALING).drink(); // You feel healed. (Potion=648129364)
Template method(模版方法模式)
意图
在操作中定义算法的框架,将一些步骤推迟到子类。模板方法允许子类在不改变算法结构的情况下重新定义算法的某些步骤。
为了确保子类不会重写模板方法,模板方法应该声明为final。
这个图更像策略模式:
适用性
- 一次实现算法的不变部分,并将其留给子类来实现可以改变的行为
- 子类之间的公共行为应该在公共类中进行分解和本地化以避免代码重复。
- 这是Opdyke和Johnson所描述的“重构以泛化”的好例子。
- 首先识别现有代码中的差异,然后将差异分为新操作。
- 最后,用调用这些新操作之一的模板方法替换不同的代码
- 控制子类扩展。可以定义一个模板方法,该方法在特定点调用“hook”操作,因此只允许在这些点进行扩展
模版方法模式就是定义一个算法的骨架,而将具体的算法延迟到子类中来实现,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤
- 优点
- 使用模版方法模式,在定义算法骨架的同时,可以很灵活的实现具体的算法,满足用户灵活多变的需求,既兼顾了原则性,有不失灵活性 + 封装不变部分,扩展可变部分 + 提取公共部分代码便于维护 + 行为由父类控制,子类实现
- 缺点
- 如果算法骨架有修改的话,则需要修改抽象类
模板方法定义了算法的步骤,把这些步骤实现延迟到子类
模板方法模式为我们提供了一种代码复用的重要技巧
模板方法的抽象类可以定义具体方法、抽象方法和钩子
抽象方法由子类实现
钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择要不要去覆盖它
为了防止子类改变模板方法中的算法,可以将模板方法声明为final
好莱坞原则告诉我们,将决策权放在高层模块中,以便决定如何以及何时调用低层模块
你将在真实世界代码中查看到模板方法模式的许多变体,不要期待他们全都是一眼可以被你认出来
策略模式和模板方法模式都封装算法,一个用组合,一个用继承
工厂方法是模板方法的一种特殊版本
案例
- Template-method Pattern Tutorial
- javax.servlet.GenericServlet.init:
Method
GenericServlet.init(ServletConfig config)
calls the parameterless methodGenericServlet.init()
which is intended to be overridden in subclasses. MethodGenericServlet.init(ServletConfig config)
is the template method in this example. - Design Patterns: Elements of Reusable Object-Oriented Software
public abstract class GetTime {
// 需求:请给我计算出一段代码的运行时间
// final防止子类覆盖
public final long getTime() {
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
if (isTraceMode()) {
System.out.println("start at:" + start);
System.out.println("end at:" + end);
}
return end - start;
}
// 必须实现,使用abstract 模版方法使用protected降低访问权限,符合迪米特法则
protected abstract void code();
// 可选实现,使用钩子,提供默认实现,子类选择是否覆盖父类方法
public boolean isTraceMode() {
return true;
}
}
public class ForDemo extends GetTime {
@Override
protected void code() {
for (int x = 0; x < 100000; x++) {
System.out.println(x);
}
}
}
Observer/Dependents/Publish-Subscribe(观察者模式/订阅模式)
意图
定义对象之间的一对多依赖关系,以便在一个对象更改状态时,自动通知和更新其所有依赖项。
适用性
- 当一个抽象有两个方面时,一个依赖于另一个。将这些方面封装在单独的对象中,可以独立地改变和重用它们
- 当对一个对象的更改需要更改其他对象,而您不知道需要更改多少个对象时
- 当一个对象应该能够通知其他对象而不必假设这些对象是谁时。换句话说,您不希望这些对象紧密耦合
观察者模式定义了对象之间的一(Subject)对多(Observer)依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新
JDK自带了观察者模式实现,Observable类和Observer接口,但是Observable是一个类并没有实现任何接口所以很局限
观察者模式定义了对象之间的一对多关系
主题(也就是观察者)用一个共同的接口来更新观察者
观察者和可观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现类观察者接口
使用此模式时,你可以从被观察者出推(push)或拉(pull)数据,然而,推的方式被认为更正确
有多个观察者时,不可以依赖特定的通知次序(取消订阅会影响顺序)
Java有多种观察者模式的实现,包括了通用的java.util.Observable,要注意它的一些问题(不是接口,扩展和多继承问题),如果有必要的话,可以实现Obserable
Swing大量使用观察者模式,许多GUI框架也如此
此模式被应用在很多地方,如JavaBeans、RMI
- 使用场景:
- 关联行为场景
- 时间多级触发场景
- 跨系统的消息交换场景,如消息队列待处理机制
- 缺点:
- 调试复杂
- 顺序执行的消息通知会造成效率问题,一般考虑采用异步的方式
- 注意事项:
- 广播链最好控制在两次以内
- 异步的线程安全和队列问题,参考消息队列
案例
- java.util.Observer
- java.util.EventListener
- javax.servlet.http.HttpSessionBindingListener
- RxJava
- Design Patterns: Elements of Reusable Object-Oriented Software
- Java Generics and Collections
// 主题
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
public class MySubject implements Subject {
private String message;
List<Observer> observers = new ArrayList<Observer>();
public void registerObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(this, this);
}
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
// 观察者
public interface Observer {
void update(Subject subject, Object args);
}
public class MyObserver implements Observer {
public MyObserver(Subject subject) {
subject.registerObserver(this);
}
public void update(Subject subject, Object args) {
if (subject instanceof MySubject) {
MySubject mySubject = (MySubject) args;
System.out.println(mySubject.getMessage());
}
}
}
//测试
@Test
public void update() throws Exception {
MySubject subject = new MySubject();
MyObserver myObserver = new MyObserver(subject);
MyObserver myObserver1 = new MyObserver(subject);
MyObserver myObserver2 = new MyObserver(subject);
MyObserver myObserver3 = new MyObserver(subject);
MyObserver myObserver4 = new MyObserver(subject);
subject.setMessage("hello");
subject.notifyObservers();
}
/**
* Generic observer inspired by Java Generics and Collection by {@literal Naftalin & Wadler}.
*
* @param <S> Subject
* @param <O> Observer
* @param <A> Argument type
*/
public abstract class Observable<S extends Observable<S, O, A>, O extends Observer<S, O, A>, A> {
protected List<O> observers;
public Observable() {
this.observers = new CopyOnWriteArrayList<>();
}
public void addObserver(O observer) {
this.observers.add(observer);
}
public void removeObserver(O observer) {
this.observers.remove(observer);
}
/**
* Notify observers.
*/
@SuppressWarnings("unchecked")
public void notifyObservers(A argument) {
for (O observer : observers) {
observer.update((S) this, argument);
}
}
}
/**
* GWeather.
*/
public class GWeather extends Observable<GWeather, Race, WeatherType> {
private static final Logger LOGGER = LoggerFactory.getLogger(GWeather.class);
private WeatherType currentWeather;
public GWeather() {
currentWeather = WeatherType.SUNNY;
}
/**
* Makes time pass for weather.
*/
public void timePasses() {
WeatherType[] enumValues = WeatherType.values();
currentWeather = enumValues[(currentWeather.ordinal() + 1) % enumValues.length];
LOGGER.info("The weather changed to {}.", currentWeather);
notifyObservers(currentWeather);
}
}
/**
* Observer.
*
* @param <S> Observable
* @param <O> Observer
* @param <A> Action
*/
public interface Observer<S extends Observable<S, O, A>, O extends Observer<S, O, A>, A> {
void update(S subject, A argument);
}
/**
* Race.
*/
public interface Race extends Observer<GWeather, Race, WeatherType> {
}
/**
* GHobbits.
*/
public class GHobbits implements Race {
private static final Logger LOGGER = LoggerFactory.getLogger(GHobbits.class);
@Override
public void update(GWeather weather, WeatherType weatherType) {
switch (weatherType) {
case COLD:
LOGGER.info("The hobbits are shivering in the cold weather.");
break;
case RAINY:
LOGGER.info("The hobbits look for cover from the rain.");
break;
case SUNNY:
LOGGER.info("The happy hobbits bade in the warm sun.");
break;
case WINDY:
LOGGER.info("The hobbits hold their hats tightly in the windy weather.");
break;
default:
break;
}
}
}
/**
* GOrcs.
*/
public class GOrcs implements Race {
private static final Logger LOGGER = LoggerFactory.getLogger(GOrcs.class);
@Override
public void update(GWeather weather, WeatherType weatherType) {
switch (weatherType) {
case COLD:
LOGGER.info("The orcs are freezing cold.");
break;
case RAINY:
LOGGER.info("The orcs are dripping wet.");
break;
case SUNNY:
LOGGER.info("The sun hurts the orcs' eyes.");
break;
case WINDY:
LOGGER.info("The orc smell almost vanishes in the wind.");
break;
default:
break;
}
}
}
State(状态模式)
意图
允许对象在其内部状态更改时更改其行为。对象将显示为更改其类。
适用性
- 对象的行为取决于其状态,并且它必须在运行时根据该状态更改其行为
- 操作具有依赖于对象状态的大型多部分条件语句。此状态通常由一个或多个枚举常量表示。
- 通常,多个操作将包含相同的条件结构。状态模式将条件的每个分支放在单独的类中。这使您可以将对象的状态视为一个独立于其他对象的对象。
策略模式和状态模式是双胞胎
允许对象在内部状态改变时,改变它的行为,对象看起来好像修改了它的类。
- 使用场景:
- 行为随状态改变而改变的场景
- 条件、分支语句的替代者
- 优点:
- 结构清晰,避免了多重判断
- 遵循开闭原则和单一职责原则
- 封装性好,外部不知道因为状态改变引起行为改变
- 缺点:
- 状态类膨胀
- 注意事项:
- 行为受状态约束的情况下使用状态模式
状态模式允许一个对象基于内部状态而拥有不同的行为
和程序状态机(PSM)不同,状态模式用类代表状态
Context会将行为委托给当前状态
通过将每个状态封装进一个类,我们把以后需要做的任何更改都局部化了
状态模式和策略模式有相同的类图,但是他们的意图不同
策略模式通常会用行为或算法来配置Context类
状态模式允许Context随着状态的改变而改变行为
状态转换可以由State类或Context控制
使用状态模式通常会导致类的数目大量增加
状态类可以被多个Context实例共享
案例
- javax.faces.lifecycle.Lifecycle#execute() controlled by FacesServlet, the behavior is dependent on current phase of lifecycle.
- JDiameter - Diameter State Machine
- Design Patterns: Elements of Reusable Object-Oriented Software
/**
* Mammoth has internal state that defines its behavior.
*/
public class Mammoth {
private State state;
public Mammoth() {
state = new PeacefulState(this);
}
/**
* Makes time pass for the mammoth.
*/
public void timePasses() {
if (state.getClass().equals(PeacefulState.class)) {
changeStateTo(new AngryState(this));
} else {
changeStateTo(new PeacefulState(this));
}
}
private void changeStateTo(State newState) {
this.state = newState;
this.state.onEnterState();
}
@Override
public String toString() {
return "The mammoth";
}
public void observe() {
this.state.observe();
}
}
/**
* State interface.
*/
public interface State {
void onEnterState();
void observe();
}
/**
* Angry state.
*/
public class AngryState implements State {
private static final Logger LOGGER = LoggerFactory.getLogger(AngryState.class);
private Mammoth mammoth;
public AngryState(Mammoth mammoth) {
this.mammoth = mammoth;
}
@Override
public void observe() {
LOGGER.info("{} is furious!", mammoth);
}
@Override
public void onEnterState() {
LOGGER.info("{} gets angry!", mammoth);
}
}
/**
* Peaceful state.
*/
public class PeacefulState implements State {
private static final Logger LOGGER = LoggerFactory.getLogger(PeacefulState.class);
private Mammoth mammoth;
public PeacefulState(Mammoth mammoth) {
this.mammoth = mammoth;
}
@Override
public void observe() {
LOGGER.info("{} is calm and peaceful.", mammoth);
}
@Override
public void onEnterState() {
LOGGER.info("{} calms down.", mammoth);
}
}
var mammoth = new Mammoth();
mammoth.observe();
mammoth.timePasses();
mammoth.observe();
mammoth.timePasses();
mammoth.observe();
红绿灯
public interface State {
void handle();
}
public abstract class AbstractState implements State {
TrafficLightContext trafficLightContext;
private String name;
private Double i = 1d;
AbstractState(TrafficLightContext trafficLightContext, String name, Double i) {
this.trafficLightContext = trafficLightContext;
this.name = name;
if (i != null) {
this.i = i;
}
}
@Override
public void handle() {
System.out.println(MessageFormat.format("{0} light", name));
try {
Thread.sleep(new Double(i * 1000).longValue());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class GreenState extends AbstractState {
GreenState(TrafficLightContext trafficLightContext) {
super(trafficLightContext, "green", 1d);
}
@Override
public void handle() {
super.handle();
trafficLightContext.setState(trafficLightContext.getYellowState());
}
}
public class RedState extends AbstractState {
RedState(TrafficLightContext trafficLightContext) {
super(trafficLightContext, "red", 1d);
}
@Override
public void handle() {
super.handle();
trafficLightContext.setState(trafficLightContext.getGreenState());
}
}
public class YellowState extends AbstractState {
YellowState(TrafficLightContext trafficLightContext) {
super(trafficLightContext, "yellow", 0.5);
}
@Override
public void handle() {
super.handle();
trafficLightContext.setState(trafficLightContext.getRedState());
}
}
public class TrafficLightContext {
// TODO:状态对象应该定义成常量
private YellowState yellowState;
private RedState redState;
private GreenState greenState;
private State state;
TrafficLightContext() {
this.yellowState = new YellowState(this);
this.redState = new RedState(this);
this.greenState = new GreenState(this);
this.state = redState;
}
public void change() {
// 委托执行
state.handle();
}
public YellowState getYellowState() {
return yellowState;
}
public RedState getRedState() {
return redState;
}
public GreenState getGreenState() {
return greenState;
}
public void setState(State state) {
this.state = state;
}
}
@Test
public void change() throws Exception {
TrafficLightContext trafficLightContext = new TrafficLightContext();
int i = 10;
while (i > 0) {
trafficLightContext.change();
i--;
}
}
Chain of responsibility(责任链模式)
意图
通过给多个对象处理请求的机会,避免将请求的发送方耦合到其接收方。链接接收对象并沿链传递请求,直到对象处理它为止。
它有助于构建对象链。请求从一端进入,不断地从一个对象进入另一个对象,直到找到合适的处理程序为止。
适用性
- 一个以上的对象可以处理一个请求,而处理程序不是先验的。处理程序应自动确定
- 您希望在不显式指定接收器的情况下向多个对象之一发出请求
- 应该动态指定可以处理请求的对象集
优点:
- 将请求的发送者和接受者解耦
- 可以简化你的对象,因为他不需要知道链的结构
- 通过改变链内部的成员或调动它的次序,允许你动态的增加或删除责任
缺点:
- 并不能保证请求一定会被执行,如果没有任何对象处理它的话,它可能落到链微端之外
- 可能不容易观察运行时特征,有碍于除错
- 节点过多引发的性能问题,调试也变的困难
案例
- java.util.logging.Logger#log()
- Apache Commons Chain
- javax.servlet.Filter#doFilter()
- Design Patterns: Elements of Reusable Object-Oriented Software
用途:
- 经常被使用在窗口系统中,处理鼠标和键盘的事件
纯的与不纯的责任链模式
一个纯的责任链模式要求一个具体的处理者对象只能在两个行为中选择一个:一是承担责任,而是把责任推给下家。不允许出现某一个具体处理者对象在承担了一部分责任后又 把责任向下传的情况。
- 在一个纯的责任链模式里面,一个请求必须被某一个处理者对象所接收;
- 在一个不纯的责任链模式里面,一个请求可以最终不被任何接收端对象所接收。
纯的的责任链模式
public abstract class Handler {
/**
* 持有后继的责任对象
*/
protected Handler successor;
/**
* 示意处理请求的方法,虽然这个示意方法是没有传入参数的
* 但实际是可以传入参数的,根据具体需要来选择是否传递参数
*/
public abstract void handleRequest();
/**
* 取值方法
*/
public Handler getSuccessor() {
return successor;
}
/**
* 赋值方法,设置后继的责任对象
*/
public void setSuccessor(Handler successor) {
this.successor = successor;
}
}
public class ConcreteHandler extends Handler {
/**
* 处理方法,调用此方法处理请求
*/
@Override
public void handleRequest() {
// 首先判断是否应该处理请求
// ..
// 然后判断是否应该放过请求
/**
* 判断是否有后继的责任对象
* 如果有,就转发请求给后继的责任对象
* 如果没有,则处理请求
*/
if (getSuccessor() != null) {
System.out.println("放过请求");
getSuccessor().handleRequest();
} else {
System.out.println("处理请求");
}
}
}
//组装责任链
Handler handler1 = new ConcreteHandler();
Handler handler2 = new ConcreteHandler();
handler1.setSuccessor(handler2);
//提交请求
handler1.handleRequest();
不纯的责任链模式
public interface Filter {
void doFilter(Request request, Response response, FilterChain filterChain);
}
public class MyFirstFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain filterChain) {
request.input += "first";
filterChain.doFilter(request, response, filterChain);
response.output += "first";
}
}
public class MySecondFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain filterChain) {
request.input += "second";
filterChain.doFilter(request, response, filterChain);
response.output += "second";
}
}
//过滤器链
public class FilterChain {
private List<Filter> filters = new ArrayList<Filter>();
Iterator<Filter> iterator;
public FilterChain addFilter(Filter filter) {
filters.add(filter);
return this;
}
public FilterChain addFilter(FilterChain filterChain) {
filters.addAll(filterChain.filters);
return this;
}
public void doFilter(Request request, Response response, FilterChain filterChain) {
if (iterator == null) {
iterator = filters.iterator();
}
if (iterator.hasNext()) {
iterator.next().doFilter(request, response, filterChain);
}
}
}
Command\Action\Transaction(命令模式)
意图
将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作
适用性
- 通过要执行的操作参数化对象。你可以用一个回调函数,也就是说,一个注册在某个地方稍后调用的函数,在过程语言中表达这样的参数化。命令是回调的面向对象替换。
- 在不同的时间指定、排队和执行请求。命令对象的生存期可以独立于原始请求。如果请求的接收者可以独立于地址空间的方式表示,那么您可以将请求的命令对象传输到另一个进程并在那里完成请求
- 支持撤消。命令的execute操作可以存储状态,以便在命令本身中反转其效果。命令接口必须有一个添加的Unexecute操作,该操作会反转要执行的前一个调用的效果。执行的命令存储在历史记录列表中。通过分别向后和向前遍历该列表来调用unexecute和execute,可以实现无限级别的undo和redo
- 支持记录更改,以便在系统崩溃时可以重新应用这些更改。通过使用加载和存储操作扩充命令接口,可以保留更改的持久日志。从崩溃中恢复包括从磁盘重新加载记录的命令,并使用execute操作重新执行这些命令
- 以基本操作为基础,围绕高级操作构建系统。这种结构在支持事务的信息系统中很常见。事务封装了对数据的一组更改。命令模式提供了一种对事务建模的方法。命令有一个公共接口,允许您以相同的方式调用所有事务。该模式还使使用新事务扩展系统变得容易
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
- 需要在不同的时间指定请求、将请求排队
命令模式将发出请求的对象和执行请求的对象解耦
在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接受者和一个或一组动作
调用者通过调用命令对象的execute()发出请求,这会使得接受者的动作被调用
调用者可以接受命令当做参数,甚至在运行时动态的进行
命令可以支持撤销,做法是实现一个undo()方法来回到execute()被执行前的状态
宏命令是命令的一种简单延伸,允许调用多个命令,宏方法也可以支持撤销
实际操作时,很常见使用“聪明”命令对象,也就是直接实现了请求,而不是将工作委托给接受者
命令也可以用来实现日志和事务系统
命令模式可以结合责任链模式,实现命令族的解析任务 命令模式可以结合模版方法模式,减少命令子类的膨胀
- 保留请求的历史记录
- 实现回调功能
- 实现撤消功能
案例
- java.lang.Runnable
- org.junit.runners.model.Statement
- Netflix Hystrix
- javax.swing.Action
- Design Patterns: Elements of Reusable Object-Oriented Software
/**
* Interface for Commands.
*/
public abstract class Command {
public abstract void execute(Target target);
public abstract void undo();
public abstract void redo();
}
/**
* InvisibilitySpell is a concrete command.
*/
@ToString
public class InvisibilitySpell extends Command {
private Target target;
@Override
public void execute(Target target) {
target.setVisibility(Visibility.INVISIBLE);
this.target = target;
}
@Override
public void undo() {
if (target != null) {
target.setVisibility(Visibility.VISIBLE);
}
}
@Override
public void redo() {
if (target != null) {
target.setVisibility(Visibility.INVISIBLE);
}
}
}
/**
* ShrinkSpell is a concrete command.
*/
@ToString
public class ShrinkSpell extends Command {
private Size oldSize;
private Target target;
@Override
public void execute(Target target) {
oldSize = target.getSize();
target.setSize(Size.SMALL);
this.target = target;
}
@Override
public void undo() {
if (oldSize != null && target != null) {
var temp = target.getSize();
target.setSize(oldSize);
oldSize = temp;
}
}
@Override
public void redo() {
undo();
}
}
/**
* Base class for spell targets.
*/
@Data
@Slf4j
public abstract class Target {
private Size size;
private Visibility visibility;
/**
* Print status.
*/
public void printStatus() {
log.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility());
}
}
/**
* Goblin is the target of the spells.
*/
@ToString
public class Goblin extends Target {
public Goblin() {
setSize(Size.NORMAL);
setVisibility(Visibility.VISIBLE);
}
}
/**
* Wizard is the invoker of the commands.
*/
@ToString
@Slf4j
public class Wizard {
private Deque<Command> undoStack = new LinkedList<>();
private Deque<Command> redoStack = new LinkedList<>();
public Wizard() {
// comment to ignore sonar issue: LEVEL critical
}
/**
* Cast spell.
*/
public void castSpell(Command command, Target target) {
log.info("{} casts {} at {}", this, command, target);
command.execute(target);
undoStack.offerLast(command);
}
/**
* Undo last spell.
*/
public void undoLastSpell() {
if (!undoStack.isEmpty()) {
var previousSpell = undoStack.pollLast();
redoStack.offerLast(previousSpell);
log.info("{} undoes {}", this, previousSpell);
previousSpell.undo();
}
}
/**
* Redo last spell.
*/
public void redoLastSpell() {
if (!redoStack.isEmpty()) {
var previousSpell = redoStack.pollLast();
undoStack.offerLast(previousSpell);
log.info("{} redoes {}", this, previousSpell);
previousSpell.redo();
}
}
}
var wizard = new Wizard();
var goblin = new Goblin();
goblin.printStatus();
wizard.castSpell(new ShrinkSpell(), goblin);
goblin.printStatus();
wizard.castSpell(new InvisibilitySpell(), goblin);
goblin.printStatus();
wizard.undoLastSpell();
goblin.printStatus();
wizard.undoLastSpell();
goblin.printStatus();
wizard.redoLastSpell();
goblin.printStatus();
wizard.redoLastSpell();
goblin.printStatus();
Servant(雇工模式/仆人模式)
意图
服务对象用于为一组类提供一些行为。在Servant中定义一次,而不是在每个类中定义该行为。或者当我们无法在公共父类中考虑该行为时适用。
雇工模式是命令模式的简化
适用性
当我们希望一些对象执行一个公共操作,而不希望在每个类中将此操作定义为一个方法时。
案例
/**
* Royalty
*/
public interface Royalty {
void getFed();
void getDrink();
void changeMood();
void receiveCompliments();
boolean getMood();
}
/**
* King
*/
public class King implements Royalty {
private boolean isDrunk;
private boolean isHungry = true;
private boolean isHappy;
private boolean complimentReceived;
@Override
public void getFed() {
isHungry = false;
}
@Override
public void getDrink() {
isDrunk = true;
}
@Override
public void receiveCompliments() {
complimentReceived = true;
}
@Override
public void changeMood() {
if (!isHungry && isDrunk) {
isHappy = true;
}
if (complimentReceived) {
isHappy = false;
}
}
@Override
public boolean getMood() {
return isHappy;
}
}
/**
*
* Queen
*
*/
public class Queen implements Royalty {
private boolean isDrunk = true;
private boolean isHungry;
private boolean isHappy;
private boolean isFlirty = true;
private boolean complimentReceived;
@Override
public void getFed() {
isHungry = false;
}
@Override
public void getDrink() {
isDrunk = true;
}
@Override
public void receiveCompliments() {
complimentReceived = true;
}
@Override
public void changeMood() {
if (complimentReceived && isFlirty && isDrunk && !isHungry) {
isHappy = true;
}
}
@Override
public boolean getMood() {
return isHappy;
}
public void setFlirtiness(boolean f) {
this.isFlirty = f;
}
}
/**
*
* Servant
*
*/
public class Servant {
public String name;
/**
* Constructor
*/
public Servant(String name) {
this.name = name;
}
public void feed(Royalty r) {
r.getFed();
}
public void giveWine(Royalty r) {
r.getDrink();
}
public void giveCompliments(Royalty r) {
r.receiveCompliments();
}
/**
* Check if we will be hanged
*/
public boolean checkIfYouWillBeHanged(List<Royalty> tableGuests) {
boolean anotherDay = true;
for (Royalty r : tableGuests) {
if (!r.getMood()) {
anotherDay = false;
}
}
return anotherDay;
}
}
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
static Servant jenkins = new Servant("Jenkins");
static Servant travis = new Servant("Travis");
/**
* Program entry point
*/
public static void main(String[] args) {
scenario(jenkins, 1);
scenario(travis, 0);
}
/**
* Can add a List with enum Actions for variable scenarios
*/
public static void scenario(Servant servant, int compliment) {
King k = new King();
Queen q = new Queen();
List<Royalty> guests = new ArrayList<>();
guests.add(k);
guests.add(q);
// feed
servant.feed(k);
servant.feed(q);
// serve drinks
servant.giveWine(k);
servant.giveWine(q);
// compliment
servant.giveCompliments(guests.get(compliment));
// outcome of the night
for (Royalty r : guests) {
r.changeMood();
}
// check your luck
if (servant.checkIfYouWillBeHanged(guests)) {
LOGGER.info("{} will live another day", servant.name);
} else {
LOGGER.info("Poor {}. His days are numbered", servant.name);
}
}
}
Service Locator(服务定位器)
意图
用一个强大的抽象层封装获取服务所涉及的过程。
当我们想要使用JNDI定位/获取各种服务时,服务定位器模式是适用的,这通常是一种冗余和昂贵的查找。 服务定位器模式通过使用缓存技术来解决这种昂贵的查找,即,在第一次请求特定服务时,服务定位器在JNDI中查找,获取相关服务,然后最终缓存此服务对象。 现在,通过服务定位器对同一个服务在其缓存中进行了进一步的查找,极大地提高了应用程序的性能。
适用性
- 当网络访问成本高昂且耗时时
- 查找服务的频率很高
- 正在使用大量的服务
后果
- 违反了接口隔离原则(ISP),向模式使用者提供了他们可能不需要的一些服务的访问权。
- 创建可在运行时中断客户端的隐藏依赖项。
案例
/**
* This is going to be the parent service interface which we will use to create our services. All
* services will have a <ul><li>service name</li> <li>unique id</li> <li>execution work flow</li></ul>
*/
public interface Service {
/**
* The human readable name of the service
*/
String getName();
/**
* Unique ID of the particular service
*/
int getId();
/**
* The workflow method that defines what this service does
*/
void execute();
}
/**
* This is a single service implementation of a sample service. This is the actual service that will
* process the request. The reference for this service is to be looked upon in the JNDI server that
* can be set in the web.xml deployment descriptor
*
* @author saifasif
*/
public class ServiceImpl implements Service {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceImpl.class);
private final String serviceName;
private final int id;
/**
* Constructor
*/
public ServiceImpl(String serviceName) {
// set the service name
this.serviceName = serviceName;
// Generate a random id to this service object
this.id = (int) Math.floor(Math.random() * 1000) + 1;
}
@Override
public String getName() {
return serviceName;
}
@Override
public int getId() {
return id;
}
@Override
public void execute() {
LOGGER.info("Service {} is now executing with id {}", getName(), getId());
}
}
/**
* The service cache implementation which will cache services that are being created. On first hit,
* the cache will be empty and thus any service that is being requested, will be created fresh and
* then placed into the cache map. On next hit, if same service name will be requested, it will be
* returned from the cache
*/
public class ServiceCache {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceCache.class);
private final Map<String, Service> serviceCache;
public ServiceCache() {
serviceCache = new HashMap<>();
}
/**
* Get the service from the cache. null if no service is found matching the name
*
* @param serviceName a string
* @return {@link Service}
*/
public Service getService(String serviceName) {
Service cachedService = null;
for (String serviceJndiName : serviceCache.keySet()) {
if (serviceJndiName.equals(serviceName)) {
cachedService = serviceCache.get(serviceJndiName);
LOGGER.info("(cache call) Fetched service {}({}) from cache... !",
cachedService.getName(), cachedService.getId());
}
}
return cachedService;
}
/**
* Adds the service into the cache map
*
* @param newService a {@link Service}
*/
public void addService(Service newService) {
serviceCache.put(newService.getName(), newService);
}
}
/**
* The service locator module. Will fetch service from cache, otherwise creates a fresh service and
* update cache
*/
public final class ServiceLocator {
private static ServiceCache serviceCache = new ServiceCache();
private ServiceLocator() {
}
/**
* Fetch the service with the name param from the cache first, if no service is found, lookup the
* service from the {@link InitContext} and then add the newly created service into the cache map
* for future requests.
*
* @param serviceJndiName a string
* @return {@link Service}
*/
public static Service getService(String serviceJndiName) {
Service serviceObj = serviceCache.getService(serviceJndiName);
if (serviceObj != null) {
return serviceObj;
} else {
/*
* If we are unable to retrive anything from cache, then lookup the service and add it in the
* cache map
*/
InitContext ctx = new InitContext();
serviceObj = (Service) ctx.lookup(serviceJndiName);
if (serviceObj != null) { // Only cache a service if it actually exists
serviceCache.addService(serviceObj);
}
return serviceObj;
}
}
}
/**
* For JNDI lookup of services from the web.xml. Will match name of the service name that is being
* requested and return a newly created service object with the name
*/
public class InitContext {
private static final Logger LOGGER = LoggerFactory.getLogger(InitContext.class);
/**
* Perform the lookup based on the service name. The returned object will need to be casted into a
* {@link Service}
*
* @param serviceName a string
* @return an {@link Object}
*/
public Object lookup(String serviceName) {
if (serviceName.equals("jndi/serviceA")) {
LOGGER.info("Looking up service A and creating new service for A");
return new ServiceImpl("jndi/serviceA");
} else if (serviceName.equals("jndi/serviceB")) {
LOGGER.info("Looking up service B and creating new service for B");
return new ServiceImpl("jndi/serviceB");
} else {
return null;
}
}
}
/**
*
* The Service Locator pattern is a design pattern used in software development to encapsulate the
* processes involved in obtaining a service with a strong abstraction layer. This pattern uses a
* central registry known as the "service locator", which on request returns the information
* necessary to perform a certain task.
*
* In this example we use the Service locator pattern to lookup JNDI-services and cache them for
* subsequent requests.
*/
public class App {
public static void main(String[] args) {
Service service = ServiceLocator.getService("jndi/serviceA");
service.execute();
service = ServiceLocator.getService("jndi/serviceB");
service.execute();
service = ServiceLocator.getService("jndi/serviceA");
service.execute();
service = ServiceLocator.getService("jndi/serviceA");
service.execute();
}
}
Visitor(访问者模式)
意图
表示要对对象结构的元素执行的操作。Visitor允许您定义一个新操作,而不必更改其操作的元素的类。
适用性
- 对象结构包含许多具有不同接口的对象类,您希望对这些对象执行依赖于其具体类的操作
- 许多不同的和不相关的操作需要在对象结构中的对象上执行,并且您希望避免用这些操作“污染”它们的类。Visitor允许您通过在一个类中定义相关操作来将它们放在一起。当对象结构被许多应用程序共享时,使用Visitor只在那些需要它们的应用程序中放置操作
- 定义对象结构的类很少更改,但您通常希望在结构上定义新操作。更改对象结构类需要重新定义所有访问者的接口,这可能会导致成本高昂。如果对象结构类经常更改,那么最好在这些类中定义操作
封装一些作用于某数种数据结构中的各元素的操作,它可以再不改变数据结构的前提下定义作用于这些元素的新的操作
当你需要为一个对象的组合增加新的能力,且封装并不重要时,使用访问者模式
访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。
- 使用场景:
- 一个对象结构包含很多结构不同的类对象,迭代器模式无法胜任的时候(避免instanceOf检查)
- 需要对一个对象结构中进行很多不同且不相关的操作的时候
- 使用访问者模式避免污染类对象,封装操作
- 优点:
- 允许你对组合结构加入新的操作,而无需改变结构本身
- 想要加入新的操作,相对容易
- 访问者所进行的操作,其代码时集中在一起的,符合单一职责原则,扩展性优秀
- 缺点:
- 会打破组合类的封装,不符合迪米特法则
- 游走的功能牵涉其中,所以对组合结构的改变就更加困难
- 违背了依赖倒置原则
案例
- Apache Wicket component tree, see MarkupContainer
- javax.lang.model.element.AnnotationValue and AnnotationValueVisitor
- javax.lang.model.element.Element and Element Visitor
- java.nio.file.FileVisitor
- Design Patterns: Elements of Reusable Object-Oriented Software
/**
* Interface for the nodes in hierarchy.
*/
public abstract class Unit {
private Unit[] children;
public Unit(Unit... children) {
this.children = children;
}
/**
* Accept visitor.
*/
public void accept(UnitVisitor visitor) {
Arrays.stream(children).forEach(child -> child.accept(visitor));
}
}
/**
* Commander.
*/
public class Commander extends Unit {
public Commander(Unit... children) {
super(children);
}
@Override
public void accept(UnitVisitor visitor) {
visitor.visitCommander(this);
super.accept(visitor);
}
@Override
public String toString() {
return "commander";
}
}
/**
* Sergeant.
*/
public class Sergeant extends Unit {
public Sergeant(Unit... children) {
super(children);
}
@Override
public void accept(UnitVisitor visitor) {
visitor.visitSergeant(this);
super.accept(visitor);
}
@Override
public String toString() {
return "sergeant";
}
}
/**
* Soldier.
*/
public class Soldier extends Unit {
public Soldier(Unit... children) {
super(children);
}
@Override
public void accept(UnitVisitor visitor) {
visitor.visitSoldier(this);
super.accept(visitor);
}
@Override
public String toString() {
return "soldier";
}
}
/**
* Visitor interface.
*/
public interface UnitVisitor {
void visitSoldier(Soldier soldier);
void visitSergeant(Sergeant sergeant);
void visitCommander(Commander commander);
}
/**
* CommanderVisitor.
*/
public class CommanderVisitor implements UnitVisitor {
private static final Logger LOGGER = LoggerFactory.getLogger(CommanderVisitor.class);
@Override
public void visitSoldier(Soldier soldier) {
// Do nothing
}
@Override
public void visitSergeant(Sergeant sergeant) {
// Do nothing
}
@Override
public void visitCommander(Commander commander) {
LOGGER.info("Good to see you {}", commander);
}
}
/**
* SergeantVisitor.
*/
public class SergeantVisitor implements UnitVisitor {
private static final Logger LOGGER = LoggerFactory.getLogger(SergeantVisitor.class);
@Override
public void visitSoldier(Soldier soldier) {
// Do nothing
}
@Override
public void visitSergeant(Sergeant sergeant) {
LOGGER.info("Hello {}", sergeant);
}
@Override
public void visitCommander(Commander commander) {
// Do nothing
}
}
/**
* SoldierVisitor.
*/
public class SoldierVisitor implements UnitVisitor {
private static final Logger LOGGER = LoggerFactory.getLogger(SoldierVisitor.class);
@Override
public void visitSoldier(Soldier soldier) {
LOGGER.info("Greetings {}", soldier);
}
@Override
public void visitSergeant(Sergeant sergeant) {
// Do nothing
}
@Override
public void visitCommander(Commander commander) {
// Do nothing
}
}
var commander = new Commander(
new Sergeant(new Soldier(), new Soldier(), new Soldier()),
new Sergeant(new Soldier(), new Soldier(), new Soldier())
);
commander.accept(new SoldierVisitor());
commander.accept(new SergeantVisitor());
commander.accept(new CommanderVisitor());
public interface Visitor {
/**
* 对应于NodeA的访问操作
*/
public void visit(NodeA node);
/**
* 对应于NodeB的访问操作
*/
public void visit(NodeB node);
}
public class VisitorA implements Visitor {
/**
* 对应于NodeA的访问操作
*/
@Override
public void visit(NodeA node) {
System.out.println(node.operationA());
}
/**
* 对应于NodeB的访问操作
*/
@Override
public void visit(NodeB node) {
System.out.println(node.operationB());
}
}
public class VisitorB implements Visitor {
/**
* 对应于NodeA的访问操作
*/
@Override
public void visit(NodeA node) {
System.out.println(node.operationA());
}
/**
* 对应于NodeB的访问操作
*/
@Override
public void visit(NodeB node) {
System.out.println(node.operationB());
}
}
public abstract class Node {
/**
* 接受操作
*/
public abstract void accept(Visitor visitor);
}
public class NodeA extends Node{
/**
* 接受操作
*/
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
/**
* NodeA特有的方法
*/
public String operationA(){
return "NodeA";
}
}
public class NodeB extends Node{
/**
* 接受方法
*/
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
/**
* NodeB特有的方法
*/
public String operationB(){
return "NodeB";
}
}
// 结构对象角色类,这个结构对象角色持有一个聚集,并向外界提供add()方法作为对聚集的管理操作。
// 通过调用这个方法,可以动态地增加一个新的节点。
// 虽然在这个示意性的实现里并没有出现一个复杂的具有多个树枝节点的对象树结构,
// 但是,在实际系统中访问者模式通常是用来处理复杂的对象树结构的,而且访问者模式可以用来处理跨越多个等级结构的树结构问题。
public class ObjectStructure {
private List<Node> nodes = new ArrayList<Node>();
/**
* 执行方法操作
*/
public void action(Visitor visitor){
for(Node node : nodes) {
node.accept(visitor);
}
}
/**
* 添加一个新元素
*/
public void add(Node node){
nodes.add(node);
}
}
ObjectStructure os = new ObjectStructure();
//给结构增加一个节点
os.add(new NodeA());
//给结构增加一个节点
os.add(new NodeB());
//创建一个访问者
Visitor visitor = new VisitorA();
os.action(visitor);
Strategy/Policy(策略模式)
意图
定义一系列算法,封装每一个算法,并使它们可以互换。策略允许算法独立于使用它的客户机而变化。
适用性
- 许多相关的类只在行为上有所不同。策略提供了一种配置类的方法,可以是多种行为之一
- 你需要不同的算法变体。例如,您可以定义反映不同空间/时间权衡的算法。当这些变量被实现为算法的类层次结构时,可以使用策略
- 算法使用客户不应该知道的数据。使用策略模式避免暴露复杂的、特定于算法的数据结构
- 类定义了许多行为,这些行为在其操作中显示为多个条件语句。将相关的条件分支移到自己的策略类中,而不是许多条件分支
具体比较方法交给不同的装配策略实现,AOP中的advice也是策略模式
- 适用场景:
- 多个类只有算法或者行为上略有不同的场景
- 算法需要自由切换的场景
- 需要屏蔽算法规则的场景
- 优点:
- 算法可以自由切换
- 避免多重条件判断
- 扩展性良好
- 缺点:
- 策略类数量膨胀
- 所有策略类都要对外暴漏,违背了迪米特法则 + 使用工厂方法模式、代理模式、享元模式弥补
- 注意事项:
- 如果一个算法家族的具体策略超过四个,考虑使用复合模式防止策略类膨胀
案例
- Strategy Pattern Tutorial
- Design Patterns: Elements of Reusable Object-Oriented Software
- Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions
/**
* DragonSlayer uses different strategies to slay the dragon.
*/
public class DragonSlayer {
private DragonSlayingStrategy strategy;
public DragonSlayer(DragonSlayingStrategy strategy) {
this.strategy = strategy;
}
public void changeStrategy(DragonSlayingStrategy strategy) {
this.strategy = strategy;
}
public void goToBattle() {
strategy.execute();
}
}
/**
* Strategy interface.
*/
@FunctionalInterface
public interface DragonSlayingStrategy {
void execute();
}
/**
* Melee strategy.
*/
public class MeleeStrategy implements DragonSlayingStrategy {
private static final Logger LOGGER = LoggerFactory.getLogger(MeleeStrategy.class);
@Override
public void execute() {
LOGGER.info("With your Excalibur you sever the dragon's head!");
}
}
/**
* Projectile strategy.
*/
public class ProjectileStrategy implements DragonSlayingStrategy {
private static final Logger LOGGER = LoggerFactory.getLogger(ProjectileStrategy.class);
@Override
public void execute() {
LOGGER.info("You shoot the dragon with the magical crossbow and it falls dead on the ground!");
}
}
// GoF Strategy pattern
var dragonSlayer = new DragonSlayer(new MeleeStrategy());
dragonSlayer.goToBattle();
LOGGER.info("Red dragon emerges.");
dragonSlayer.changeStrategy(new ProjectileStrategy());
dragonSlayer.goToBattle();
LOGGER.info("Black dragon lands before you.");
dragonSlayer.changeStrategy(new SpellStrategy());
dragonSlayer.goToBattle();
Comparable和Comparator
public class MyNumber implements Comparable<MyNumber> {
int value;
private Comparator<MyNumber> myNumberComparator;
public MyNumber(Comparator<MyNumber> myNumberComparator) {
this.myNumberComparator = myNumberComparator;
}
@Override
public int compareTo(MyNumber o) {
return myNumberComparator.compare(this, o);
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
public class MyComparator implements Comparator<MyNumber> {
@Override
public int compare(MyNumber o1, MyNumber o2) {
return o1.value - o2.value;
}
}
Memento/Token(备忘录模式)
意图
在不违反封装的情况下,捕获并外部化对象的内部状态,以便以后可以将对象还原到该状态。
适用性
- 必须保存对象状态的快照,以便以后可以将其还原到该状态,并且
- 获取状态的直接接口将公开实现细节并破坏对象的封装
当你让对象返回之前的状态时,使用备忘录模式
在不破环封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态
- 目标:
- 储存系统关键对象的重要状态
- 维护关键对象的封装
- 优点:
- 将被储存的状态放在外面,不要和关键对象混在一起
- 保持关键对象的数据封装
- 提供了容易实现的恢复能力
- 缺点:
- 储存和恢复状态的过程可能相当耗时
- 考虑使用序列化机制替代
- 注意事项:
- 备忘录就近使用,建立即使用,不使用即废弃,等待垃圾回收
- 大对象建立备忘录耗费资源
- 不要在建立备份频繁的场景使用,无法控制备忘录建立的对象数量
- “白箱”备忘录模式
- 备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。(Memento作为公开的外部类)
- “黑箱”备忘录模式
- 备忘录角色对发起人(Originator)角色对象提供一个宽接口,而为其他对象提供一个窄接口(Memento作为内部类,向外提供MementoIF接口的实现类,而不暴漏Memento具体类)
案例
/**
* External interface to memento.
*/
public interface StarMemento {
}
/**
* Star uses "mementos" to store and restore state.
*/
public class Star {
private StarType type;
private int ageYears;
private int massTons;
/**
* Constructor.
*/
public Star(StarType startType, int startAge, int startMass) {
this.type = startType;
this.ageYears = startAge;
this.massTons = startMass;
}
/**
* Makes time pass for the star.
*/
public void timePasses() {
ageYears *= 2;
massTons *= 8;
switch (type) {
case RED_GIANT:
type = StarType.WHITE_DWARF;
break;
case SUN:
type = StarType.RED_GIANT;
break;
case SUPERNOVA:
type = StarType.DEAD;
break;
case WHITE_DWARF:
type = StarType.SUPERNOVA;
break;
case DEAD:
ageYears *= 2;
massTons = 0;
break;
default:
break;
}
}
StarMemento getMemento() {
StarMementoInternal state = new StarMementoInternal();
state.setAgeYears(ageYears);
state.setMassTons(massTons);
state.setType(type);
return state;
}
void setMemento(StarMemento memento) {
StarMementoInternal state = (StarMementoInternal) memento;
this.type = state.getType();
this.ageYears = state.getAgeYears();
this.massTons = state.getMassTons();
}
@Override
public String toString() {
return String.format("%s age: %d years mass: %d tons", type.toString(), ageYears, massTons);
}
/**
* StarMemento implementation.
*/
@Data
private static class StarMementoInternal implements StarMemento {
private StarType type;
private int ageYears;
private int massTons;
}
}
Stack<StarMemento> states = new Stack<>();
Star star = new Star(StarType.SUN, 10000000, 500000);
log.info(star.toString());
states.add(star.getMemento());
star.timePasses();
log.info(star.toString());
states.add(star.getMemento());
star.timePasses();
log.info(star.toString());
states.add(star.getMemento());
star.timePasses();
log.info(star.toString());
states.add(star.getMemento());
star.timePasses();
log.info(star.toString());
while (!states.isEmpty()) {
star.setMemento(states.pop());
log.info(star.toString());
}
多重检查点(白箱)
// 发起人角色
public class Originator {
private List<String> states;
// 检查点指数
private int index;
// 构造函数
public Originator(){
states = new ArrayList<String>();
index = 0;
}
// 工厂方法,返还一个新的备忘录对象
public Memento createMemento(){
return new Memento(states , index);
}
// 将发起人恢复到备忘录对象记录的状态上
public void restoreMemento(Memento memento){
states = memento.getStates();
index = memento.getIndex();
}
// 状态的赋值方法
public void setState(String state){
states.add(state);
index++;
}
// 辅助方法,打印所有状态
public void printStates(){
for(String state : states){
System.out.println(state);
}
}
}
public class Memento {
@Getter
private List<String> states;
@Getter
private int index;
public Memento(List<String> states , int index){
this.states = new ArrayList<String>(states);
this.index = index;
}
}
public class Caretaker {
private Originator o;
private List<Memento> mementos = new ArrayList<Memento>();
private int current;
public Caretaker(Originator o){
this.o = o;
current = 0;
}
// 创建一个新的检查点
public int createMemento(){
Memento memento = o.createMemento();
mementos.add(memento);
return current++;
}
// 将发起人恢复到某个检查点
public void restoreMemento(int index){
Memento memento = mementos.get(index);
o.restoreMemento(memento);
}
// 将某个检查点删除
public void removeMemento(int index){
mementos.remove(index);
}
}
Originator o = new Originator();
Caretaker c = new Caretaker(o);
//改变状态
o.setState("state 0");
//建立一个检查点
c.createMemento();
//改变状态
o.setState("state 1");
//建立一个检查点
c.createMemento();
//改变状态
o.setState("state 2");
//建立一个检查点
c.createMemento();
//改变状态
o.setState("state 3");
//建立一个检查点
c.createMemento();
//打印出所有检查点
o.printStates();
System.out.println("-----------------恢复检查点-----------------");
//恢复到第二个检查点
c.restoreMemento(2);
//打印出所有检查点
o.printStates();
Iterator/Cursor(迭代器模式)
意图
提供一种在不公开其底层表示的情况下按顺序访问聚合对象的元素的方法。
适用性
- 访问聚合对象的内容而不公开其内部表示
- 支持聚合对象的多个遍历
- 提供用于遍历不同聚合结构的统一接口
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴漏其内部的表示
把游走的任务放在迭代器上,而不是聚合上,这样简化了聚合的接口和实现,也让责任各得其所
迭代器允许访问聚合的元素,而不需要暴漏它的内在结构
迭代器将遍历聚合的工作封装近一个对象中
当使用迭代器的时候,我们依赖聚合提供遍历
迭代器提供了一个通用的接口,让我们遍历聚合的项,当我们编码使用聚合的项时,就可以使用多态机制
我们应该努力让一个类只分配一个责任
案例
- java.util.Iterator
- java.util.Enumeration
- Design Patterns: Elements of Reusable Object-Oriented Software
// Jdk的Iterator接口
public interface Iterator<E> {
boolean hasNext();
E next();
// defalut方法不用实现
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext()){
action.accept(next());
}
}
}
public class MyIntegerIterator implements Iterator<Integer> {
private Integer integer;
public MyIntegerIterator(Integer integer) {
this.integer = integer;
}
public boolean hasNext() {
return integer < Integer.MAX_VALUE;
}
public Integer next() {
//自动拆箱 return this.integer = this.integer.intValue() + 1;
return ++integer;
}
}
Interpreter(解释器模式)
意图
给定一种语言,为其语法定义一个表示,并定义一个使用该表示解释该语言中的句子的解释器。
适用性
- 语法很简单。
- 对于复杂的语法,语法的类层次结构变得很大,无法管理。
- 在这种情况下,解析器生成器之类的工具是更好的选择。它们可以在不构建抽象语法树的情况下解释表达式,这样可以节省空间和时间
- 效率不是一个关键问题。
- 最有效的解释器通常不是通过直接解释解析树来实现的,而是首先将它们转换成另一种形式。
- 例如,正则表达式通常被转换为状态机。但即使这样,翻译程序也可以通过解释器模式实现,因此该模式仍然适用
使用场景:
- 重复发生的问题可以使用解释器模式(终结符表达式相同,非终结符表达式定制)
- 简单语法解析,复杂使用其他替代库
优点:
- 将每一个Context规则表示成一个类,方便与实现语言
- 因为Context由许多类表示,所以你可以轻易的扩展此Context(扩展表达式)
- 通过在类结构中加入新的方法,可以在解释的同时增加新的行为
缺点:
- 当Context规则数目太大时,这个模式可能会变的非常繁杂
- 表达式类膨胀
- 递归导致调试困难
- 递归和循环导致效率低下
注意事项:
- 不要在重要模块中使用解释器模式,使用shell、JRuby、Groovy等脚本替代解释器模式
- 考虑使用Expression4J、MESP(Math Expression String Parser)、Jep等开源的解析包替代
案例
- java.util.Pattern
- java.text.Normalizer
- All subclasses of java.text.Format
- javax.el.ELResolver
- Design Patterns: Elements of Reusable Object-Oriented Software
public interface Expression<T> {
T interpret(Context context);
}
public class VarExpression implements Expression<Integer> {
private String key;
public VarExpression(String key) {
this.key = key;
}
public Integer interpret(Context context) {
return (Integer) context.getExpression(this.key).interpret(context);
}
}
public abstract class SymbolExpression<T> implements Expression<T> {
protected Expression<T> left;
protected Expression<T> right;
//所有的解析公式都应只关心自己左右两个表达式的结果
public SymbolExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
}
public class AddExpression implements Expression<Integer> {
private Expression<Integer> left;
private Expression<Integer> right;
public AddExpression(Expression<Integer> left, Expression<Integer> right) {
this.left = left;
this.right = right;
}
@Override
public Integer interpret(Context context) {
return left.interpret(context) + right.interpret(context);
}
}
public class SubExpression extends SymbolExpression {
private Expression<Integer> left;
private Expression<Integer> right;
public SubExpression(Expression left, Expression right) {
super(left, right);
this.left = left;
this.right = right;
}
@Override
public Integer interpret(Context context) {
return left.interpret(context) - right.interpret(context);
}
}
public class Context {
private Map<String, Expression> stringExpressionMap = new HashMap<>();
public Context() {
stringExpressionMap.put("1", context -> (1));
stringExpressionMap.put("2", context -> (2));
stringExpressionMap.put("3", context -> (3));
stringExpressionMap.put("4", context -> (4));
stringExpressionMap.put("5", context -> (5));
stringExpressionMap.put("6", context -> (6));
stringExpressionMap.put("7", context -> (7));
stringExpressionMap.put("8", context -> (8));
stringExpressionMap.put("9", context -> (9));
stringExpressionMap.put("0", context -> (0));
}
public Expression getExpression(String str) {
return stringExpressionMap.get(str);
}
public void addExpression(String str, Expression expression) {
this.stringExpressionMap.put(str, expression);
}
}
public class Calculator<T> {
//定义表达式
private Expression expression;
//构造函数传参,并解析
public Calculator(String expStr) {
//定义一个栈,安排运算的先后顺序
Stack<Expression> stack = new Stack<Expression>();
//表达式拆分为字符数组
char[] charArray = expStr.toCharArray();
//运算
Expression left = null;
Expression right = null;
for (int i = 0; i < charArray.length; i++) {
switch (charArray[i]) {
case '+': //加法
//加法结果放到栈中
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new AddExpression(left, right));
break;
case '-':
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new SubExpression(left, right));
break;
default: //公式中的变量
stack.push(new VarExpression(String.valueOf(charArray[i])));
}
}
//把运算结果抛出来
this.expression = stack.pop();
}
//开始运算
public T run(Context context) {
return (T) this.expression.interpret(context);
}
}
System.out.println(new Calculator<Integer>("1-2+3-4-6+5+7-8+9+0").run(new Context()));
Mediator(中介者模式/调停者模式)
意图
定义一个对象,该对象封装一组对象如何交互。Mediator通过防止对象显式地相互引用来促进松耦合,并且它允许您独立地改变它们的交互。
适用性
- 一组对象以定义良好但复杂的方式进行通信。由此产生的相互依赖关系是非结构化的,很难理解
- 重用一个对象是困难的,因为它引用并与许多其他对象通信
- 在几个类之间分布的行为应该是可定制的,而不需要很多子类
中介者模式集中相关对象之间复杂的沟通和控制方式
- 用途:
- 常被用于协调相关的GUI组件
- 优点:
- 通过将对象彼此解耦(一对多依赖转换为一对一依赖),可以增加对象的复用性
- 通过将控制逻辑集中,可以简化系统维护
- 可以让对象之间所传递的消息变得简单且大幅减少
- 缺点:
- 设计不当,增加复杂度,同事类越多,中介者越复杂
- 往往与依赖倒置原则冲突,具有继承带来的侵入性
- 最佳实践:
- N个对象之间产生了相互依赖(N>2)
- 多个对象之间有依赖,但依赖关系尚不确定或有发生改变的可能,使用中介者模式降低风险扩散
案例
- All scheduleXXX() methods of java.util.Timer
- java.util.concurrent.Executor#execute()
- submit() and invokeXXX() methods of java.util.concurrent.ExecutorService
- scheduleXXX() methods of java.util.concurrent.ScheduledExecutorService
- java.lang.reflect.Method#invoke()
- Design Patterns: Elements of Reusable Object-Oriented Software
/**
* Party interface.
*/
public interface Party {
void addMember(PartyMember member);
void act(PartyMember actor, Action action);
}
/**
* Party implementation.
*/
public class PartyImpl implements Party {
private final List<PartyMember> members;
public PartyImpl() {
members = new ArrayList<>();
}
@Override
public void act(PartyMember actor, Action action) {
for (PartyMember member : members) {
if (!member.equals(actor)) {
member.partyAction(action);
}
}
}
@Override
public void addMember(PartyMember member) {
members.add(member);
member.joinedParty(this);
}
}
/**
* Interface for party members interacting with {@link Party}.
*/
public interface PartyMember {
void joinedParty(Party party);
void partyAction(Action action);
void act(Action action);
}
/**
* Abstract base class for party members.
*/
@Slf4j
public abstract class PartyMemberBase implements PartyMember {
protected Party party;
@Override
public void joinedParty(Party party) {
log.info("{} joins the party", this);
this.party = party;
}
@Override
public void partyAction(Action action) {
log.info("{} {}", this, action.getDescription());
}
@Override
public void act(Action action) {
if (party != null) {
log.info("{} {}", this, action);
party.act(this, action);
}
}
@Override
public abstract String toString();
}
/**
* Hobbit party member.
*/
public class Hobbit extends PartyMemberBase {
@Override
public String toString() {
return "Hobbit";
}
}
/**
* Hunter party member.
*/
public class Hunter extends PartyMemberBase {
@Override
public String toString() {
return "Hunter";
}
}
// create party and members
Party party = new PartyImpl();
Hobbit hobbit = new Hobbit();
Wizard wizard = new Wizard();
Rogue rogue = new Rogue();
Hunter hunter = new Hunter();
// add party members
party.addMember(hobbit);
party.addMember(wizard);
party.addMember(rogue);
party.addMember(hunter);
// perform actions -> the other party members
// are notified by the party
hobbit.act(Action.ENEMY);
wizard.act(Action.TALE);
rogue.act(Action.GOLD);
hunter.act(Action.HUNT);
Acyclic Visitor(无环访问者)
意图
允许在不影响这些层次结构的情况下,将新的函数添加到现有的类层次结构中,并且不创建GOF访问者模式所固有的麻烦的依赖周期。
适用性
- 需要向现有层次结构中添加新函数而不需要更改或影响该层次结构时。
- 存在在层次结构上操作但不属于层次结构本身的函数时。例如,ConfigureForDOS/ConfigureForUnix/ConfigureForX问题。
- 需要根据对象的类型对其执行非常不同的操作时。
- 当访问的类层次结构将经常使用元素类的新派生来扩展时。
- 重新编译、重新连接、重新测试或重新分配元素的导数时,代价非常昂贵。
好处:
- 类层次结构之间没有依赖循环。
- 如果添加了新的访问者,则无需重新编译所有访问者。
- 如果类层次结构有新成员,则不会导致现有访问者中的编译失败。
坏处:
- 违反了最小惊喜原则或Liskov的替代原则,即它可以接受所有访客,但实际上只对特定访客感兴趣。
- 必须为visitable类层次结构中的所有成员创建访问者的并行层次结构。
案例
/**
* Modem abstract class
*/
public abstract class Modem {
public abstract void accept(ModemVisitor modemVisitor);
}
/**
* Hayes class implements its accept method
*/
@ToString
@Slf4j
public class Hayes extends Modem {
/**
* Accepts all visitors but honors only HayesVisitor
*/
@Override
public void accept(ModemVisitor modemVisitor) {
if (modemVisitor instanceof HayesVisitor) {
((HayesVisitor) modemVisitor).visit(this);
} else {
log.info("Only HayesVisitor is allowed to visit Hayes modem");
}
}
}
/**
* Zoom class implements its accept method
*/
@Slf4j
@ToString
public class Zoom extends Modem {
/**
* Accepts all visitors but honors only ZoomVisitor
*/
@Override
public void accept(ModemVisitor modemVisitor) {
if (modemVisitor instanceof ZoomVisitor) {
((ZoomVisitor) modemVisitor).visit(this);
} else {
log.info("Only ZoomVisitor is allowed to visit Zoom modem");
}
}
}
/**
* ModemVisitor interface does not contain any visit methods so that it does not
* depend on the visited hierarchy. Each derivative's visit method is declared in
* its own visitor interface
*/
public interface ModemVisitor {
// Visitor is a degenerate base class for all visitors.
}
/**
* ZoomVisitor interface
*/
public interface ZoomVisitor extends ModemVisitor {
void visit(Zoom zoom);
}
/**
* HayesVisitor interface
*/
public interface HayesVisitor extends ModemVisitor {
void visit(Hayes hayes);
}
/**
* All ModemVisitor interface extends all visitor interfaces. This interface
* provides ease of use when a visitor needs to visit all modem types.
*/
public interface AllModemVisitor extends ZoomVisitor, HayesVisitor {
}
/**
* ConfigureForDosVisitor class implements both zoom's and hayes' visit method
* for Dos manufacturer
*/
@Slf4j
public class ConfigureForDosVisitor implements AllModemVisitor {
@Override
public void visit(Hayes hayes) {
log.info(hayes + " used with Dos configurator.");
}
@Override
public void visit(Zoom zoom) {
log.info(zoom + " used with Dos configurator.");
}
}
/**
* ConfigureForUnixVisitor class implements zoom's visit method for Unix
* manufacturer, unlike traditional visitor pattern, this class may selectively implement
* visit for other modems.
*/
@Slf4j
public class ConfigureForUnixVisitor implements ZoomVisitor {
@Override
public void visit(Zoom zoom) {
log.info(zoom + " used with Unix configurator.");
}
}
ConfigureForUnixVisitor conUnix = new ConfigureForUnixVisitor();
ConfigureForDosVisitor conDos = new ConfigureForDosVisitor();
Zoom zoom = new Zoom();
Hayes hayes = new Hayes();
hayes.accept(conDos); // Hayes modem with Unix configurator ConfigureForDosVisitor - Hayes() used with Dos configurator.
zoom.accept(conDos); // Zoom modem with Dos configurator ConfigureForDosVisitor - Zoom() used with Dos configurator.
hayes.accept(conUnix); // Hayes modem with Unix configurator Hayes - Only HayesVisitor is allowed to visit Hayes modem
zoom.accept(conUnix); // Zoom modem with Unix configurator ConfigureForUnixVisitor - Zoom() used with Unix configurator.
Bytecode(字节码)
意图
允许将行为编码为虚拟机的指令。
适用性
当需要定义大量行为,并且游戏的实现语言不适合时,使用字节码模式
- 它太low-level了,使得编程变得乏味或容易出错。
- 由于编译速度慢或其他工具问题,迭代时间太长。
- 它有太多的信任。如果要确保所定义的行为不会破坏游戏,则需要从其余代码库中对其进行沙盒处理。
案例
/**
* Representation of instructions understandable by virtual machine.
*/
public enum Instruction {
LITERAL(1),
SET_HEALTH(2),
SET_WISDOM(3),
SET_AGILITY(4),
PLAY_SOUND(5),
SPAWN_PARTICLES(6),
GET_HEALTH(7),
GET_AGILITY(8),
GET_WISDOM(9),
ADD(10),
DIVIDE(11);
@Getter
private final int value;
Instruction(int value) {
this.value = value;
}
/**
* Converts integer value to Instruction.
*
* @param value value of instruction
* @return representation of the instruction
*/
public static Instruction getInstruction(int value) {
for (var i = 0; i < Instruction.values().length; i++) {
if (Instruction.values()[i].getValue() == value) {
return Instruction.values()[i];
}
}
throw new IllegalArgumentException("Invalid instruction value");
}
}
/**
* This class represent game objects which properties can be changed by instructions interpreted by
* virtual machine.
*/
@Slf4j
public class Wizard {
@Getter
@Setter
private int health;
@Getter
@Setter
private int agility;
@Getter
@Setter
private int wisdom;
@Getter
private int numberOfPlayedSounds;
@Getter
private int numberOfSpawnedParticles;
public void playSound() {
log.info("Playing sound");
numberOfPlayedSounds++;
}
public void spawnParticles() {
log.info("Spawning particles");
numberOfSpawnedParticles++;
}
}
/**
* Implementation of virtual machine.
*/
public class VirtualMachine {
private Stack<Integer> stack = new Stack<>();
private Wizard[] wizards = new Wizard[2];
/**
* Constructor.
*/
public VirtualMachine() {
for (var i = 0; i < wizards.length; i++) {
wizards[i] = new Wizard();
}
}
/**
* Executes provided bytecode.
*
* @param bytecode to execute
*/
public void execute(int[] bytecode) {
for (var i = 0; i < bytecode.length; i++) {
Instruction instruction = Instruction.getInstruction(bytecode[i]);
switch (instruction) {
case LITERAL:
// Read the next byte from the bytecode.
int value = bytecode[++i];
stack.push(value);
break;
case SET_AGILITY:
var amount = stack.pop();
var wizard = stack.pop();
setAgility(wizard, amount);
break;
case SET_WISDOM:
amount = stack.pop();
wizard = stack.pop();
setWisdom(wizard, amount);
break;
case SET_HEALTH:
amount = stack.pop();
wizard = stack.pop();
setHealth(wizard, amount);
break;
case GET_HEALTH:
wizard = stack.pop();
stack.push(getHealth(wizard));
break;
case GET_AGILITY:
wizard = stack.pop();
stack.push(getAgility(wizard));
break;
case GET_WISDOM:
wizard = stack.pop();
stack.push(getWisdom(wizard));
break;
case ADD:
var a = stack.pop();
var b = stack.pop();
stack.push(a + b);
break;
case DIVIDE:
a = stack.pop();
b = stack.pop();
stack.push(b / a);
break;
case PLAY_SOUND:
wizard = stack.pop();
getWizards()[wizard].playSound();
break;
case SPAWN_PARTICLES:
wizard = stack.pop();
getWizards()[wizard].spawnParticles();
break;
default:
throw new IllegalArgumentException("Invalid instruction value");
}
}
}
public Stack<Integer> getStack() {
return stack;
}
public void setHealth(int wizard, int amount) {
wizards[wizard].setHealth(amount);
}
public void setWisdom(int wizard, int amount) {
wizards[wizard].setWisdom(amount);
}
public void setAgility(int wizard, int amount) {
wizards[wizard].setAgility(amount);
}
public int getHealth(int wizard) {
return wizards[wizard].getHealth();
}
public int getWisdom(int wizard) {
return wizards[wizard].getWisdom();
}
public int getAgility(int wizard) {
return wizards[wizard].getAgility();
}
public Wizard[] getWizards() {
return wizards;
}
}
/**
* Utility class used for instruction validation and conversion.
*/
public class InstructionConverterUtil {
/**
* Converts instructions represented as String.
*
* @param instructions to convert
* @return array of int representing bytecode
*/
public static int[] convertToByteCode(String instructions) {
if (instructions == null || instructions.trim().length() == 0) {
return new int[0];
}
var splitedInstructions = instructions.trim().split(" ");
var bytecode = new int[splitedInstructions.length];
for (var i = 0; i < splitedInstructions.length; i++) {
if (isValidInstruction(splitedInstructions[i])) {
bytecode[i] = Instruction.valueOf(splitedInstructions[i]).getValue();
} else if (isValidInt(splitedInstructions[i])) {
bytecode[i] = Integer.parseInt(splitedInstructions[i]);
} else {
var errorMessage = "Invalid instruction or number: " + splitedInstructions[i];
throw new IllegalArgumentException(errorMessage);
}
}
return bytecode;
}
private static boolean isValidInstruction(String instruction) {
try {
Instruction.valueOf(instruction);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
private static boolean isValidInt(String value) {
try {
Integer.parseInt(value);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}
/**
* Main app method.
*
* @param args command line args
*/
public static void main(String[] args) {
var wizard = new Wizard();
wizard.setHealth(45);
wizard.setAgility(7);
wizard.setWisdom(11);
var vm = new VirtualMachine();
vm.getWizards()[0] = wizard;
interpretInstruction("LITERAL 0", vm);
interpretInstruction("LITERAL 0", vm);
interpretInstruction("GET_HEALTH", vm);
interpretInstruction("LITERAL 0", vm);
interpretInstruction("GET_AGILITY", vm);
interpretInstruction("LITERAL 0", vm);
interpretInstruction("GET_WISDOM ", vm);
interpretInstruction("ADD", vm);
interpretInstruction("LITERAL 2", vm);
interpretInstruction("DIVIDE", vm);
interpretInstruction("ADD", vm);
interpretInstruction("SET_HEALTH", vm);
}
private static void interpretInstruction(String instruction, VirtualMachine vm) {
vm.execute(InstructionConverterUtil.convertToByteCode(instruction));
var stack = vm.getStack();
log.info(instruction + String.format("%" + (12 - instruction.length()) + "s", "") + stack);
}
Dependency Injection(依赖注入)
意图
依赖项注入是一种软件设计模式,其中一个或多个依赖项(或服务)被注入或通过引用传递到依赖对象(或客户机)中,并成为客户机状态的一部分。 该模式将客户机依赖项的创建与其自身行为分离开来,从而允许程序设计松散耦合,并遵循控制反转和单一责任原则。
适用性
- 当需要从对象中移除具体实现的知识时
- 使用模拟对象或存根对独立的类启用单元测试
案例
Extension objects(扩展对象)
意图
预计将来需要扩展对象的接口。其他接口由扩展对象定义。
适用性
- 需要支持将新的或不可预见的接口添加到现有的类中,而不希望影响不需要这种新接口的客户端。扩展对象允许通过在单独的类中定义相关操作来将它们放在一起
- 表示密钥抽象的类对于不同的客户机扮演不同的角色。类可以扮演的角色数应该是开放的。有必要保留密钥抽象本身。
- 例如,一个customer对象仍然是一个customer对象,即使不同的子系统对它的看法不同。
- 类应该可以用新的行为进行扩展,而不必从中进行子类化。
案例
/**
* Other Extensions will extend this interface.
*/
public interface UnitExtension {
}
public interface SoldierExtension extends UnitExtension {
void soldierReady();
}
public interface SergeantExtension extends UnitExtension {
void sergeantReady();
}
public interface CommanderExtension extends UnitExtension {
void commanderReady();
}
/**
* Class defining Commander.
*/
public class Commander implements CommanderExtension {
private static final Logger LOGGER = LoggerFactory.getLogger(Commander.class);
private CommanderUnit unit;
public Commander(CommanderUnit commanderUnit) {
this.unit = commanderUnit;
}
@Override
public void commanderReady() {
LOGGER.info("[Commander] " + unit.getName() + " is ready!");
}
}
/**
* Class defining Sergeant.
*/
public class Sergeant implements SergeantExtension {
private static final Logger LOGGER = LoggerFactory.getLogger(Sergeant.class);
private SergeantUnit unit;
public Sergeant(SergeantUnit sergeantUnit) {
this.unit = sergeantUnit;
}
@Override
public void sergeantReady() {
LOGGER.info("[Sergeant] " + unit.getName() + " is ready! ");
}
}
/**
* Class defining Soldier.
*/
public class Soldier implements SoldierExtension {
private static final Logger LOGGER = LoggerFactory.getLogger(Soldier.class);
private SoldierUnit unit;
public Soldier(SoldierUnit soldierUnit) {
this.unit = soldierUnit;
}
@Override
public void soldierReady() {
LOGGER.info("[Solider] " + unit.getName() + " is ready!");
}
}
/**
* Class defining Unit, other units will extend this class.
*/
public class Unit {
@Getter
@Setter
private String name;
protected UnitExtension unitExtension = null;
public Unit(String name) {
this.name = name;
}
public UnitExtension getUnitExtension(String extensionName) {
return null;
}
}
public class SoldierUnit extends Unit {
public SoldierUnit(String name) {
super(name);
}
@Override
public UnitExtension getUnitExtension(String extensionName) {
if (extensionName.equals("SoldierExtension")) {
if (unitExtension == null) {
unitExtension = new Soldier(this);
}
return unitExtension;
}
return super.getUnitExtension(extensionName);
}
}
public class SergeantUnit extends Unit {
public SergeantUnit(String name) {
super(name);
}
@Override
public UnitExtension getUnitExtension(String extensionName) {
if (extensionName.equals("SergeantExtension")) {
if (unitExtension == null) {
unitExtension = new Sergeant(this);
}
return unitExtension;
}
return super.getUnitExtension(extensionName);
}
}
public class CommanderUnit extends Unit{}
public static void main(String[] args) {
//Create 3 different units
Unit soldierUnit = new SoldierUnit("SoldierUnit1");
Unit sergeantUnit = new SergeantUnit("SergeantUnit1");
Unit commanderUnit = new CommanderUnit("CommanderUnit1");
//check for each unit to have an extension
checkExtensionsForUnit(soldierUnit);
checkExtensionsForUnit(sergeantUnit);
checkExtensionsForUnit(commanderUnit);
}
private static void checkExtensionsForUnit(Unit unit) {
final Logger logger = LoggerFactory.getLogger(App.class);
SoldierExtension soldierExtension = (SoldierExtension) unit.getUnitExtension("SoldierExtension");
SergeantExtension sergeantExtension = (SergeantExtension) unit.getUnitExtension("SergeantExtension");
CommanderExtension commanderExtension = (CommanderExtension) unit.getUnitExtension("CommanderExtension");
//if unit have extension call the method
if (soldierExtension != null) {
soldierExtension.soldierReady();
} else {
logger.info(unit.getName() + " without SoldierExtension");
}
if (sergeantExtension != null) {
sergeantExtension.sergeantReady();
} else {
logger.info(unit.getName() + " without SergeantExtension");
}
if (commanderExtension != null) {
commanderExtension.commanderReady();
} else {
logger.info(unit.getName() + " without CommanderExtension");
}
}
Feature Toggle\Feature Flag(功能切换)
意图
用于根据属性或分组切换代码执行路径。允许发布、测试和推出新功能。允许在需要时快速切换回旧功能。 应该注意的是,这种模式,可以很容易地引入代码复杂度。 还有一个值得关注的问题是,切换最终将逐步淘汰的旧功能从未被删除,这会导致冗余的代码气味和增加的可维护性。
适用性
- 为不同的用户提供不同的功能。
- 逐步推出新功能。
- 在开发和生产环境之间切换。
案例
/**
* Simple interfaces to allow the calling of the method to generate the welcome message for a given user. While there is
* a helper method to gather the the status of the feature toggle. In some cases there is no need for the
* {@link Service#isEnhanced()} in {@link com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion}
* where the toggle is determined by the actual {@link User}.
*
* @see com.iluwatar.featuretoggle.pattern.propertiesversion.PropertiesFeatureToggleVersion
* @see com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion
* @see User
*/
public interface Service {
/**
* Generates a welcome message for the passed user.
*
* @param user the {@link User} to be used if the message is to be personalised.
* @return Generated {@link String} welcome message
*/
String getWelcomeMessage(User user);
/**
* Returns if the welcome message to be displayed will be the enhanced version.
*
* @return Boolean {@code true} if enhanced.
*/
boolean isEnhanced();
}
/**
* This example of the Feature Toogle pattern is less dynamic version than
* {@link com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion} where the feature is turned on
* or off at the time of creation of the service. This example uses simple Java {@link Properties} however it could as
* easily be done with an external configuration file loaded by Spring and so on. A good example of when to use this
* version of the feature toggle is when new features are being developed. So you could have a configuration property
* boolean named development or some sort of system environment variable.
*
* @see Service
* @see com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion
* @see User
*/
public class PropertiesFeatureToggleVersion implements Service {
private boolean isEnhanced;
/**
* Creates an instance of {@link PropertiesFeatureToggleVersion} using the passed {@link Properties} to determine,
* the status of the feature toggle {@link PropertiesFeatureToggleVersion#isEnhanced()}. There is also some
* defensive
* code to ensure the {@link Properties} passed are as expected.
*
* @param properties {@link Properties} used to configure the service and toggle features.
* @throws IllegalArgumentException when the passed {@link Properties} is not as expected
* @see Properties
*/
public PropertiesFeatureToggleVersion(final Properties properties) {
if (properties == null) {
throw new IllegalArgumentException("No Properties Provided.");
} else {
try {
isEnhanced = (boolean)properties.get("enhancedWelcome");
} catch (Exception e) {
throw new IllegalArgumentException("Invalid Enhancement Settings Provided.");
}
}
}
/**
* Generate a welcome message based on the user being passed and the status of the feature toggle. If the enhanced
* version is enabled, then the message will be personalised with the name of the passed {@link User}. However if
* disabled then a generic version fo the message is returned.
*
* @param user the {@link User} to be displayed in the message if the enhanced version is enabled see
* {@link PropertiesFeatureToggleVersion#isEnhanced()}. If the enhanced version is enabled, then the
* message will be personalised with the name of the passed {@link User}. However if disabled then a
* generic version fo the message is returned.
* @return Resulting welcome message.
* @see User
*/
@Override
public String getWelcomeMessage(final User user) {
if (isEnhanced()) {
return "Welcome " + user + ". You're using the enhanced welcome message.";
}
return "Welcome to the application.";
}
/**
* Method that checks if the welcome message to be returned is the enhanced venison or not. For this service it will
* see the value of the boolean that was set in the constructor
* {@link PropertiesFeatureToggleVersion#PropertiesFeatureToggleVersion(Properties)}
*
* @return Boolean value {@code true} if enhanced.
*/
@Override
public boolean isEnhanced() {
return isEnhanced;
}
}
/**
* This example of the Feature Toogle pattern shows how it could be implemented based on a {@link User}. Therefore
* showing its use within a tiered application where the paying users get access to different content or
* better versions of features. So in this instance a {@link User} is passed in and if they are found to be
* on the {@link UserGroup#isPaid(User)} they are welcomed with a personalised message. While the other is more
* generic. However this pattern is limited to simple examples such as the one below.
*
* @see Service
* @see User
* @see com.iluwatar.featuretoggle.pattern.propertiesversion.PropertiesFeatureToggleVersion
* @see UserGroup
*/
public class TieredFeatureToggleVersion implements Service {
/**
* Generates a welcome message from the passed {@link User}. The resulting message depends on the group of the
* {@link User}. So if the {@link User} is in the {@link UserGroup#paidGroup} then the enhanced version of the
* welcome message will be returned where the username is displayed.
*
* @param user the {@link User} to generate the welcome message for, different messages are displayed if the user is
* in the {@link UserGroup#isPaid(User)} or {@link UserGroup#freeGroup}
* @return Resulting welcome message.
* @see User
* @see UserGroup
*/
@Override
public String getWelcomeMessage(User user) {
if (UserGroup.isPaid(user)) {
return "You're amazing " + user + ". Thanks for paying for this awesome software.";
}
return "I suppose you can use this software.";
}
/**
* Method that checks if the welcome message to be returned is the enhanced version. For this instance as the logic
* is driven by the user group. This method is a little redundant. However can be used to show that there is an
* enhanced version available.
*
* @return Boolean value {@code true} if enhanced.
*/
@Override
public boolean isEnhanced() {
return true;
}
}
final Properties properties = new Properties();
properties.put("enhancedWelcome", true);
Service service = new PropertiesFeatureToggleVersion(properties);
final String welcomeMessage = service.getWelcomeMessage(new User("Jamie No Code"));
LOGGER.info(welcomeMessage);
final Properties turnedOff = new Properties();
turnedOff.put("enhancedWelcome", false);
Service turnedOffService = new PropertiesFeatureToggleVersion(turnedOff);
final String welcomeMessageturnedOff = turnedOffService.getWelcomeMessage(new User("Jamie No Code"));
LOGGER.info(welcomeMessageturnedOff);
Service service2 = new TieredFeatureToggleVersion();
final User paidUser = new User("Jamie Coder");
final User freeUser = new User("Alan Defect");
UserGroup.addUserToPaidGroup(paidUser);
UserGroup.addUserToFreeGroup(freeUser);
final String welcomeMessagePaidUser = service2.getWelcomeMessage(paidUser);
final String welcomeMessageFreeUser = service2.getWelcomeMessage(freeUser);
LOGGER.info(welcomeMessageFreeUser);
LOGGER.info(welcomeMessagePaidUser);
Intercepting Filter(拦截器过滤器)
意图
提供可插入过滤器,以对从客户端到目标的请求进行必要的预处理和后处理
适用性
- 系统使用预处理或后处理请求
- 系统应该对请求进行身份验证/授权/日志记录或跟踪,然后将请求传递给相应的处理程序
- 您需要一种模块化方法来配置预处理和后处理方案
案例
- Introduction to Intercepting Filter Pattern in Java
- javax.servlet.FilterChain and javax.servlet.Filter
- Struts 2 - Interceptors
- TutorialsPoint - Intercepting Filter
/**
* Filter Manager manages the filters and {@link FilterChain}.
*/
public class FilterManager {
private FilterChain filterChain;
public FilterManager() {
filterChain = new FilterChain();
}
public void addFilter(Filter filter) {
filterChain.addFilter(filter);
}
public String filterRequest(Order order) {
return filterChain.execute(order);
}
}
/**
* Filter Chain carries multiple filters and help to execute them in defined order on target.
*/
public class FilterChain {
private Filter chain;
/**
* Adds filter.
*/
public void addFilter(Filter filter) {
if (chain == null) {
chain = filter;
} else {
chain.getLast().setNext(filter);
}
}
/**
* Execute filter chain.
*/
public String execute(Order order) {
if (chain != null) {
return chain.execute(order);
} else {
return "RUNNING...";
}
}
}
/**
* Filters perform certain tasks prior or after execution of request by request handler. In this
* case, before the request is handled by the target, the request undergoes through each Filter
*/
public interface Filter {
/**
* Execute order processing filter.
*/
String execute(Order order);
/**
* Set next filter in chain after this.
*/
void setNext(Filter filter);
/**
* Get next filter in chain after this.
*/
Filter getNext();
/**
* Get last filter in the chain.
*/
Filter getLast();
}
/**
* Base class for order processing filters. Handles chain management.
*/
public abstract class AbstractFilter implements Filter {
private Filter next;
public AbstractFilter() {
}
public AbstractFilter(Filter next) {
this.next = next;
}
@Override
public void setNext(Filter filter) {
this.next = filter;
}
@Override
public Filter getNext() {
return next;
}
@Override
public Filter getLast() {
Filter last = this;
while (last.getNext() != null) {
last = last.getNext();
}
return last;
}
@Override
public String execute(Order order) {
if (getNext() != null) {
return getNext().execute(order);
} else {
return "";
}
}
}
/**
* Concrete implementation of filter This filter checks for the contact field in which it checks if
* the input consist of numbers and it also checks if the input follows the length constraint (11
* digits).
*/
public class ContactFilter extends AbstractFilter {
@Override
public String execute(Order order) {
String result = super.execute(order);
if (order.getContactNumber() == null || order.getContactNumber().isEmpty()
|| order.getContactNumber().matches(".*[^\\d]+.*")
|| order.getContactNumber().length() != 11) {
return result + "Invalid contact number! ";
} else {
return result;
}
}
}
/**
* Concrete implementation of filter This checks for the deposit code.
*/
public class DepositFilter extends AbstractFilter {
@Override
public String execute(Order order) {
String result = super.execute(order);
if (order.getDepositNumber() == null || order.getDepositNumber().isEmpty()) {
return result + "Invalid deposit number! ";
} else {
return result;
}
}
}
/**
* Concrete implementation of filter. This filter checks if the input in the Name field is valid.
* (alphanumeric)
*/
public class NameFilter extends AbstractFilter {
@Override
public String execute(Order order) {
String result = super.execute(order);
if (order.getName() == null || order.getName().isEmpty()
|| order.getName().matches(".*[^\\w|\\s]+.*")) {
return result + "Invalid name! ";
} else {
return result;
}
}
}
Null Object(空对象)
意图
在大多数面向对象语言(如Java或C#)中,引用可能为空。在调用任何方法之前,需要检查这些引用以确保它们不为空,因为通常不能对空引用调用方法。 不是使用空引用来传达对象的缺失(例如,不存在的客户),而是使用实现预期界面的对象,但其方法体是空的。 与正常工作的默认实现相比,这种方法的优势在于空对象是非常可预测的,并且没有副作用:它什么也不做。
适用性
- 希望避免显式的空检查,并保持算法优雅且易于阅读。
案例
/**
* Interface for binary tree node.
*/
public interface Node {
String getName();
int getTreeSize();
Node getLeft();
Node getRight();
void walk();
}
/**
* Implementation for binary tree's normal nodes.
*/
public class NodeImpl implements Node {
private static final Logger LOGGER = LoggerFactory.getLogger(NodeImpl.class);
private final String name;
private final Node left;
private final Node right;
/**
* Constructor.
*/
public NodeImpl(String name, Node left, Node right) {
this.name = name;
this.left = left;
this.right = right;
}
@Override
public int getTreeSize() {
return 1 + left.getTreeSize() + right.getTreeSize();
}
@Override
public Node getLeft() {
return left;
}
@Override
public Node getRight() {
return right;
}
@Override
public String getName() {
return name;
}
@Override
public void walk() {
LOGGER.info(name);
if (left.getTreeSize() > 0) {
left.walk();
}
if (right.getTreeSize() > 0) {
right.walk();
}
}
}
/**
* Null Object implementation for binary tree node.
*
* <p>Implemented as Singleton, since all the NullNodes are the same.
*/
public final class NullNode implements Node {
private static NullNode instance = new NullNode();
private NullNode() {
}
public static NullNode getInstance() {
return instance;
}
@Override
public int getTreeSize() {
return 0;
}
@Override
public Node getLeft() {
return null;
}
@Override
public Node getRight() {
return null;
}
@Override
public String getName() {
return null;
}
@Override
public void walk() {
// Do nothing
}
}
Pipeline(管道)
意图
允许在一系列阶段中处理数据,方法是提供初始输入并传递处理后的输出以供下一个阶段使用。
适用性
- 执行产生最终值的各个阶段
- 通过提供fluent builder作为接口,为复杂的操作序列增加可读性
- 提高代码的可测试性,因为阶段很可能只做一件事,遵守单一责任原则(SRP)
- 实现阶段并按顺序执行
案例
- java.util.Stream
- Maven Build Lifecycle
- Functional Java
- The Pipeline Pattern — for fun and profit
- The Pipeline design pattern (in Java)
-
[Pipelines Microsoft Docs](https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff963548(v=pandp.10))
/**
* Main Pipeline class that initially sets the current handler. Processed output of the initial
* handler is then passed as the input to the next stage handlers.
*
* @param <I> the type of the input for the first stage handler
* @param <O> the final stage handler's output type
*/
class Pipeline<I, O> {
private final Handler<I, O> currentHandler;
Pipeline(Handler<I, O> currentHandler) {
this.currentHandler = currentHandler;
}
<K> Pipeline<I, K> addHandler(Handler<O, K> newHandler) {
return new Pipeline<>(input -> newHandler.process(currentHandler.process(input)));
}
O execute(I input) {
return currentHandler.process(input);
}
}
/**
* Forms a contract to all stage handlers to accept a certain type of input and return a processed
* output.
*
* @param <I> the input type of the handler
* @param <O> the processed output type of the handler
*/
interface Handler<I, O> {
O process(I input);
}
/**
* Stage handler that converts an input String to its char[] array counterpart.
*/
class ConvertToCharArrayHandler implements Handler<String, char[]> {
private static final Logger LOGGER = LoggerFactory.getLogger(ConvertToCharArrayHandler.class);
@Override
public char[] process(String input) {
char[] characters = input.toCharArray();
LOGGER.info(String.format("Current handler: %s, input is %s of type %s, output is %s, of type %s",
ConvertToCharArrayHandler.class, input, String.class, Arrays.toString(characters), Character[].class));
return characters;
}
}
/**
* Stage handler that returns a new instance of String without the digit characters of the input
* string.
*/
class RemoveDigitsHandler implements Handler<String, String> {
private static final Logger LOGGER = LoggerFactory.getLogger(RemoveDigitsHandler.class);
@Override
public String process(String input) {
StringBuilder inputWithoutDigits = new StringBuilder();
for (int index = 0; index < input.length(); index++) {
char currentCharacter = input.charAt(index);
if (Character.isDigit(currentCharacter)) {
continue;
}
inputWithoutDigits.append(currentCharacter);
}
String inputWithoutDigitsStr = inputWithoutDigits.toString();
LOGGER.info(String.format("Current handler: %s, input is %s of type %s, output is %s, of type %s",
RemoveDigitsHandler.class, input, String.class, inputWithoutDigitsStr, String.class));
return inputWithoutDigitsStr;
}
}
/**
* Stage handler that returns a new instance of String without the alphabet characters of the input
* string.
*/
class RemoveAlphabetsHandler implements Handler<String, String> {
private static final Logger LOGGER = LoggerFactory.getLogger(RemoveAlphabetsHandler.class);
@Override
public String process(String input) {
StringBuilder inputWithoutAlphabets = new StringBuilder();
for (int index = 0; index < input.length(); index++) {
char currentCharacter = input.charAt(index);
if (Character.isAlphabetic(currentCharacter)) {
continue;
}
inputWithoutAlphabets.append(currentCharacter);
}
String inputWithoutAlphabetsStr = inputWithoutAlphabets.toString();
LOGGER.info(
String.format(
"Current handler: %s, input is %s of type %s, output is %s, of type %s",
RemoveAlphabetsHandler.class, input,
String.class, inputWithoutAlphabetsStr, String.class
)
);
return inputWithoutAlphabetsStr;
}
}
new Pipeline<>(new RemoveAlphabetsHandler())
.addHandler(new RemoveDigitsHandler())
.addHandler(new ConvertToCharArrayHandler());
Priority Queue Pattern(优先级队列模式)
意图
对发送到服务的请求进行优先级排序,以便接收和处理优先级较高的请求比处理优先级较低的请求更快。 此模式在为单个客户机提供不同服务级别保证的应用程序中非常有用。
应用程序可以将特定任务委托给其他服务;例如,执行后台处理或与其他应用程序或服务集成。 在云中,消息队列通常用于将任务委托给后台处理。在许多情况下,服务接收请求的顺序并不重要。 但是,在某些情况下,可能需要优先处理特定的请求。这些请求应比应用程序以前发送的优先级较低的其他请求更早处理。
适用性
- 系统必须处理可能具有不同优先级的多个任务。
- 应为不同的用户或租户提供不同优先级的服务。。
案例
- Priority Queue Pattern Microsoft Azure does not provide a queuing mechanism that natively support automatic prioritization of messages through sorting. However, it does provide Azure Service Bus topics and subscriptions, which support a queuing mechanism that provides message filtering, together with a wide range of flexible capabilities that make it ideal for use in almost all priority queue implementations.
Throttling(限流)
意图
确保给定的客户端无法访问超过分配的限制的服务资源。
适用性
- 需要限制服务访问以不对服务性能产生高影响时。
- 多个客户机使用相同的服务资源时,必须根据每个客户机的使用情况进行限制。
案例
/**
* An interface for defining the structure of different types of throttling ways.
*/
public interface Throttler {
void start();
}
/**
* Implementation of throttler interface. This class resets the counter every second.
*/
public class ThrottleTimerImpl implements Throttler {
private final int throttlePeriod;
private final CallsCount callsCount;
public ThrottleTimerImpl(int throttlePeriod, CallsCount callsCount) {
this.throttlePeriod = throttlePeriod;
this.callsCount = callsCount;
}
/**
* A timer is initiated with this method. The timer runs every second and resets the counter.
*/
@Override
public void start() {
new Timer(true).schedule(new TimerTask() {
@Override
public void run() {
callsCount.reset();
}
}, 0, throttlePeriod);
}
}
/**
* A class to keep track of the counter of different Tenants.
*/
public final class CallsCount {
private static final Logger LOGGER = LoggerFactory.getLogger(CallsCount.class);
private Map<String, AtomicLong> tenantCallsCount = new ConcurrentHashMap<>();
/**
* Add a new tenant to the map.
*
* @param tenantName name of the tenant.
*/
public void addTenant(String tenantName) {
tenantCallsCount.putIfAbsent(tenantName, new AtomicLong(0));
}
/**
* Increment the count of the specified tenant.
*
* @param tenantName name of the tenant.
*/
public void incrementCount(String tenantName) {
tenantCallsCount.get(tenantName).incrementAndGet();
}
/**
* Get count of tenant based on tenant name.
*
* @param tenantName name of the tenant.
* @return the count of the tenant.
*/
public long getCount(String tenantName) {
return tenantCallsCount.get(tenantName).get();
}
/**
* Resets the count of all the tenants in the map.
*/
public void reset() {
LOGGER.debug("Resetting the map.");
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
}
}
/**
* A Pojo class to create a basic Tenant with the allowed calls per second.
*/
public class Tenant {
@Getter
private String name;
@Getter
private int allowedCallsPerSecond;
/**
* Constructor.
*
* @param name Name of the tenant
* @param allowedCallsPerSecond The number of calls allowed for a particular tenant.
* @throws InvalidParameterException If number of calls is less than 0, throws exception.
*/
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
if (allowedCallsPerSecond < 0) {
throw new InvalidParameterException("Number of calls less than 0 not allowed");
}
this.name = name;
this.allowedCallsPerSecond = allowedCallsPerSecond;
callsCount.addTenant(name);
}
}
/**
* A service which accepts a tenant and throttles the resource based on the time given to the tenant.
*/
class B2BService {
private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
private final CallsCount callsCount;
public B2BService(Throttler timer, CallsCount callsCount) {
this.callsCount = callsCount;
timer.start();
}
/**
* Calls dummy customer api.
*
* @return customer id which is randomly generated
*/
public int dummyCustomerApi(Tenant tenant) {
var tenantName = tenant.getName();
var count = callsCount.getCount(tenantName);
LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
if (count >= tenant.getAllowedCallsPerSecond()) {
LOGGER.error("API access per second limit reached for: {}", tenantName);
return -1;
}
callsCount.incrementCount(tenantName);
return getRandomCustomerId();
}
private int getRandomCustomerId() {
return ThreadLocalRandom.current().nextInt(1, 10000);
}
}
public static void main(String[] args) {
var callsCount = new CallsCount();
var adidas = new Tenant("Adidas", 5, callsCount);
var nike = new Tenant("Nike", 6, callsCount);
var executorService = Executors.newFixedThreadPool(2);
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
executorService.execute(() -> makeServiceCalls(nike, callsCount));
executorService.shutdown();
try {
executorService.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOGGER.error("Executor Service terminated: {}", e.getMessage());
}
}
/**
* Make calls to the B2BService dummy API.
*/
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
var timer = new ThrottleTimerImpl(10, callsCount);
var service = new B2BService(timer, callsCount);
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
IntStream.range(0, 20).forEach(i -> {
service.dummyCustomerApi(tenant);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
LOGGER.error("Thread interrupted: {}", e.getMessage());
}
});
}
Trampoline(蹦床)
意图
蹦床模式用于在Java中递归地实现算法,而无需吹扫堆栈,并在不硬编码的情况下交叉执行函数, 可以通过以下两种状态之一来表示计算:done | more(用结果完成,或引用计算提醒,就像java.util.Supplier那样)。
蹦床模式允许通过迭代循环定义递归算法。
适用性
- 实现尾部递归函数。此模式允许打开无堆栈操作。
- 在同一线程上交错执行两个或多个函数。
案例
- 蹦床是指使用反射来避免使用内部类,例如在事件侦听器中。反射调用的时间开销与内部类的空间开销交换。Java中的蹦床通常需要创建一个GenericListener来将事件传递给外部类。
- Trampolining: a practical guide for awesome Java Developers
- Trampoline in java
- library ‘cyclops-react’ uses the pattern
/**
* Trampoline pattern allows to define recursive algorithms by iterative loop.
*
* <p>When get is called on the returned Trampoline, internally it will iterate calling ‘jump’
* on the returned Trampoline as long as the concrete instance returned is {@link
* #more(Trampoline)}, stopping once the returned instance is {@link #done(Object)}.
*
* <p>Essential we convert looping via recursion into iteration,
* the key enabling mechanism is the fact that {@link #more(Trampoline)} is a lazy operation.
*
* @param <T> is type for returning result.
*/
public interface Trampoline<T> {
T get();
/**
* Jump to next stage.
*
* @return next stage
*/
default Trampoline<T> jump() {
return this;
}
default T result() {
return get();
}
/**
* Checks if complete.
*
* @return true if complete
*/
default boolean complete() {
return true;
}
/**
* Created a completed Trampoline.
*
* @param result Completed result
* @return Completed Trampoline
*/
static <T> Trampoline<T> done(final T result) {
return () -> result;
}
/**
* Create a Trampoline that has more work to do.
*
* @param trampoline Next stage in Trampoline
* @return Trampoline with more work
*/
static <T> Trampoline<T> more(final Trampoline<Trampoline<T>> trampoline) {
return new Trampoline<T>() {
@Override
public boolean complete() {
return false;
}
@Override
public Trampoline<T> jump() {
return trampoline.result();
}
@Override
public T get() {
return trampoline(this);
}
T trampoline(final Trampoline<T> trampoline) {
return Stream.iterate(trampoline, Trampoline::jump)
.filter(Trampoline::complete)
.findFirst()
.map(Trampoline::result)
.orElseThrow();
}
};
}
}
public static void main(String[] args) {
log.info("start pattern");
var result = loop(10, 1).result();
log.info("result {}", result);
}
/**
* Manager for pattern. Define it with a factorial function.
*/
public static Trampoline<Integer> loop(int times, int prod) {
if (times == 0) {
return Trampoline.done(prod);
} else {
return Trampoline.more(() -> loop(times - 1, prod * times));
}
}
Type-object(类型对象)
意图
通过创建一个类来允许灵活创建新的“类”,每个类代表一个不同类型的对象
适用性
- 不知道需要什么类型的。
- 希望能够修改或添加新类型,而不必重新编译或更改代码。
- 不同“类型”对象之间的唯一区别是数据,而不是行为。
案例
- Game Programming Patterns/Type Object by Robert Nystrom
- [http://www.cs.sjsu.edu/~pearce/modules/patterns/analysis/top.htm]
API Gateway(API 网关)
意图
在单个位置聚合调用微服务:API网关。 用户只需调用API网关,然后API网关就会调用每个相关的微服务。
适用性
- 当使用微服务模式,并且需要为微服务调用提供单点聚合
案例
@RestController
public class ApiGateway {
@Resource
private ImageClient imageClient;
@Resource
private PriceClient priceClient;
/**
* Retrieves product information that desktop clients need
*
* @return Product information for clients on a desktop
*/
@RequestMapping("/desktop")
public DesktopProduct getProductDesktop() {
DesktopProduct desktopProduct = new DesktopProduct();
desktopProduct.setImagePath(imageClient.getImagePath());
desktopProduct.setPrice(priceClient.getPrice());
return desktopProduct;
}
/**
* Retrieves product information that mobile clients need
*
* @return Product information for clients on a mobile device
*/
@RequestMapping("/mobile")
public MobileProduct getProductMobile() {
MobileProduct mobileProduct = new MobileProduct();
mobileProduct.setPrice(priceClient.getPrice());
return mobileProduct;
}
}
/**
* Exposes the Image microservice's endpoints
*/
@RestController
public class ImageController {
/**
* An endpoint for a user to retrieve an image path
* @return An image path
*/
@RequestMapping(value = "/image-path", method = RequestMethod.GET)
public String getImagePath() {
return "/product-image.png";
}
}
/**
* Exposes the Price microservice's endpoints
*/
@RestController
public class PriceController {
/**
* An endpoint for a user to retrieve a product's price
*
* @return A product's price
*/
@RequestMapping(value = "/price", method = RequestMethod.GET)
public String getPrice() {
return "20";
}
}
Aggregator Microservices(聚合器微服务)
意图
用户对聚合器进行一次调用,然后聚合器调用每个相关的微服务并收集数据,将业务逻辑应用于它,并进一步发布作为REST端点。
聚合器的更多变化是:
- 代理微服务设计模式:根据业务需要调用不同的微服务。
- 链式微服务设计模式:在这种情况下,每个微服务依赖/链接到一系列其他微服务。
适用性
当需要为各种微服务提供统一的API时,无论客户端设备如何,都可以使用聚合器微服务模式。
案例
/**
* The aggregator aggregates calls on various micro-services, collects
* data and further publishes them under a REST endpoint.
*/
@RestController
public class Aggregator {
@Resource
private ProductInformationClient informationClient;
@Resource
private ProductInventoryClient inventoryClient;
/**
* Retrieves product data.
*
* @return a Product.
*/
@RequestMapping("/product")
public Product getProduct() {
Product product = new Product();
product.setTitle(informationClient.getProductTitle());
product.setProductInventories(inventoryClient.getProductInventories());
return product;
}
}
/**
* Encapsulates all the data for a Product that clients will request.
*/
@Data
public class Product {
/**
* The title of the product.
*/
private String title;
/**
* The inventories of the product.
*/
private int productInventories;
}
/**
* Interface for the Information micro-service.
*/
public interface ProductInformationClient {
String getProductTitle();
}
/**
* An adapter to communicate with information micro-service.
*/
@Component
public class ProductInformationClientImpl implements ProductInformationClient {
private static final Logger LOGGER = LoggerFactory.getLogger(ProductInformationClientImpl.class);
@Override
public String getProductTitle() {
String response = null;
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet("http://localhost:51515/information");
try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) {
response = EntityUtils.toString(httpResponse.getEntity());
}
} catch (IOException e) {
LOGGER.error("Exception caught.", e);
}
return response;
}
}
/**
* Interface to Inventory micro-service.
*/
public interface ProductInventoryClient {
int getProductInventories();
}
/**
* An adapter to communicate with inventory micro-service.
*/
@Component
public class ProductInventoryClientImpl implements ProductInventoryClient {
private static final Logger LOGGER = LoggerFactory.getLogger(ProductInventoryClientImpl.class);
@Override
public int getProductInventories() {
String response = "0";
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet("http://localhost:51516/inventories");
try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) {
response = EntityUtils.toString(httpResponse.getEntity());
}
} catch (IOException e) {
LOGGER.error("Exception caught.", e);
}
return Integer.parseInt(response);
}
}
/**
* Controller providing endpoints to retrieve information about products
*/
@RestController
public class InformationController {
/**
* Endpoint to retrieve a product's informations.
*
* @return product inventory.
*/
@RequestMapping(value = "/information", method = RequestMethod.GET)
public String getProductTitle() {
return "The Product Title.";
}
}
/**
* Controller providing endpoints to retrieve product inventories
*/
@RestController
public class InventoryController {
/**
* Endpoint to retrieve a product's inventories.
*
* @return product inventory.
*/
@RequestMapping(value = "/inventories", method = RequestMethod.GET)
public int getProductInventories() {
return 5;
}
}
CQRS(命令查询分离)
意图
CQRS命令查询责任隔离-将查询侧与命令端分开。
适用性
- 想要独立地扩展查询和命令。
- 希望为查询和命令使用不同的数据模型。处理复杂域时很有用。
- 希望使用事件源或基于任务的UI等架构。
案例
- Greg Young - CQRS, Task Based UIs, Event Sourcing agh!
- Martin Fowler - CQRS
- Oliver Wolf - CQRS for Great Good
/**
* This interface represents the commands of the CQRS pattern
*/
public interface ICommandService {
void authorCreated(String username, String name, String email);
void bookAddedToAuthor(String title, double price, String username);
void authorNameUpdated(String username, String name);
void authorUsernameUpdated(String oldUsername, String newUsername);
void authorEmailUpdated(String username, String email);
void bookTitleUpdated(String oldTitle, String newTitle);
void bookPriceUpdated(String title, double price);
}
/**
* This class is an implementation of {@link ICommandService} interface. It uses Hibernate as an api for persistence.
*/
public class CommandServiceImpl implements ICommandService {
private SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
private Author getAuthorByUsername(String username) {
Author author = null;
try (Session session = sessionFactory.openSession()) {
Query query = session.createQuery("from Author where username=:username");
query.setParameter("username", username);
author = (Author) query.uniqueResult();
}
if (author == null) {
HibernateUtil.getSessionFactory().close();
throw new NullPointerException("Author " + username + " doesn't exist!");
}
return author;
}
private Book getBookByTitle(String title) {
Book book = null;
try (Session session = sessionFactory.openSession()) {
Query query = session.createQuery("from Book where title=:title");
query.setParameter("title", title);
book = (Book) query.uniqueResult();
}
if (book == null) {
HibernateUtil.getSessionFactory().close();
throw new NullPointerException("Book " + title + " doesn't exist!");
}
return book;
}
@Override
public void authorCreated(String username, String name, String email) {
Author author = new Author(username, name, email);
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.save(author);
session.getTransaction().commit();
}
}
@Override
public void bookAddedToAuthor(String title, double price, String username) {
Author author = getAuthorByUsername(username);
Book book = new Book(title, price, author);
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.save(book);
session.getTransaction().commit();
}
}
@Override
public void authorNameUpdated(String username, String name) {
Author author = getAuthorByUsername(username);
author.setName(name);
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.update(author);
session.getTransaction().commit();
}
}
@Override
public void authorUsernameUpdated(String oldUsername, String newUsername) {
Author author = getAuthorByUsername(oldUsername);
author.setUsername(newUsername);
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.update(author);
session.getTransaction().commit();
}
}
@Override
public void authorEmailUpdated(String username, String email) {
Author author = getAuthorByUsername(username);
author.setEmail(email);
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.update(author);
session.getTransaction().commit();
}
}
@Override
public void bookTitleUpdated(String oldTitle, String newTitle) {
Book book = getBookByTitle(oldTitle);
book.setTitle(newTitle);
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.update(book);
session.getTransaction().commit();
}
}
@Override
public void bookPriceUpdated(String title, double price) {
Book book = getBookByTitle(title);
book.setPrice(price);
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.update(book);
session.getTransaction().commit();
}
}
}
/**
* This interface represents the query methods of the CQRS pattern
*/
public interface IQueryService {
Author getAuthorByUsername(String username);
Book getBook(String title);
List<Book> getAuthorBooks(String username);
BigInteger getAuthorBooksCount(String username);
BigInteger getAuthorsCount();
}
/**
* This class is an implementation of {@link IQueryService}. It uses Hibernate native queries to return DTOs from the
* database.
*
*/
public class QueryServiceImpl implements IQueryService {
private SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
@Override
public Author getAuthorByUsername(String username) {
Author authorDTo = null;
try (Session session = sessionFactory.openSession()) {
SQLQuery sqlQuery = session
.createSQLQuery("SELECT a.username as \"username\", a.name as \"name\", a.email as \"email\""
+ "FROM Author a where a.username=:username");
sqlQuery.setParameter(AppConstants.USER_NAME, username);
authorDTo = (Author) sqlQuery.setResultTransformer(Transformers.aliasToBean(Author.class)).uniqueResult();
}
return authorDTo;
}
@Override
public Book getBook(String title) {
Book bookDTo = null;
try (Session session = sessionFactory.openSession()) {
SQLQuery sqlQuery = session
.createSQLQuery("SELECT b.title as \"title\", b.price as \"price\"" + " FROM Book b where b.title=:title");
sqlQuery.setParameter("title", title);
bookDTo = (Book) sqlQuery.setResultTransformer(Transformers.aliasToBean(Book.class)).uniqueResult();
}
return bookDTo;
}
@Override
public List<Book> getAuthorBooks(String username) {
List<Book> bookDTos = null;
try (Session session = sessionFactory.openSession()) {
SQLQuery sqlQuery = session.createSQLQuery("SELECT b.title as \"title\", b.price as \"price\""
+ " FROM Author a , Book b where b.author_id = a.id and a.username=:username");
sqlQuery.setParameter(AppConstants.USER_NAME, username);
bookDTos = sqlQuery.setResultTransformer(Transformers.aliasToBean(Book.class)).list();
}
return bookDTos;
}
@Override
public BigInteger getAuthorBooksCount(String username) {
BigInteger bookcount = null;
try (Session session = sessionFactory.openSession()) {
SQLQuery sqlQuery = session.createSQLQuery(
"SELECT count(b.title)" + " FROM Book b, Author a where b.author_id = a.id and a.username=:username");
sqlQuery.setParameter(AppConstants.USER_NAME, username);
bookcount = (BigInteger) sqlQuery.uniqueResult();
}
return bookcount;
}
@Override
public BigInteger getAuthorsCount() {
BigInteger authorcount = null;
try (Session session = sessionFactory.openSession()) {
SQLQuery sqlQuery = session.createSQLQuery("SELECT count(id) from Author");
authorcount = (BigInteger) sqlQuery.uniqueResult();
}
return authorcount;
}
}
// DOMAIN MODEL
/**
* This is an Author entity. It is used by Hibernate for persistence.
*/
@Data
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String username;
private String name;
private String email;
/**
*
* @param username
* username of the author
* @param name
* name of the author
* @param email
* email of the author
*/
public Author(String username, String name, String email) {
this.username = username;
this.name = name;
this.email = email;
}
protected Author() {
}
}
/**
* This is a Book entity. It is used by Hibernate for persistence. Many books can be written by one {@link Author}
*/
@Data
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String title;
private double price;
@ManyToOne
private Author author;
/**
*
* @param title
* title of the book
* @param price
* price of the book
* @param author
* author of the book
*/
public Book(String title, double price, Author author) {
this.title = title;
this.price = price;
this.author = author;
}
protected Book() {
}
}
// DTO
/**
* This is a DTO (Data Transfer Object) author, contains only useful information to be returned
*/
@Data
public class Author {
private String name;
private String email;
private String username;
/**
*
* @param name
* name of the author
* @param email
* email of the author
* @param username
* username of the author
*/
public Author(String name, String email, String username) {
this.name = name;
this.email = email;
this.username = username;
}
public Author() {
}
}
/**
* This is a DTO (Data Transfer Object) book, contains only useful information to be returned
*/
@Data
public class Book {
private String title;
private double price;
/**
* @param title title of the book
* @param price price of the book
*/
public Book(String title, double price) {
this.title = title;
this.price = price;
}
public Book() {
}
}
Data Bus(数据总线)
意图
允许在应用程序的组件之间发送消息/事件,而无需彼此了解。他们只需要知道正在发送的消息/事件的类型。
适用性
- 希望组件自己决定要接收哪些消息/事件
- 想要多对多的沟通
- 希望你的组件对彼此一无所知
关联模式
- 中介模式,由数据总线成员自己决定是否接受任何给定的消息。
- 支持多到多通信的观察员模式
- 发布/订阅模式,数据总线将发布者和订阅者分离
案例
/**
* The Data-Bus implementation.
*
* <p>This implementation uses a Singleton.</p>
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
public class DataBus {
private static final DataBus INSTANCE = new DataBus();
private final Set<Member> listeners = new HashSet<>();
public static DataBus getInstance() {
return INSTANCE;
}
/**
* Register a member with the data-bus to start receiving events.
*
* @param member The member to register
*/
public void subscribe(final Member member) {
this.listeners.add(member);
}
/**
* Deregister a member to stop receiving events.
*
* @param member The member to deregister
*/
public void unsubscribe(final Member member) {
this.listeners.remove(member);
}
/**
* Publish and event to all members.
*
* @param event The event
*/
public void publish(final DataType event) {
event.setDataBus(this);
listeners.forEach(listener -> listener.accept(event));
}
}
/**
* Events are sent via the Data-Bus.
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
public interface DataType {
/**
* Returns the data-bus the event is being sent on.
*
* @return The data-bus
*/
DataBus getDataBus();
/**
* Set the data-bus the event will be sent on.
*
* @param dataBus The data-bus
*/
void setDataBus(DataBus dataBus);
}
/**
* Base for data to send via the Data-Bus.
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
@Data
public class AbstractDataType implements DataType {
private DataBus dataBus;
}
/**
* An event raised when a string message is sent.
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
@AllArgsConstructor
public class MessageData extends AbstractDataType {
@Getter
private final String message;
public static DataType of(final String message) {
return new MessageData(message);
}
}
/**
* An event raised when applications starts, containing the start time of the application.
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
@AllArgsConstructor
public class StartingData extends AbstractDataType {
@Getter
private final LocalDateTime when;
public static DataType of(final LocalDateTime when) {
return new StartingData(when);
}
}
/**
* An event raised when applications stops, containing the stop time of the application.
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
@AllArgsConstructor
public class StoppingData extends AbstractDataType {
@Getter
private final LocalDateTime when;
public static DataType of(final LocalDateTime when) {
return new StoppingData(when);
}
}
/**
* Members receive events from the Data-Bus.
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
public interface Member extends Consumer<DataType> {
void accept(DataType event);
}
/**
* Receiver of Data-Bus events that collects the messages from each {@link MessageData}.
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
public class MessageCollectorMember implements Member {
private static final Logger LOGGER = Logger.getLogger(MessageCollectorMember.class.getName());
private final String name;
private List<String> messages = new ArrayList<>();
public MessageCollectorMember(String name) {
this.name = name;
}
@Override
public void accept(final DataType data) {
if (data instanceof MessageData) {
handleEvent((MessageData) data);
}
}
private void handleEvent(MessageData data) {
LOGGER.info(String.format("%s sees message %s", name, data.getMessage()));
messages.add(data.getMessage());
}
public List<String> getMessages() {
return Collections.unmodifiableList(messages);
}
}
/**
* Receiver of Data-Bus events.
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
public class StatusMember implements Member {
private static final Logger LOGGER = Logger.getLogger(StatusMember.class.getName());
private final int id;
private LocalDateTime started;
private LocalDateTime stopped;
public StatusMember(int id) {
this.id = id;
}
@Override
public void accept(final DataType data) {
if (data instanceof StartingData) {
handleEvent((StartingData) data);
} else if (data instanceof StoppingData) {
handleEvent((StoppingData) data);
}
}
private void handleEvent(StartingData data) {
started = data.getWhen();
LOGGER.info(String.format("Receiver #%d sees application started at %s", id, started));
}
private void handleEvent(StoppingData data) {
stopped = data.getWhen();
LOGGER.info(String.format("Receiver #%d sees application stopping at %s", id, stopped));
LOGGER.info(String.format("Receiver #%d sending goodbye message", id));
data.getDataBus().publish(MessageData.of(String.format("Goodbye cruel world from #%d!", id)));
}
public LocalDateTime getStarted() {
return started;
}
public LocalDateTime getStopped() {
return stopped;
}
}
Data Transfer Object(数据传输对象)
意图
从客户端到服务器一次性传递具有多个属性的数据,以避免多次调用远程服务器。
适用性
- 客户要求提供多种信息。而且信息是相关的。
- 想提高性能以获取资源时。
- 希望减少远程调用的数量。
案例
/**
* The resource class which serves customer information.
* This class act as server in the demo. Which has all customer details.
*/
public class CustomerResource {
private List<CustomerDto> customers;
/**
* @param customers initialize resource with existing customers. Act as database.
*/
public CustomerResource(List<CustomerDto> customers) {
this.customers = customers;
}
/**
* @return : all customers in list.
*/
public List<CustomerDto> getAllCustomers() {
return customers;
}
/**
* @param customer save new customer to list.
*/
public void save(CustomerDto customer) {
customers.add(customer);
}
/**
* @param customerId delete customer with id {@code customerId}
*/
public void delete(String customerId) {
customers.removeIf(customer -> customer.getId().equals(customerId));
}
}
/**
* {@link CustomerDto} is a data transfer object POJO. Instead of sending individual information to client
* We can send related information together in POJO.
* <p>
* Dto will not have any business logic in it.
*/
public class CustomerDto {
private final String id;
private final String firstName;
private final String lastName;
/**
* @param id customer id
* @param firstName customer first name
* @param lastName customer last name
*/
public CustomerDto(String id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
public String getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
Event Driven Architecture(事件驱动架构)
意图
使用事件驱动架构将对象的状态更改发送并通知给其他应用程序。
适用性
- 想创建一个松散耦合的系统
- 想建立一个响应更快的系统
- 想要一个更容易扩展的系统
案例
- SendGrid是一个电子邮件API,每当处理,传递,打开电子邮件等时都会发送事件
- Chargify是一种结算API,通过各种活动公开付款活动
- Amazon的AWS Lambda允许您执行代码以响应事件,例如Amazon S3存储桶的更改,Amazon DynamoDB表的更新或应用程序或设备生成的自定义事件。
- MySQL根据事件(如数据库表上发生的插入和更新事件)运行触发器。
- Event-driven architecture - Wikipedia
- Fundamental Components of an Event-Driven Architecture
- Real World Applications/Event Driven Applications
- Event-driven architecture definition
/**
* A {@link Event} is an object with a specific type that is associated
* to a specific {@link Handler}.
*/
public interface Event {
/**
* Returns the message type as a {@link Class} object. In this example the message type is
* used to handle events by their type.
* @return the message type as a {@link Class}.
*/
Class<? extends Event> getType();
}
/**
* The {@link AbstractEvent} class serves as a base class for defining custom events happening with your
* system. In this example we have two types of events defined.
* <ul>
* <li>{@link UserCreatedEvent} - used when a user is created</li>
* <li>{@link UserUpdatedEvent} - used when a user is updated</li>
* </ul>
* Events can be distinguished using the {@link #getType() getType} method.
*/
public abstract class AbstractEvent implements Event {
/**
* Returns the event type as a {@link Class} object
* In this example, this method is used by the {@link EventDispatcher} to
* dispatch events depending on their type.
*
* @return the AbstractEvent type as a {@link Class}.
*/
@Override
public Class<? extends Event> getType() {
return getClass();
}
}
/**
* The {@link UserCreatedEvent} should should be dispatched whenever a user has been created.
* This class can be extended to contain details about the user has been created. In this example,
* the entire {@link User} object is passed on as data with the event.
*/
@AllArgsConstructor
public class UserCreatedEvent extends AbstractEvent {
@Getter
private User user;
}
/**
* The {@link UserUpdatedEvent} should should be dispatched whenever a user has been updated.
* This class can be extended to contain details about the user has been updated. In this example,
* the entire {@link User} object is passed on as data with the event.
*/
@AllArgsConstructor
public class UserUpdatedEvent extends AbstractEvent {
@Getter
private User user;
}
/**
* This {@link User} class is a basic pojo used to demonstrate user data sent along with
* the {@link UserCreatedEvent} and {@link UserUpdatedEvent} events.
*/
@AllArgsConstructor
public class User {
@Getter
private String username;
}
/**
* Handles the routing of {@link Event} messages to associated handlers.
* A {@link HashMap} is used to store the association between events and their respective handlers.
*/
public class EventDispatcher {
private Map<Class<? extends Event>, Handler<? extends Event>> handlers;
public EventDispatcher() {
handlers = new HashMap<>();
}
/**
* Links an {@link Event} to a specific {@link Handler}.
*
* @param eventType The {@link Event} to be registered
* @param handler The {@link Handler} that will be handling the {@link Event}
*/
public <E extends Event> void registerHandler(Class<E> eventType, Handler<E> handler) {
handlers.put(eventType, handler);
}
/**
* Dispatches an {@link Event} depending on it's type.
*
* @param event The {@link Event} to be dispatched
*/
@SuppressWarnings("unchecked")
public <E extends Event> void dispatch(E event) {
Handler<E> handler = (Handler<E>) handlers.get(event.getClass());
if (handler != null) {
handler.onEvent(event);
}
}
}
/**
* This interface can be implemented to handle different types of messages.
* Every handler is responsible for a single of type message
* @param <E> Handler can handle events of type E
*/
public interface Handler<E extends Event> {
/**
* The onEvent method should implement and handle behavior related to the event.
* This can be as simple as calling another service to handle the event on publishing the event on
* a queue to be consumed by other sub systems.
* @param event the {@link Event} object to be handled.
*/
void onEvent(E event);
}
/**
* Handles the {@link UserCreatedEvent} message.
*/
public class UserCreatedEventHandler implements Handler<UserCreatedEvent> {
private static final Logger LOGGER = LoggerFactory.getLogger(UserCreatedEventHandler.class);
@Override
public void onEvent(UserCreatedEvent event) {
LOGGER.info("User '{}' has been Created!", event.getUser().getUsername());
}
}
/**
* Handles the {@link UserUpdatedEvent} message.
*/
public class UserUpdatedEventHandler implements Handler<UserUpdatedEvent> {
private static final Logger LOGGER = LoggerFactory.getLogger(UserUpdatedEventHandler.class);
@Override
public void onEvent(UserUpdatedEvent event) {
LOGGER.info("User '{}' has been Updated!", event.getUser().getUsername());
}
}
Event Sourcing(事件溯源)
意图
不要仅仅存储域中数据的当前状态,而是使用仅附加存储来记录对该数据采取的全部操作。存储充当记录系统,并可用于转换成域对象。 这可以简化复杂域中的任务,避免同步数据模型和业务域,同时提高性能,可伸缩性和响应能力。它还可以为事务数据提供一致性,并维护可以实现补偿操作的完整审计跟踪和历史记录。
适用性
- 即使应用程序状态具有复杂的关系数据结构,也需要非常高的性能来保持应用程序状态。
- 需要记录应用程序状态的更改以及恢复任何时刻状态的能力。
- 需要通过重播过去的事件来调试生产问题。
案例
- The Lmax Architecture
- Martin Fowler - Event Sourcing
- Event Sourcing Microsoft Docs
- Reference 3: Introducing Event Sourcing
/**
* This is the base class for domain events. All events must extend this class.
*/
@Data
public abstract class DomainEvent implements Serializable {
private final long sequenceId;
private final long createdTime;
private final String eventClassName;
private boolean realTime = true;
/**
* Instantiates a new Domain event.
*
* @param sequenceId the sequence id
* @param createdTime the created time
* @param eventClassName the event class name
*/
public DomainEvent(long sequenceId, long createdTime, String eventClassName) {
this.sequenceId = sequenceId;
this.createdTime = createdTime;
this.eventClassName = eventClassName;
}
/**
* Process.
*/
public abstract void process();
}
/**
* This is the class that implements account create event.
* Holds the necessary info for an account create event.
* Implements the process function that finds the event related
* domain objects and calls the related domain object's handle event functions
*/
public class AccountCreateEvent extends DomainEvent {
@Getter
private final int accountNo;
@Getter
private final String owner;
/**
* Instantiates a new Account create event.
*
* @param sequenceId the sequence id
* @param createdTime the created time
* @param accountNo the account no
* @param owner the owner
*/
public AccountCreateEvent(long sequenceId, long createdTime, int accountNo, String owner) {
super(sequenceId, createdTime, "AccountCreateEvent");
this.accountNo = accountNo;
this.owner = owner;
}
@Override
public void process() {
Account account = AccountAggregate.getAccount(accountNo);
if (account != null) {
throw new RuntimeException("Account already exists");
}
account = new Account(accountNo, owner);
account.handleEvent(this);
}
}
/**
* This is the class that implements money deposit event.
* Holds the necessary info for a money deposit event.
* Implements the process function that finds the event related
* domain objects and calls the related domain object's handle event functions
*/
public class MoneyDepositEvent extends DomainEvent {
@Getter
private final BigDecimal money;
@Getter
private final int accountNo;
/**
* Instantiates a new Money deposit event.
*
* @param sequenceId the sequence id
* @param createdTime the created time
* @param accountNo the account no
* @param money the money
*/
public MoneyDepositEvent(long sequenceId, long createdTime, int accountNo, BigDecimal money) {
super(sequenceId, createdTime, "MoneyDepositEvent");
this.money = money;
this.accountNo = accountNo;
}
@Override
public void process() {
Account account = AccountAggregate.getAccount(accountNo);
if (account == null) {
throw new RuntimeException("Account not found");
}
account.handleEvent(this);
}
}
/**
* This is the class that implements money transfer event.
* Holds the necessary info for a money transfer event.
* Implements the process function that finds the event related
* domain objects and calls the related domain object's handle event functions
*/
public class MoneyTransferEvent extends DomainEvent {
@Getter
private final BigDecimal money;
@Getter
private final int accountNoFrom;
@Getter
private final int accountNoTo;
/**
* Instantiates a new Money transfer event.
*
* @param sequenceId the sequence id
* @param createdTime the created time
* @param money the money
* @param accountNoFrom the account no from
* @param accountNoTo the account no to
*/
public MoneyTransferEvent(long sequenceId, long createdTime, BigDecimal money, int accountNoFrom,
int accountNoTo) {
super(sequenceId, createdTime, "MoneyTransferEvent");
this.money = money;
this.accountNoFrom = accountNoFrom;
this.accountNoTo = accountNoTo;
}
@Override
public void process() {
Account accountFrom = AccountAggregate.getAccount(accountNoFrom);
if (accountFrom == null) {
throw new RuntimeException("Account not found " + accountNoFrom);
}
Account accountTo = AccountAggregate.getAccount(accountNoTo);
if (accountTo == null) {
throw new RuntimeException("Account not found " + accountNoTo);
}
accountFrom.handleTransferFromEvent(this);
accountTo.handleTransferToEvent(this);
}
}
/**
* This is the Account class that holds the account info, the account number,
* account owner name and money of the account. Account class also have the business logic of events
* that effects this account.
*/
@Data
public class Account {
private static final Logger LOGGER = LoggerFactory.getLogger(Account.class);
private final int accountNo;
private final String owner;
private BigDecimal money;
private static final String MSG = "Some external api for only realtime execution could be called here.";
/**
* Instantiates a new Account.
*
* @param accountNo the account no
* @param owner the owner
*/
public Account(int accountNo, String owner) {
this.accountNo = accountNo;
this.owner = owner;
money = BigDecimal.ZERO;
}
/**
* Copy account.
*
* @return the account
*/
public Account copy() {
Account account = new Account(accountNo, owner);
account.setMoney(money);
return account;
}
private void depositMoney(BigDecimal money) {
this.money = this.money.add(money);
}
private void withdrawMoney(BigDecimal money) {
this.money = this.money.subtract(money);
}
private void handleDeposit(BigDecimal money, boolean realTime) {
depositMoney(money);
AccountAggregate.putAccount(this);
if (realTime) {
LOGGER.info(MSG);
}
}
private void handleWithdrawal(BigDecimal money, boolean realTime) {
if (this.money.compareTo(money) == -1) {
throw new RuntimeException("Insufficient Account Balance");
}
withdrawMoney(money);
AccountAggregate.putAccount(this);
if (realTime) {
LOGGER.info(MSG);
}
}
/**
* Handles the MoneyDepositEvent.
*
* @param moneyDepositEvent the money deposit event
*/
public void handleEvent(MoneyDepositEvent moneyDepositEvent) {
handleDeposit(moneyDepositEvent.getMoney(), moneyDepositEvent.isRealTime());
}
/**
* Handles the AccountCreateEvent.
*
* @param accountCreateEvent the account create event
*/
public void handleEvent(AccountCreateEvent accountCreateEvent) {
AccountAggregate.putAccount(this);
if (accountCreateEvent.isRealTime()) {
LOGGER.info(MSG);
}
}
/**
* Handles transfer from account event.
*
* @param moneyTransferEvent the money transfer event
*/
public void handleTransferFromEvent(MoneyTransferEvent moneyTransferEvent) {
handleWithdrawal(moneyTransferEvent.getMoney(), moneyTransferEvent.isRealTime());
}
/**
* Handles transfer to account event.
*
* @param moneyTransferEvent the money transfer event
*/
public void handleTransferToEvent(MoneyTransferEvent moneyTransferEvent) {
handleDeposit(moneyTransferEvent.getMoney(), moneyTransferEvent.isRealTime());
}
}
/**
* This is the static accounts map holder class.
* This class holds the state of the accounts.
*/
public class AccountAggregate {
private static Map<Integer, Account> accounts = new HashMap<>();
private AccountAggregate() {
}
/**
* Put account.
*
* @param account the account
*/
public static void putAccount(Account account) {
accounts.put(account.getAccountNo(), account);
}
/**
* Gets account.
*
* @param accountNo the account no
* @return the copy of the account or null if not found
*/
public static Account getAccount(int accountNo) {
Account account = accounts.get(accountNo);
if (account == null) {
return null;
}
return account.copy();
}
/**
* Reset state.
*/
public static void resetState() {
accounts = new HashMap<>();
}
}
/**
* This is the implementation of event processor.
* All events are processed by this class.
* This processor uses processorJournal to persist and recover events.
*/
public class DomainEventProcessor {
private final JsonFileJournal processorJournal = new JsonFileJournal();
/**
* Process.
*
* @param domainEvent the domain event
*/
public void process(DomainEvent domainEvent) {
domainEvent.process();
processorJournal.write(domainEvent);
}
/**
* Reset.
*/
public void reset() {
processorJournal.reset();
}
/**
* Recover.
*/
public void recover() {
DomainEvent domainEvent;
while (true) {
domainEvent = processorJournal.readNext();
if (domainEvent == null) {
break;
} else {
domainEvent.process();
}
}
}
}
/**
* This is the implementation of event journal.
* This implementation serialize/deserialize the events with JSON
* and writes/reads them on a Journal.json file at the working directory.
*/
public class JsonFileJournal {
private final File aFile;
private final List<String> events = new ArrayList<>();
private int index = 0;
/**
* Instantiates a new Json file journal.
*/
public JsonFileJournal() {
aFile = new File("Journal.json");
if (aFile.exists()) {
try (BufferedReader input = new BufferedReader(
new InputStreamReader(new FileInputStream(aFile), "UTF-8"))) {
String line;
while ((line = input.readLine()) != null) {
events.add(line);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
reset();
}
}
/**
* Write.
*
* @param domainEvent the domain event
*/
public void write(DomainEvent domainEvent) {
Gson gson = new Gson();
JsonElement jsonElement;
if (domainEvent instanceof AccountCreateEvent) {
jsonElement = gson.toJsonTree(domainEvent, AccountCreateEvent.class);
} else if (domainEvent instanceof MoneyDepositEvent) {
jsonElement = gson.toJsonTree(domainEvent, MoneyDepositEvent.class);
} else if (domainEvent instanceof MoneyTransferEvent) {
jsonElement = gson.toJsonTree(domainEvent, MoneyTransferEvent.class);
} else {
throw new RuntimeException("Journal Event not recegnized");
}
try (Writer output = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(aFile, true), "UTF-8"))) {
String eventString = jsonElement.toString();
output.write(eventString + "\r\n");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Reset.
*/
public void reset() {
aFile.delete();
}
/**
* Read next domain event.
*
* @return the domain event
*/
public DomainEvent readNext() {
if (index >= events.size()) {
return null;
}
String event = events.get(index);
index++;
JsonParser parser = new JsonParser();
JsonElement jsonElement = parser.parse(event);
String eventClassName = jsonElement.getAsJsonObject().get("eventClassName").getAsString();
Gson gson = new Gson();
DomainEvent domainEvent;
if (eventClassName.equals("AccountCreateEvent")) {
domainEvent = gson.fromJson(jsonElement, AccountCreateEvent.class);
} else if (eventClassName.equals("MoneyDepositEvent")) {
domainEvent = gson.fromJson(jsonElement, MoneyDepositEvent.class);
} else if (eventClassName.equals("MoneyTransferEvent")) {
domainEvent = gson.fromJson(jsonElement, MoneyTransferEvent.class);
} else {
throw new RuntimeException("Journal Event not recegnized");
}
domainEvent.setRealTime(false);
return domainEvent;
}
}
Hexagonal Architecture(六边形架构)
同名:
- Ports and Adapters Architecture
- Clean Architecture清洁架构
- Onion Architecture洋葱建筑
意图
允许应用程序对等的由用户、程序、自动测试或批处理脚本驱动,并与其最终的运行时设备和数据库隔离开发和测试。
洋葱架构、整洁架构:
- 依赖原则(The dependency rules):总得来说,越往里面,代码级别越高。外层的圆是(实现)机制,而内层的圆是原则(Policies)。越往里面抽象级别越高,最外层的圆是低级别的具体细节。越往里面内容越抽象,并且封装更高级别的原则(Policies)。最里面的圆是最通用的。
- Entities:Entities封装了企业级的业务规则。一个Entity可以是一个带方法的对象,也可以是一个数据结构和方法集。
- Entities可以被用于企业的其他应用。如果你没有加入企业,而是仅仅在写一个简单的应用,那么这些Entities就是这个应用的业务对象。它们封装了最通用、最上层的原则。
- 它们是最不容易改变的,即使外部的东西改变了。例如,你不想让这些对象受到页面导航、安全的影响。应用的任何操作变化都不应该影响Entities Layer。
- Use Casess:这一层包含了应用特有的业务规则。它封装和实现了系统的所有用例。
- 这些用例协调数据从entities的流入和流出,并且指导entities使用它们的企业级业务规则来达到用例的目标。
- 我们不希望这一层的改变影响到Entities,同时也不希望这一层被外层的改变影响,如外层的数据库,UI或者任何Frameworks的改变,这一层独立于这些关注点。
- Interface Adapters:这一层包含一个adapters set(数据适配器集),它们把适用于Use Casess和entities的数据转换为适用于外部服务(external agency,如Database或Web)的格式。例如,这一层可以完全包括GUI的MVX架构,Presenters, Views和Controllers都属于这里。
- Models可能仅仅是从Controllers传到Use Casess的数据结构,然后从Use Casess返回给Presenters和Views。这一层的数据会被转换,从适用于entities和Use Casess的格式转换到适用于所使用的持久化框架的格式(如数据库)。这个圆以内的代码不应该知道关于数据库的任何东西。同样,这一层也需要一些其他必要的Adapter来把外部的数据格式(如来自于外部服务的格式),转换为适用于Use Casess和entities的格式。
- Frameworks and Drivers:最外面的一层通常由Frameworks和Tools组成,如Database,Web Framework等。一般来说,除了用于和内层圆交互的连接代码,你不会在这一层写很多代码。
- 这一层是实现所有细节的地方。Web和Database都是需要实现的细节。我们把这些东西放在外面以减轻来自于它们的伤害(即减轻对他们的依赖)。
- 跨界:通常用依赖倒置原则来解决这个明显的矛盾。比如,在Java这样的语言里,我们可以使用接口和继承关系在合适的地方让源码依赖与控制流反向来跨界。
- 例如,假设Use Cases需要访问Presenter,当然,不能是直接访问,不然会违反依赖原则,所以我们让内圆的Use Cases访问一个接口(如图中的Use Cases output port),然后外圆的Presenter实现这个接口。在这个架构中,同样的技术也被用于跨越其他的边界。我们利用运行时多态来创建与控制流相反的SourceCode依赖以满足依赖原则,无论控制流是如何流向的。
- 在图的右下角是一个我们应该如何跨界的例子。它展示了Controllers、Presenters与下一层的Use Casess的交互。注意控制的流向,它开始于Controller,经过Use Casess,最终在Presenter中执行。
- 同时也请注意Source Code依赖,它们每一个都指向内部的Use Casess。例如,很多的数据库框架对于query返回一个方便的数据格式,我们可以称之为Row Structure,我们不想向内部传递这个row structure。这会让内圆知道外圆的内容而违反了依赖原则。所以,我们应该以最适用于内圆使用的格式来传递跨界的数据。
适用性
- 当应用程序需要独立于任何框架时
- 当应用程序的高可维护性和可测试非常重要时
案例
- Build Maintainable Systems With Hexagonal Architecture
- Hexagonal Architecture
- Apache Isis builds generic UI and REST API directly from the underlying domain objects
- Alistair Cockburn - Hexagonal Architecture
Layers(分层架构)
意图
分层是一种架构风格,其中软件职责在应用程序的不同层之间分配
适用性
- 将软件职责明确划分为程序的不同部分
- 防止更改在整个应用程序中传播
- 使应用程序更易于维护和可测试
案例
@Entity
@Data
public class Cake {
@Id
@GeneratedValue
private Long id;
@OneToOne(cascade = CascadeType.REMOVE)
private CakeTopping topping;
@OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER)
private Set<CakeLayer> layers;
public Cake() {
setLayers(new HashSet<>());
}
}
/**
* CRUD repository for cakes
*/
@Repository
public interface CakeDao extends CrudRepository<Cake, Long> {}
/**
* DTO for cakes
*/
@ToString
public class CakeInfo {
public final Optional<Long> id;
public final CakeToppingInfo cakeToppingInfo;
public final List<CakeLayerInfo> cakeLayerInfos;
/**
* Constructor
*/
public CakeInfo(Long id, CakeToppingInfo cakeToppingInfo, List<CakeLayerInfo> cakeLayerInfos) {
this.id = Optional.of(id);
this.cakeToppingInfo = cakeToppingInfo;
this.cakeLayerInfos = cakeLayerInfos;
}
/**
* Constructor
*/
public CakeInfo(CakeToppingInfo cakeToppingInfo, List<CakeLayerInfo> cakeLayerInfos) {
this.id = Optional.empty();
this.cakeToppingInfo = cakeToppingInfo;
this.cakeLayerInfos = cakeLayerInfos;
}
/**
* Calculate calories
*/
public int calculateTotalCalories() {
int total = cakeToppingInfo != null ? cakeToppingInfo.calories : 0;
total += cakeLayerInfos.stream().mapToInt(c -> c.calories).sum();
return total;
}
}
/**
* CakeLayer entity
*/
@Entity
@Data
@NoArgsConstructor
public class CakeLayer {
@Id
@GeneratedValue
private Long id;
private String name;
private int calories;
@ManyToOne(cascade = CascadeType.ALL)
private Cake cake;
public CakeLayer(String name, int calories) {
this.setName(name);
this.setCalories(calories);
}
}
/**
* CRUD repository for cake layers
*/
@Repository
public interface CakeLayerDao extends CrudRepository<CakeLayer, Long> {}
/**
* DTO for cake layers
*/
@AllArgsConstructor
@ToString
public class CakeLayerInfo {
public final Optional<Long> id;
public final String name;
public final int calories;
/**
* Constructor
*/
public CakeLayerInfo(Long id, String name, int calories) {
this.id = Optional.of(id);
this.name = name;
this.calories = calories;
}
/**
* Constructor
*/
public CakeLayerInfo(String name, int calories) {
this.id = Optional.empty();
this.name = name;
this.calories = calories;
}
}
/**
* CakeTopping entity
*/
@Entity
@Data
@NoArgsConstructor
public class CakeTopping {
@Id
@GeneratedValue
private Long id;
private String name;
private int calories;
@OneToOne(cascade = CascadeType.ALL)
private Cake cake;
public CakeTopping(String name, int calories) {
this.setName(name);
this.setCalories(calories);
}
}
/**
* CRUD repository cake toppings
*/
@Repository
public interface CakeToppingDao extends CrudRepository<CakeTopping, Long> {}
/**
* DTO for cake toppings
*/
@ToString
public class CakeToppingInfo {
public final Optional<Long> id;
public final String name;
public final int calories;
/**
* Constructor
*/
public CakeToppingInfo(Long id, String name, int calories) {
this.id = Optional.of(id);
this.name = name;
this.calories = calories;
}
/**
* Constructor
*/
public CakeToppingInfo(String name, int calories) {
this.id = Optional.empty();
this.name = name;
this.calories = calories;
}
}
/**
* Service for cake baking operations
*/
public interface CakeBakingService {
/**
* Bakes new cake according to parameters
*/
void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException;
/**
* Get all cakes
*/
List<CakeInfo> getAllCakes();
/**
* Store new cake topping
*/
void saveNewTopping(CakeToppingInfo toppingInfo);
/**
* Get available cake toppings
*/
List<CakeToppingInfo> getAvailableToppings();
/**
* Add new cake layer
*/
void saveNewLayer(CakeLayerInfo layerInfo);
/**
* Get available cake layers
*/
List<CakeLayerInfo> getAvailableLayers();
}
/**
* Implementation of CakeBakingService
*/
@Service
@Transactional
public class CakeBakingServiceImpl implements CakeBakingService {
private AbstractApplicationContext context;
public CakeBakingServiceImpl() {
this.context = new ClassPathXmlApplicationContext("applicationContext.xml");
}
@Override
public void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException {
List<CakeTopping> allToppings = getAvailableToppingEntities();
List<CakeTopping> matchingToppings =
allToppings.stream().filter(t -> t.getName().equals(cakeInfo.cakeToppingInfo.name))
.collect(Collectors.toList());
if (matchingToppings.isEmpty()) {
throw new CakeBakingException(String.format("Topping %s is not available",
cakeInfo.cakeToppingInfo.name));
}
List<CakeLayer> allLayers = getAvailableLayerEntities();
Set<CakeLayer> foundLayers = new HashSet<>();
for (CakeLayerInfo info : cakeInfo.cakeLayerInfos) {
Optional<CakeLayer> found =
allLayers.stream().filter(layer -> layer.getName().equals(info.name)).findFirst();
if (!found.isPresent()) {
throw new CakeBakingException(String.format("Layer %s is not available", info.name));
} else {
foundLayers.add(found.get());
}
}
CakeToppingDao toppingBean = context.getBean(CakeToppingDao.class);
CakeTopping topping = toppingBean.findOne(matchingToppings.iterator().next().getId());
CakeDao cakeBean = context.getBean(CakeDao.class);
Cake cake = new Cake();
cake.setTopping(topping);
cake.setLayers(foundLayers);
cakeBean.save(cake);
topping.setCake(cake);
toppingBean.save(topping);
CakeLayerDao layerBean = context.getBean(CakeLayerDao.class);
for (CakeLayer layer : foundLayers) {
layer.setCake(cake);
layerBean.save(layer);
}
}
@Override
public void saveNewTopping(CakeToppingInfo toppingInfo) {
CakeToppingDao bean = context.getBean(CakeToppingDao.class);
bean.save(new CakeTopping(toppingInfo.name, toppingInfo.calories));
}
@Override
public void saveNewLayer(CakeLayerInfo layerInfo) {
CakeLayerDao bean = context.getBean(CakeLayerDao.class);
bean.save(new CakeLayer(layerInfo.name, layerInfo.calories));
}
private List<CakeTopping> getAvailableToppingEntities() {
CakeToppingDao bean = context.getBean(CakeToppingDao.class);
List<CakeTopping> result = new ArrayList<>();
Iterator<CakeTopping> iterator = bean.findAll().iterator();
while (iterator.hasNext()) {
CakeTopping topping = iterator.next();
if (topping.getCake() == null) {
result.add(topping);
}
}
return result;
}
@Override
public List<CakeToppingInfo> getAvailableToppings() {
CakeToppingDao bean = context.getBean(CakeToppingDao.class);
List<CakeToppingInfo> result = new ArrayList<>();
Iterator<CakeTopping> iterator = bean.findAll().iterator();
while (iterator.hasNext()) {
CakeTopping next = iterator.next();
if (next.getCake() == null) {
result.add(new CakeToppingInfo(next.getId(), next.getName(), next.getCalories()));
}
}
return result;
}
private List<CakeLayer> getAvailableLayerEntities() {
CakeLayerDao bean = context.getBean(CakeLayerDao.class);
List<CakeLayer> result = new ArrayList<>();
Iterator<CakeLayer> iterator = bean.findAll().iterator();
while (iterator.hasNext()) {
CakeLayer next = iterator.next();
if (next.getCake() == null) {
result.add(next);
}
}
return result;
}
@Override
public List<CakeLayerInfo> getAvailableLayers() {
CakeLayerDao bean = context.getBean(CakeLayerDao.class);
List<CakeLayerInfo> result = new ArrayList<>();
Iterator<CakeLayer> iterator = bean.findAll().iterator();
while (iterator.hasNext()) {
CakeLayer next = iterator.next();
if (next.getCake() == null) {
result.add(new CakeLayerInfo(next.getId(), next.getName(), next.getCalories()));
}
}
return result;
}
@Override
public List<CakeInfo> getAllCakes() {
CakeDao cakeBean = context.getBean(CakeDao.class);
List<CakeInfo> result = new ArrayList<>();
Iterator<Cake> iterator = cakeBean.findAll().iterator();
while (iterator.hasNext()) {
Cake cake = iterator.next();
CakeToppingInfo cakeToppingInfo =
new CakeToppingInfo(cake.getTopping().getId(), cake.getTopping().getName(), cake
.getTopping().getCalories());
List<CakeLayerInfo> cakeLayerInfos = new ArrayList<>();
for (CakeLayer layer : cake.getLayers()) {
cakeLayerInfos.add(new CakeLayerInfo(layer.getId(), layer.getName(), layer.getCalories()));
}
CakeInfo cakeInfo = new CakeInfo(cake.getId(), cakeToppingInfo, cakeLayerInfos);
result.add(cakeInfo);
}
return result;
}
}
/**
* Custom exception used in cake baking
*/
public class CakeBakingException extends Exception {
private static final long serialVersionUID = 1L;
public CakeBakingException() {
}
public CakeBakingException(String message) {
super(message);
}
}
Naked Objects(裸对象模式)
意图
裸对象结构模式非常适合快速原型设计。使用该模式,您只需要编写域对象,其他所有内容都由框架自动生成。
适用性
- 你是原型,需要快速的开发周期
- 自动生成的用户界面就足够了
- 希望自动将域发布为REST服务
案例
Partial Response(部分响应)
意图
根据需要从服务器发送部分响应。客户端将指定服务器所需的字段,而不是提供资源的所有详细信息。
适用性
- 客户端只需要来自资源的数据子集。
- 避免过多的数据传输
案例
/**
* {@link Video} is a entity to serve from server.It contains all video related information..
* <p>
*/
@AllArgsConstructor
@ToString
public class Video {
private final Integer id;
private final String title;
private final Integer length;
private final String description;
private final String director;
private final String language;
}
/**
* The resource class which serves video information.
* This class act as server in the demo. Which has all video details.
*/
public class VideoResource {
private FieldJsonMapper fieldJsonMapper;
private Map<Integer, Video> videos;
/**
* @param fieldJsonMapper map object to json.
* @param videos initialize resource with existing videos. Act as database.
*/
public VideoResource(FieldJsonMapper fieldJsonMapper, Map<Integer, Video> videos) {
this.fieldJsonMapper = fieldJsonMapper;
this.videos = videos;
}
/**
* @param id video id
* @param fields fields to get information about
* @return full response if no fields specified else partial response for given field.
*/
public String getDetails(Integer id, String... fields) throws Exception {
if (fields.length == 0) {
return videos.get(id).toString();
}
return fieldJsonMapper.toJson(videos.get(id), fields);
}
}
/**
* Map a video to json
*/
public class FieldJsonMapper {
/**
* @param video object containing video information
* @param fields fields information to get
* @return json of required fields from video
*/
public String toJson(Video video, String[] fields) throws Exception {
StringBuilder json = new StringBuilder().append("{");
for (int i = 0, fieldsLength = fields.length; i < fieldsLength; i++) {
json.append(getString(video, Video.class.getDeclaredField(fields[i])));
if (i != fieldsLength - 1) {
json.append(",");
}
}
json.append("}");
return json.toString();
}
private String getString(Video video, Field declaredField) throws IllegalAccessException {
declaredField.setAccessible(true);
Object value = declaredField.get(video);
if (declaredField.get(video) instanceof Integer) {
return "\"" + declaredField.getName() + "\"" + ": " + value;
}
return "\"" + declaredField.getName() + "\"" + ": " + "\"" + value.toString() + "\"";
}
}
public static void main(String[] args) throws Exception {
Map<Integer, Video> videos = new HashMap<>();
videos.put(1, new Video(1, "Avatar", 178, "epic science fiction film", "James Cameron", "English"));
videos.put(2, new Video(2, "Godzilla Resurgence", 120, "Action & drama movie|", "Hideaki Anno", "Japanese"));
videos.put(3, new Video(3, "Interstellar", 169, "Adventure & Sci-Fi", "Christopher Nolan", "English"));
VideoResource videoResource = new VideoResource(new FieldJsonMapper(), videos);
String videoDetails = videoResource.getDetails(1);
LOGGER.info(videoDetails);
String specificFieldsDetails = videoResource.getDetails(3, "id", "title", "director");
LOGGER.info(specificFieldsDetails);
String videoLength = videoResource.getDetails(3, "id", "length");
}
Serverless(无服务器)
解释
术语“Serverless”令人困惑,因为这些应用程序在某处运行服务器硬件和服务器进程,但与正常方法的不同之处在于,构建和支持“无服务器”应用程序的组织不关注硬件或进程 - 他们将此外包给供应商。
Serverless消除了规划基础架构的需要,让您专注于您的应用程序。
以下是在构建无服务器应用程序时应注意的优化点:
- 精益功能
- 简洁逻辑:使用函数进行转换,而不是传输(利用提供程序提供的某些集成进行传输),并确保只读取所需内容
- 高效/单一用途代码:避免条件/路由逻辑并分解为单个函数,避免“胖”/单片函数并控制函数部署包中的依赖关系以减少函数的加载时间
- 短暂的环境:利用容器启动进行昂贵的初始化
- 事件调用
- 简洁的有效负载:尽可能仔细检查事件,并监视有效负载限制(异步-128K)
- 弹性路由:了解重试策略并利用死信队列(SQS或SNS进行重放)并记住重试计数为调用
- 并发执行:Amazon Lambda认为它在并发性方面的规模,而不是基于请求/基于持续时间。 Lambda将根据请求调整实例数。
- 协调调用
- 通过API解耦:设置应用程序的最佳做法是将API作为合同来确保关注点分离
- 规模匹配的下游:确保当Lambda调用下游组件时,您将匹配扩展配置(通过基于下游服务指定最大并发)
- 安全:总是问一个问题,我需要一个VPC吗?
- 服务性操作
- 自动化:使用自动化工具来管理和维护堆栈
- 受监控的应用程序:使用监控服务来全面查看Serverless应用程序
Serverless计算是云计算执行模型,其中云提供商动态地管理机器资源的分配。 定价基于应用程序消耗的实际资源量,而不是预先购买的容量单位。
(Function as a Service or “FaaS”)
意图
无论是降低基础架构成本,缩短花在操作任务上的时间,简化部署流程,实现无限可扩展性,无服务器都能将上市时间缩短一半。
案例
Service Layer(服务层)
意图
服务层是域逻辑的抽象。
通常,应用程序需要多种接口来存储它们存储的数据和它们实现的逻辑:数据加载器,用户界面,集成网关等。 尽管它们的目的不同,但这些接口通常需要与应用程序进行通用交互才能访问和操作其数据并调用其业务逻辑。
服务层履行此职责。
适用性
- 您想在API下封装域逻辑
- 需要使用通用逻辑和数据实现多个接口
案例
/**
* Service interface.
*/
public interface MagicService {
List<Wizard> findAllWizards();
List<Spellbook> findAllSpellbooks();
List<Spell> findAllSpells();
List<Wizard> findWizardsWithSpellbook(String name);
List<Wizard> findWizardsWithSpell(String name);
}
/**
* Service implementation.
*/
public class MagicServiceImpl implements MagicService {
private WizardDao wizardDao;
private SpellbookDao spellbookDao;
private SpellDao spellDao;
/**
* Constructor
*/
public MagicServiceImpl(WizardDao wizardDao, SpellbookDao spellbookDao, SpellDao spellDao) {
this.wizardDao = wizardDao;
this.spellbookDao = spellbookDao;
this.spellDao = spellDao;
}
@Override
public List<Wizard> findAllWizards() {
return wizardDao.findAll();
}
@Override
public List<Spellbook> findAllSpellbooks() {
return spellbookDao.findAll();
}
@Override
public List<Spell> findAllSpells() {
return spellDao.findAll();
}
@Override
public List<Wizard> findWizardsWithSpellbook(String name) {
Spellbook spellbook = spellbookDao.findByName(name);
return new ArrayList<>(spellbook.getWizards());
}
@Override
public List<Wizard> findWizardsWithSpell(String name) {
Spell spell = spellDao.findByName(name);
Spellbook spellbook = spell.getSpellbook();
return new ArrayList<>(spellbook.getWizards());
}
}
/**
* SpellDao interface.
*/
public interface SpellDao extends Dao<Spell> {
Spell findByName(String name);
}
/**
* SpellbookDao interface.
*/
public interface SpellbookDao extends Dao<Spellbook> {
Spellbook findByName(String name);
}
/**
* WizardDao interface.
*/
public interface WizardDao extends Dao<Wizard> {
Wizard findByName(String name);
}
Unit Of Work(单元化)
意图
当业务事务完成时,所有这些更新将作为一个大的工作单元一次性发送到数据库中,以便最大限度地减少数据库交互。
适用性
- 优化数据库事务所需的时间。
- 将更改作为工作单元发送到数据库,以确保事务的原子性。
- 减少数据库调用次数。
案例
/**
* @param <T> Any generic entity
*/
public interface IUnitOfWork<T> {
String INSERT = "INSERT";
String DELETE = "DELETE";
String MODIFY = "MODIFY";
/**
* Any register new operation occurring on UnitOfWork is only going to be performed on commit.
*/
void registerNew(T entity);
/**
* Any register modify operation occurring on UnitOfWork is only going to be performed on commit.
*/
void registerModified(T entity);
/**
* Any register delete operation occurring on UnitOfWork is only going to be performed on commit.
*/
void registerDeleted(T entity);
/***
* All UnitOfWork operations batched together executed in commit only.
*/
void commit();
}
/**
* {@link StudentRepository} Student database repository.
* supports unit of work for student data.
*/
public class StudentRepository implements IUnitOfWork<Student> {
private static final Logger LOGGER = LoggerFactory.getLogger(StudentRepository.class);
private Map<String, List<Student>> context;
private StudentDatabase studentDatabase;
/**
* @param context set of operations to be perform during commit.
* @param studentDatabase Database for student records.
*/
public StudentRepository(Map<String, List<Student>> context, StudentDatabase studentDatabase) {
this.context = context;
this.studentDatabase = studentDatabase;
}
@Override
public void registerNew(Student student) {
LOGGER.info("Registering {} for insert in context.", student.getName());
register(student, IUnitOfWork.INSERT);
}
@Override
public void registerModified(Student student) {
LOGGER.info("Registering {} for modify in context.", student.getName());
register(student, IUnitOfWork.MODIFY);
}
@Override
public void registerDeleted(Student student) {
LOGGER.info("Registering {} for delete in context.", student.getName());
register(student, IUnitOfWork.DELETE);
}
private void register(Student student, String operation) {
List<Student> studentsToOperate = context.get(operation);
if (studentsToOperate == null) {
studentsToOperate = new ArrayList<>();
}
studentsToOperate.add(student);
context.put(operation, studentsToOperate);
}
/**
* All UnitOfWork operations are batched and executed together on commit only.
*/
@Override
public void commit() {
if (context == null || context.size() == 0) {
return;
}
LOGGER.info("Commit started");
if (context.containsKey(IUnitOfWork.INSERT)) {
commitInsert();
}
if (context.containsKey(IUnitOfWork.MODIFY)) {
commitModify();
}
if (context.containsKey(IUnitOfWork.DELETE)) {
commitDelete();
}
LOGGER.info("Commit finished.");
}
private void commitInsert() {
List<Student> studentsToBeInserted = context.get(IUnitOfWork.INSERT);
for (Student student : studentsToBeInserted) {
LOGGER.info("Saving {} to database.", student.getName());
studentDatabase.insert(student);
}
}
private void commitModify() {
List<Student> modifiedStudents = context.get(IUnitOfWork.MODIFY);
for (Student student : modifiedStudents) {
LOGGER.info("Modifying {} to database.", student.getName());
studentDatabase.modify(student);
}
}
private void commitDelete() {
List<Student> deletedStudents = context.get(IUnitOfWork.DELETE);
for (Student student : deletedStudents) {
LOGGER.info("Deleting {} to database.", student.getName());
studentDatabase.delete(student);
}
}
}
Abstract Document(抽象文档)
意图
实现非类型化语言的灵活性,保持类型安全。
适用性
- 需要动态添加新属性
- you want a flexible way to organize domain in tree like structure
- 你想要一种灵活的方式来组织树状结构中的域
- 你想要更松散耦合的系统
案例
/**
* Document interface
*/
public interface Document {
/**
* Puts the value related to the key
*
* @param key element key
* @param value element value
* @return Void
*/
Void put(String key, Object value);
/**
* Gets the value for the key
*
* @param key element key
* @return value or null
*/
Object get(String key);
/**
* Gets the stream of child documents
*
* @param key element key
* @param constructor constructor of child class
* @return child documents
*/
<T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor);
}
/**
* Abstract implementation of Document interface
*/
public abstract class AbstractDocument implements Document {
private final Map<String, Object> properties;
protected AbstractDocument(Map<String, Object> properties) {
Objects.requireNonNull(properties, "properties map is required");
this.properties = properties;
}
@Override
public Void put(String key, Object value) {
properties.put(key, value);
return null;
}
@Override
public Object get(String key) {
return properties.get(key);
}
@Override
public <T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor) {
Optional<List<Map<String, Object>>> any = Stream.of(get(key))
.filter(Objects::nonNull)
.map(el -> (List<Map<String, Object>>) el)
.findAny();
return any.map(maps -> maps.stream().map(constructor)).orElseGet(Stream::empty);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(getClass().getName()).append("[");
properties.forEach((key, value) -> builder.append("[").append(key).append(" : ").append(value).append("]"));
builder.append("]");
return builder.toString();
}
}
/**
*
* Enum To Describe Property type
*
*/
public enum Property {
PARTS, TYPE, PRICE, MODEL
}
/**
* HasModel trait for static access to 'model' property
*/
public interface HasModel extends Document {
default Optional<String> getModel() {
return Optional.ofNullable((String) get(Property.MODEL.toString()));
}
}
/**
* HasPrice trait for static access to 'price' property
*/
public interface HasPrice extends Document {
default Optional<Number> getPrice() {
return Optional.ofNullable((Number) get(Property.PRICE.toString()));
}
}
/**
* HasType trait for static access to 'type' property
*/
public interface HasType extends Document {
default Optional<String> getType() {
return Optional.ofNullable((String) get(Property.TYPE.toString()));
}
}
/**
* HasParts trait for static access to 'parts' property
*/
public interface HasParts extends Document {
default Stream<Part> getParts() {
// 这里获得children
return children(Property.PARTS.toString(), Part::new);
}
}
/**
* Car entity
*/
public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts {
public Car(Map<String, Object> properties) {
super(properties);
}
}
/**
* Part entity
*/
public class Part extends AbstractDocument implements HasType, HasModel, HasPrice {
public Part(Map<String, Object> properties) {
super(properties);
}
}
Business Delegate(业务委托)
意图
业务委托模式在表示层和业务层之间添加了一个抽象层。通过使用该模式,可以获得层之间的松散耦合,并封装有关如何定位、连接和与构成应用程序的业务对象交互的知识。
适用性
- 需要在表示层和业务层之间进行松耦合
- 协调对多个业务服务的调用
- 封装服务查找和服务调用
案例
/**
* Interface for service implementations
*/
public interface BusinessService {
void doProcessing();
}
/**
* Service EJB implementation
*/
public class EjbService implements BusinessService {
private static final Logger LOGGER = LoggerFactory.getLogger(EjbService.class);
@Override
public void doProcessing() {
LOGGER.info("EjbService is now processing");
}
}
/**
* Service JMS implementation
*/
public class JmsService implements BusinessService {
private static final Logger LOGGER = LoggerFactory.getLogger(JmsService.class);
@Override
public void doProcessing() {
LOGGER.info("JmsService is now processing");
}
}
/**
* Enumeration for service types
*/
public enum ServiceType {
EJB, JMS;
}
/**
* Class for performing service lookups.
*/
public class BusinessLookup {
@Setter
private EjbService ejbService;
@Setter
private JmsService jmsService;
/**
* @param serviceType Type of service instance to be returned.
* @return Service instance.
*/
public BusinessService getBusinessService(ServiceType serviceType) {
if (serviceType.equals(ServiceType.EJB)) {
return ejbService;
} else {
return jmsService;
}
}
}
/**
* BusinessDelegate separates the presentation and business tiers
*/
public class BusinessDelegate {
@Setter
private BusinessLookup lookupService;
private BusinessService businessService;
@Setter
private ServiceType serviceType;
public void doTask() {
businessService = lookupService.getBusinessService(serviceType);
businessService.doProcessing();
}
}
public class Client {
private BusinessDelegate businessDelegate;
public Client(BusinessDelegate businessDelegate) {
this.businessDelegate = businessDelegate;
}
public void doTask() {
businessDelegate.doTask();
}
}
public static void main(String[] args) {
BusinessDelegate businessDelegate = new BusinessDelegate();
BusinessLookup businessLookup = new BusinessLookup();
businessLookup.setEjbService(new EjbService());
businessLookup.setJmsService(new JmsService());
businessDelegate.setLookupService(businessLookup);
businessDelegate.setServiceType(ServiceType.EJB);
Client client = new Client(businessDelegate);
client.doTask();
businessDelegate.setServiceType(ServiceType.JMS);
client.doTask();
}
Converter(转换器)
意图
Converter模式的目的是提供相应类型之间的通用,通用的双向转换方式,允许一种干净的实现,其中类型不需要彼此了解。 此外,转换器模式引入了双向收集映射,将样板代码减少到最小。
适用性
- 当有逻辑上与其他类型相对应的类型时,需要在它们之间转换实体
- 如果要根据上下文提供不同类型的转换方式
- 每当引入DTO(数据传输对象)时,可能需要将其转换为域等效
案例
@Data
@AllArgsConstructor
public class User {
private String firstName;
private String lastName;
private boolean isActive;
private String userId;
}
/**
* User DTO class
*/
@Data
@AllArgsConstructor
public class UserDto {
private String firstName;
private String lastName;
private boolean isActive;
private String email;
}
/**
* Generic converter, thanks to Java8 features not only provides a way of generic bidirectional
* conversion between corresponding types, but also a common way of converting a collection of objects
* of the same type, reducing boilerplate code to the absolute minimum.
*
* @param <T> DTO representation's type
* @param <U> Domain representation's type
*/
public class Converter<T, U> {
private final Function<T, U> fromDto;
private final Function<U, T> fromEntity;
/**
* @param fromDto Function that converts given dto entity into the domain entity.
* @param fromEntity Function that converts given domain entity into the dto entity.
*/
public Converter(final Function<T, U> fromDto, final Function<U, T> fromEntity) {
this.fromDto = fromDto;
this.fromEntity = fromEntity;
}
/**
* @param dto DTO entity
* @return The domain representation - the result of the converting function application on dto entity.
*/
public final U convertFromDto(final T dto) {
return fromDto.apply(dto);
}
/**
* @param entity domain entity
* @return The DTO representation - the result of the converting function application on domain entity.
*/
public final T convertFromEntity(final U entity) {
return fromEntity.apply(entity);
}
/**
* @param dtos collection of DTO entities
* @return List of domain representation of provided entities retrieved by
* mapping each of them with the conversion function
*/
public final List<U> createFromDtos(final Collection<T> dtos) {
return dtos.stream().map(this::convertFromDto).collect(Collectors.toList());
}
/**
* @param entities collection of domain entities
* @return List of domain representation of provided entities retrieved by
* mapping each of them with the conversion function
*/
public final List<T> createFromEntities(final Collection<U> entities) {
return entities.stream().map(this::convertFromEntity).collect(Collectors.toList());
}
}
/**
* Example implementation of the simple User converter.
*/
public class UserConverter extends Converter<UserDto, User> {
/**
* Constructor.
*/
public UserConverter() {
super(userDto -> new User(userDto.getFirstName(), userDto.getLastName(), userDto.isActive(),
userDto.getEmail()),
user -> new UserDto(user.getFirstName(), user.getLastName(), user.isActive(),
user.getUserId()));
}
}
EIP Aggregator
意图
有时在企业系统中,需要对传入的数据进行分组,以便将其作为一个整体进行处理。例如,您可能需要收集报价,在收到指定数量的报价后,您希望选择具有最佳参数的报价。
聚合器允许您根据定义的条件和参数合并消息。它收集原始消息,应用聚合策略,并在满足给定条件时释放合并消息。
适用性
- 需要合并多个传入消息
- 要处理分组数据
案例
EIP Message Channel
意图
当两个应用程序使用消息传递系统进行通信时,它们通过使用系统的逻辑地址(即所谓的消息通道)来进行通信。
适用性
- 两个或多个应用程序需要使用消息传递系统进行通信
案例
EIP Publish Subscribe
意图
从发送者向所有感兴趣的接收者广播消息。
适用性
- 两个或多个应用程序需要使用用于广播的消息传递系统进行通信。
案例
EIP Splitter
意图
在集成系统中,传入消息由捆绑在一起的许多项目组成是非常常见的。例如,发票单据包含多个描述交易的发票行(数量,提供的服务/售出商品的名称,价格等)。
这样的捆绑消息可能不会被其他系统接受。这是分离器模式派上用场的地方。它将处理整个文档,根据给定的标准对其进行拆分,然后将各个项目发送到端点。
适用性
- 需要将接收到的数据拆分成较小的部分,以便分别处理
- 需要控制能够处理的数据批的大小
案例
EIP Wire Tap
意图
在大多数集成情况下,需要监视流经系统的消息。 通常可以通过截取消息并将其重定向到其他位置(例如控制台,文件系统或数据库)来实现。 重要的是,此类功能不得修改原始消息并影响处理路径。
适用性
- 需要监视流经系统的消息
- 需要将相同但未更改的消息重定向到两个不同的端点/路径
案例
Tolerant Reader(容错阅读器)
意图
容错阅读器是一种集成模式,有助于创建健壮的通信系统。这样做的目的是在从另一个服务读取数据时尽可能宽容。这样,当通信模式改变时,Reader就不中断。
适用性
- 通信模式可以演变和改变,但接收端不应中断
案例
/**
* RainbowFish is the initial schema
*/
public class RainbowFish implements Serializable {
private static final long serialVersionUID = 1L;
@Getter
private String name;
@Getter
private int age;
@Getter
private int lengthMeters;
@Getter
private int weightTons;
/**
* Constructor
*/
public RainbowFish(String name, int age, int lengthMeters, int weightTons) {
this.name = name;
this.age = age;
this.lengthMeters = lengthMeters;
this.weightTons = weightTons;
}
}
/**
* RainbowFishV2 is the evolved schema
*/
public class RainbowFishV2 extends RainbowFish {
private static final long serialVersionUID = 1L;
@Getter
private boolean sleeping;
@Getter
private boolean hungry;
@Getter
private boolean angry;
public RainbowFishV2(String name, int age, int lengthMeters, int weightTons) {
super(name, age, lengthMeters, weightTons);
}
/**
* Constructor
*/
public RainbowFishV2(String name, int age, int lengthMeters, int weightTons, boolean sleeping,
boolean hungry, boolean angry) {
this(name, age, lengthMeters, weightTons);
this.sleeping = sleeping;
this.hungry = hungry;
this.angry = angry;
}
}
/**
* RainbowFishSerializer provides methods for reading and writing {@link RainbowFish} objects to
* file. Tolerant Reader pattern is implemented here by serializing maps instead of
* {@link RainbowFish} objects. This way the reader does not break even though new properties are
* added to the schema.
*/
public final class RainbowFishSerializer {
private RainbowFishSerializer() {
}
/**
* Write V1 RainbowFish to file
*/
public static void writeV1(RainbowFish rainbowFish, String filename) throws IOException {
Map<String, String> map = new HashMap<>();
map.put("name", rainbowFish.getName());
map.put("age", String.format("%d", rainbowFish.getAge()));
map.put("lengthMeters", String.format("%d", rainbowFish.getLengthMeters()));
map.put("weightTons", String.format("%d", rainbowFish.getWeightTons()));
try (FileOutputStream fileOut = new FileOutputStream(filename);
ObjectOutputStream objOut = new ObjectOutputStream(fileOut)) {
objOut.writeObject(map);
}
}
/**
* Write V2 RainbowFish to file
*/
public static void writeV2(RainbowFishV2 rainbowFish, String filename) throws IOException {
Map<String, String> map = new HashMap<>();
map.put("name", rainbowFish.getName());
map.put("age", String.format("%d", rainbowFish.getAge()));
map.put("lengthMeters", String.format("%d", rainbowFish.getLengthMeters()));
map.put("weightTons", String.format("%d", rainbowFish.getWeightTons()));
map.put("angry", Boolean.toString(rainbowFish.isAngry()));
map.put("hungry", Boolean.toString(rainbowFish.isHungry()));
map.put("sleeping", Boolean.toString(rainbowFish.isSleeping()));
try (FileOutputStream fileOut = new FileOutputStream(filename);
ObjectOutputStream objOut = new ObjectOutputStream(fileOut)) {
objOut.writeObject(map);
}
}
/**
* Read V1 RainbowFish from file
*/
public static RainbowFish readV1(String filename) throws IOException, ClassNotFoundException {
Map<String, String> map = null;
try (FileInputStream fileIn = new FileInputStream(filename);
ObjectInputStream objIn = new ObjectInputStream(fileIn)) {
map = (Map<String, String>) objIn.readObject();
}
return new RainbowFish(map.get("name"), Integer.parseInt(map.get("age")), Integer.parseInt(map.get("lengthMeters")),
Integer.parseInt(map.get("weightTons")));
}
}
// Write V1
RainbowFish fishV1 = new RainbowFish("Zed", 10, 11, 12);
log.info("fishV1 name={} age={} length={} weight={}", fishV1.getName(),
fishV1.getAge(), fishV1.getLengthMeters(), fishV1.getWeightTons());
RainbowFishSerializer.writeV1(fishV1, "fish1.out");
// Read V1
RainbowFish deserializedFishV1 = RainbowFishSerializer.readV1("fish1.out");
log.info("deserializedFishV1 name={} age={} length={} weight={}",
deserializedFishV1.getName(), deserializedFishV1.getAge(),
deserializedFishV1.getLengthMeters(), deserializedFishV1.getWeightTons());
// Write V2
RainbowFishV2 fishV2 = new RainbowFishV2("Scar", 5, 12, 15, true, true, true);
log.info("fishV2 name={} age={} length={} weight={} sleeping={} hungry={} angry={}",
fishV2.getName(), fishV2.getAge(), fishV2.getLengthMeters(), fishV2.getWeightTons(),
fishV2.isHungry(), fishV2.isAngry(), fishV2.isSleeping());
RainbowFishSerializer.writeV2(fishV2, "fish2.out");
// Read V2 with V1 method
RainbowFish deserializedFishV2 = RainbowFishSerializer.readV1("fish2.out");
log.info("deserializedFishV2 name={} age={} length={} weight={}",
deserializedFishV2.getName(), deserializedFishV2.getAge(),
deserializedFishV2.getLengthMeters(), deserializedFishV2.getWeightTons());
Data Access Object(数据访问对象)
意图
对象为某种类型的数据库或其他持久性机制提供抽象接口。
适用性
- 要合并如何访问数据层时
- 要避免编写多个数据检索/持久层时
案例
Data Mapper(数据映射器)
意图
在对象和数据库之间移动数据的映射器层,同时使它们彼此独立并与映射器本身独立
适用性
- 想将数据对象与数据库访问层分离时
- 要编写多个数据检索/持久性实现时
案例
Repository(存储库)
意图
在域和数据映射层之间添加存储库层,以将域对象与数据库访问代码的详细信息隔离开来,并将查询代码的分散和重复降至最低。 在使用大量域类或大量查询的系统中,存储库模式特别有用。
适用性
- 域对象的数量很大
- 要避免重复查询代码
- 想将数据库查询代码放在单个位置
- 有多个数据源
案例
- Spring Data
- Don’t use DAO, use Repository
- Advanced Spring Data JPA - Specifications and Querydsl
- Repository Pattern Benefits and Spring Implementation
Flux(通量)
意图
Flux避开了MVC,而倾向于单向数据流。当用户与视图交互时,视图通过中央调度器将操作传播到保存应用程序数据和业务逻辑的各种存储区,这些存储区更新所有受影响的视图。
适用性
- 您希望专注于为应用程序的数据创建显式且可理解的更新路径,这将使开发期间的跟踪更改更简单,并使错误更易于跟踪和修复。
案例
@ToString
public enum Content {
PRODUCTS("Products - This page lists the company's products."),
COMPANY("Company - This page displays information about the company.");
private String title;
private Content(String title) {
this.title = title;
}
}
@ToString
public enum MenuItem {
HOME("Home"),
PRODUCTS("Products"),
COMPANY("Company");
private String title;
MenuItem(String title) {
this.title = title;
}
}
/**
* Types of actions.
*/
public enum ActionType {
MENU_ITEM_SELECTED,
CONTENT_CHANGED;
}
/**
* Action is the data payload dispatched to the stores when something happens.
*/
public abstract class Action {
@Getter
private ActionType type;
public Action(ActionType type) {
this.type = type;
}
}
/**
* ContentAction is a concrete action.
*/
public class ContentAction extends Action {
@Getter
private Content content;
public ContentAction(Content content) {
super(ActionType.CONTENT_CHANGED);
this.content = content;
}
}
/**
* MenuAction is a concrete action.
*/
public class MenuAction extends Action {
@Getter
private MenuItem menuItem;
public MenuAction(MenuItem menuItem) {
super(ActionType.MENU_ITEM_SELECTED);
this.menuItem = menuItem;
}
}
/**
* Views define the representation of data.
*/
public interface View {
void storeChanged(Store store);
void render();
}
/**
* ContentView is a concrete view.
*/
@Slf4j
public class ContentView implements View {
private Content content = Content.PRODUCTS;
@Override
public void storeChanged(Store store) {
ContentStore contentStore = (ContentStore) store;
content = contentStore.getContent();
render();
}
@Override
public void render() {
log.info(content.toString());
}
}
/**
* MenuView is a concrete view.
*/
@Slf4j
public class MenuView implements View {
private MenuItem selected = MenuItem.HOME;
@Override
public void storeChanged(Store store) {
MenuStore menuStore = (MenuStore) store;
selected = menuStore.getSelected();
render();
}
@Override
public void render() {
for (MenuItem item : MenuItem.values()) {
if (selected.equals(item)) {
log.info("* {}", item);
} else {
log.info(item.toString());
}
}
}
public void itemClicked(MenuItem item) {
Dispatcher.getInstance().menuItemSelected(item);
}
}
/**
* Store is a data model.
*/
public abstract class Store {
private List<View> views = new LinkedList<>();
public abstract void onAction(Action action);
public void registerView(View view) {
views.add(view);
}
protected void notifyChange() {
views.forEach(view -> view.storeChanged(this));
}
}
/**
* ContentStore is a concrete store.
*/
public class ContentStore extends Store {
private Content content = Content.PRODUCTS;
@Override
public void onAction(Action action) {
if (action.getType().equals(ActionType.CONTENT_CHANGED)) {
ContentAction contentAction = (ContentAction) action;
content = contentAction.getContent();
notifyChange();
}
}
public Content getContent() {
return content;
}
}
/**
* MenuStore is a concrete store.
*/
public class MenuStore extends Store {
private MenuItem selected = MenuItem.HOME;
@Override
public void onAction(Action action) {
if (action.getType().equals(ActionType.MENU_ITEM_SELECTED)) {
MenuAction menuAction = (MenuAction) action;
selected = menuAction.getMenuItem();
notifyChange();
}
}
public MenuItem getSelected() {
return selected;
}
}
/**
* Dispatcher sends Actions to registered Stores.
*/
public final class Dispatcher {
private static Dispatcher instance = new Dispatcher();
private List<Store> stores = new LinkedList<>();
private Dispatcher() {
}
public static Dispatcher getInstance() {
return instance;
}
public void registerStore(Store store) {
stores.add(store);
}
/**
* Menu item selected handler
*/
public void menuItemSelected(MenuItem menuItem) {
dispatchAction(new MenuAction(menuItem));
switch (menuItem) {
case HOME:
case PRODUCTS:
default:
dispatchAction(new ContentAction(Content.PRODUCTS));
break;
case COMPANY:
dispatchAction(new ContentAction(Content.COMPANY));
break;
}
}
private void dispatchAction(Action action) {
stores.forEach(store -> store.onAction(action));
}
}
// initialize and wire the system
MenuStore menuStore = new MenuStore();
Dispatcher.getInstance().registerStore(menuStore);
ContentStore contentStore = new ContentStore();
Dispatcher.getInstance().registerStore(contentStore);
MenuView menuView = new MenuView();
menuStore.registerView(menuView);
ContentView contentView = new ContentView();
contentStore.registerView(contentView);
// render initial view
menuView.render();
contentView.render();
// user clicks another menu item
// this triggers action dispatching and eventually causes views to render with new content
menuView.itemClicked(MenuItem.COMPANY);
Front Controller(前控制器)
意图
为网站的所有请求引入公共处理程序。这样我们就可以在一个地方封装通用功能,比如安全性、国际化、路由和日志记录。
适用性
- 希望将公共请求处理功能封装在一个位置
- 希望实现动态请求处理,即在不修改代码的情况下更改路由
- 使web服务器配置可移植,只需注册特定于web服务器的处理程序
案例
- Apache Struts
- J2EE Design Patterns
- Presentation Tier Patterns
- Patterns of Enterprise Application Architecture
Model-View-Controller/MVC(模型视图控制器)
意图
将用户界面分为三个相互连接的组件:模型、视图和控制器。让模型管理数据,视图显示数据,控制器中介更新数据并重新绘制显示。
适用性
- 清楚地将域数据与其用户界面表示分离
案例
- Trygve Reenskaug - Model-view-controller
- J2EE Design Patterns
- Patterns of Enterprise Application Architecture
/**
* GiantModel contains the giant data
*/
@Data
public class GiantModel {
private Health health;
private Fatigue fatigue;
private Nourishment nourishment;
GiantModel(Health health, Fatigue fatigue, Nourishment nourishment) {
this.health = health;
this.fatigue = fatigue;
this.nourishment = nourishment;
}
}
/**
* GiantView displays the giant
*/
@Slf4j
public class GiantView {
public void displayGiant(GiantModel giant) {
log.info(giant.toString());
}
}
// create model, view and controller
GiantModel giant = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED);
GiantView view = new GiantView();
GiantController controller = new GiantController(giant, view);
// initial display
controller.updateView();
// controller receives some interactions that affect the giant
controller.setHealth(Health.WOUNDED);
controller.setNourishment(Nourishment.HUNGRY);
controller.setFatigue(Fatigue.TIRED);
// redisplay
controller.updateView(); // create model, view and controller
GiantModel giant = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED);
GiantView view = new GiantView();
GiantController controller = new GiantController(giant, view);
// initial display
controller.updateView();
// controller receives some interactions that affect the giant
controller.setHealth(Health.WOUNDED);
controller.setNourishment(Nourishment.HUNGRY);
controller.setFatigue(Fatigue.TIRED);
// redisplay
controller.updateView();
Model-View-Presenter
意图
以允许开发人员构建和测试用户界面的方式应用“关注点分离”原则。
适用性
- 想要改进表示逻辑中的“关注点分离”原则时
- 需要用户界面开发和测试时
案例
Page Object(页面对象)
意图
Page Object封装了UI,隐藏了应用程序的底层UI小部件(通常是Web应用程序),并提供了特定于应用程序的API,以允许操作测试所需的UI组件。 这样做,它允许测试类本身专注于测试逻辑。
适用性
- 正在为Web应用程序编写自动化测试,并且您希望将测试所需的UI操作与实际测试逻辑分开。
- 使您的测试不那么脆弱,更易读和更强大
案例
Async Method Invocation(异步方法调用)
意图
异步方法调用是在等待任务结果时不阻塞调用线程的模式。该模式提供了对多个独立任务的并行处理,并通过回调或等待一切完成来检索结果。
适用性
- 有多个可以并行运行的独立任务
- 需要提高一组连续任务的性能
- 处理能力或长时间运行的任务有限,调用者不应等待任务准备就绪
案例
- FutureTask, CompletableFuture and ExecutorService (Java)
- Task-based Asynchronous Pattern (.NET)
/**
* AsyncExecutor interface.
*/
public interface AsyncExecutor {
/**
* Starts processing of an async task. Returns immediately with async result.
*
* @param task task to be executed asynchronously
* @return async result for the task
*/
<T> AsyncResult<T> startProcess(Callable<T> task);
/**
* Starts processing of an async task. Returns immediately with async result. Executes callback
* when the task is completed.
*
* @param task task to be executed asynchronously
* @param callback callback to be executed on task completion
* @return async result for the task
*/
<T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback);
/**
* Ends processing of an async task. Blocks the current thread if necessary and returns the
* evaluated value of the completed task.
*
* @param asyncResult async result of a task
* @return evaluated value of the completed task
* @throws ExecutionException if execution has failed, containing the root cause
* @throws InterruptedException if the execution is interrupted
*/
<T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, InterruptedException;
}
/**
* AsyncResult interface.
*
* @param <T> parameter returned when getValue is invoked
*/
public interface AsyncResult<T> {
/**
* Status of the async task execution.
*
* @return <code>true</code> if execution is completed or failed
*/
boolean isCompleted();
/**
* Gets the value of completed async task.
*
* @return evaluated value or throws ExecutionException if execution has failed
* @throws ExecutionException if execution has failed, containing the root cause
* @throws IllegalStateException if execution is not completed
*/
T getValue() throws ExecutionException;
/**
* Blocks the current thread until the async task is completed.
*
* @throws InterruptedException if the execution is interrupted
*/
void await() throws InterruptedException;
}
/**
* AsyncCallback interface.
*
* @param <T> Type of Result
*/
public interface AsyncCallback<T> {
/**
* Complete handler which is executed when async task is completed or fails execution.
*
* @param value the evaluated value from async task, undefined when execution fails
* @param ex empty value if execution succeeds, some exception if executions fails
*/
void onComplete(T value, Optional<Exception> ex);
}
/**
* Implementation of async executor that creates a new thread for every task.
*/
public class ThreadAsyncExecutor implements AsyncExecutor {
/**
* Index for thread naming.
*/
private final AtomicInteger idx = new AtomicInteger(0);
@Override
public <T> AsyncResult<T> startProcess(Callable<T> task) {
return startProcess(task, null);
}
@Override
public <T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback) {
var result = new CompletableResult<>(callback);
new Thread(() -> {
try {
result.setValue(task.call());
} catch (Exception ex) {
result.setException(ex);
}
}, "executor-" + idx.incrementAndGet()).start();
return result;
}
@Override
public <T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException,
InterruptedException {
if (!asyncResult.isCompleted()) {
asyncResult.await();
}
return asyncResult.getValue();
}
/**
* Simple implementation of async result that allows completing it successfully with a value or
* exceptionally with an exception. A really simplified version from its real life cousins
* FutureTask and CompletableFuture.
*
* @see java.util.concurrent.FutureTask
* @see java.util.concurrent.CompletableFuture
*/
private static class CompletableResult<T> implements AsyncResult<T> {
static final int RUNNING = 1;
static final int FAILED = 2;
static final int COMPLETED = 3;
final Object lock;
final Optional<AsyncCallback<T>> callback;
volatile int state = RUNNING;
T value;
Exception exception;
CompletableResult(AsyncCallback<T> callback) {
this.lock = new Object();
this.callback = Optional.ofNullable(callback);
}
/**
* Sets the value from successful execution and executes callback if available. Notifies any
* thread waiting for completion.
*
* @param value value of the evaluated task
*/
void setValue(T value) {
this.value = value;
this.state = COMPLETED;
this.callback.ifPresent(ac -> ac.onComplete(value, Optional.<Exception>empty()));
synchronized (lock) {
lock.notifyAll();
}
}
/**
* Sets the exception from failed execution and executes callback if available. Notifies any
* thread waiting for completion.
*
* @param exception exception of the failed task
*/
void setException(Exception exception) {
this.exception = exception;
this.state = FAILED;
this.callback.ifPresent(ac -> ac.onComplete(null, Optional.of(exception)));
synchronized (lock) {
lock.notifyAll();
}
}
@Override
public boolean isCompleted() {
return state > RUNNING;
}
@Override
public T getValue() throws ExecutionException {
if (state == COMPLETED) {
return value;
} else if (state == FAILED) {
throw new ExecutionException(exception);
} else {
throw new IllegalStateException("Execution not completed yet");
}
}
@Override
public void await() throws InterruptedException {
synchronized (lock) {
while (!isCompleted()) {
lock.wait();
}
}
}
}
}
Balking(阻止)
意图
阻止模式用于防止对象在不完整或不适当的状态下执行某些代码
适用性
- 仅当对象处于特定状态时才要对其调用操作
- 对象通常只处于一种容易暂时停止的状态,但有一段未知的时间
案例
- Guarded Suspension Pattern
- Double Checked Locking Pattern
/**
* WashingMachineState enum describes in which state machine is, it can be enabled and ready to work
* as well as during washing.
*/
public enum WashingMachineState {
ENABLED,
WASHING
}
/**
* An interface to simulate delay while executing some work.
*/
public interface DelayProvider {
void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task);
}
/**
* Washing machine class.
*/
public class WashingMachine {
private static final Logger LOGGER = LoggerFactory.getLogger(WashingMachine.class);
private final DelayProvider delayProvider;
@Getter
private WashingMachineState washingMachineState;
/**
* Creates a new instance of WashingMachine.
*/
public WashingMachine() {
this((interval, timeUnit, task) -> {
try {
Thread.sleep(timeUnit.toMillis(interval));
} catch (InterruptedException ie) {
ie.printStackTrace();
}
task.run();
});
}
/**
* Creates a new instance of WashingMachine using provided delayProvider. This constructor is used
* only for unit testing purposes.
*/
public WashingMachine(DelayProvider delayProvider) {
this.delayProvider = delayProvider;
this.washingMachineState = WashingMachineState.ENABLED;
}
/**
* Method responsible for washing if the object is in appropriate state.
*/
public void wash() {
synchronized (this) {
var machineState = getWashingMachineState();
LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), machineState);
if (this.washingMachineState == WashingMachineState.WASHING) {
LOGGER.error("ERROR: Cannot wash if the machine has been already washing!");
return;
}
this.washingMachineState = WashingMachineState.WASHING;
}
LOGGER.info("{}: Doing the washing", Thread.currentThread().getName());
this.delayProvider.executeAfterDelay(50, TimeUnit.MILLISECONDS, this::endOfWashing);
}
/**
* Method responsible of ending the washing by changing machine state.
*/
public synchronized void endOfWashing() {
washingMachineState = WashingMachineState.ENABLED;
LOGGER.info("{}: Washing completed.", Thread.currentThread().getId());
}
}
Double Checked Locking(双重检查)
意图
通过首先测试锁定条件(“锁提示”)而不是实际获取锁,减少获取锁的开销。只有当锁定条件检查指示需要锁定时,实际锁定逻辑才会继续。
适用性
- 在对象创建中有一个并发访问,例如singleton,在这里您希望创建同一类的单个实例,并且当有两个或多个线程检查实例是否为空时,检查它是否为空可能是不够的。
- 方法上有一个并发访问,其中方法的行为根据某些约束而改变,并且这些约束在此方法内改变。
案例
/**
* Item.
*/
public class Item {
}
/**
* Inventory.
*/
public class Inventory {
private static final Logger LOGGER = LoggerFactory.getLogger(Inventory.class);
private final int inventorySize;
private final List<Item> items;
private final Lock lock;
/**
* Constructor.
*/
public Inventory(int inventorySize) {
this.inventorySize = inventorySize;
this.items = new ArrayList<>(inventorySize);
this.lock = new ReentrantLock();
}
/**
* Add item.
*/
public boolean addItem(Item item) {
if (items.size() < inventorySize) {
lock.lock