在15分钟内开始使用23种设计模式:图表、示例和比较在面对代码设计问题时更加自信。本文源代码:UML,SampleCode。开门见山,开门见山,分类呈现庐山23种设计模式的真面目:Creational(5)Creationalstructural(7)Structuralbehavioral(11)Behavioral工厂方法工厂方法抽象工厂AbstractfactorybuilderBuilderprototype原型单例SingleTonAdapterBridgeCompositionCompositeDecoratorDecoratorAppearanceFacadeFlyweightProxyProxyChainofResponsibilityCommandInterpreterInterpreterIteratorIteratorMediatorMementoMementoObserverStateStrategyStrategyTemplateMethodTemplatemethodVisitor这23个设计模式来自《设计模式-要素》一书GoF着的《ReusableObject-OrientedSoftware》(本书也直接简称为GoF),译为《DesignPatterns:TheFoundationofReusableObject-OrientedSoftware》。原著将这23种设计模式分为三类:创建型包含5种模式,涉及对象/对象组合的创建和构建。Structural包含7种模式,处理对象/类之间的关系。行为类型包含11种模式,涉及对象/类的行为、状态、过程。从书名我们可以了解到设计模式是面向对象开发方式下的一个概念,是解决代码设计/软件架构问题的可复用元素,也是一个基本元素(elements).引用原著中的例子,我们都熟悉的MVC模式,Model-View-Controller,可以解构为几种设计模式的组合和演化。比如观察者模式Observer,组合模式在View和Model的关系中可以看到Composite,装饰模式Decorator,在Controller中找到了策略模式的影子。通过对23种基本模式的有机利用和组合,可以进一步演化出更复杂的软件架构。限于篇幅,本文将不对每种设计模式的定义和背景进行说明。读者可以参考设计模式简介来了解定义。在设计模式的UML、类比和实例这一部分,我们逐步从尝鲜到类比,深入理解设计模式的一些常见和有趣的UML及其经典实例。GoF原著也推荐学习者从“模式之间是如何相关的”和“具有相似研究目的的模式”来学习和选择设计模式的。先看最简单最常见的策略模式和另一种也是行为模式的状态模式:StrategypatternStrategystatepatternStateUMLexample-Comparator#compare()andCollections#sort()-SpringSecurity:PasswordEncoder-Standardexample:javax.faces.lifecycle.LifeCycle#execute()-类似示例:JavaThreadState,ExoPlayer概述让外部对算法的相互替换不敏感允许一个对象根据内部状态改变行为关键字Strategy,ruleState,switch,阶段、生命周期核心角色StrategyState策略模式和状态模式在UML图形中非常相似。它们的主要区别在于:状态对象可以持有上下文对象(调用者),而策略模式一般都有这种依赖。状态模式可以通过彼此之间的跳转来代替。比如调用播放器的play方法,状态可能会从stop->playing变化。这个操作可以用状态对象来完成。策略与调用者的关系(依赖性)可能弱于状态与上下文对象(持有、属性)的关系。策略上的差异可能只影响一种行为,但状态上的差异会影响状态持有对象行为的方方面面。整体来说,策略模式比状态模式更简洁易懂,应用场景更广,在大型项目中的应用随处可见。状态模型虽然也是对常用概念的抽象,但其应用比较有限。原因可能是在更多情况下,定义不同状态下行为的差异可能不是一种直观的操作:而不是将状态也定义为携带对象的行为。最好将状态定义为标记,直接使用if或者switch来判断。或者换句话说,在大多数情况下,问题还没有复杂到可以使用状态模式。以这种对比的视角,让我们了解更多的模式。我们来看看下面三种结构设计模式:Adapter桥接模式外观UML例子RecyclerView.Adapter例子比较少:-Collections#newSetFromMap()-(ADB?),比如Spring中Service和Repository的关系很通用的,例如:Facades、FacesContext、ExternalContext、DataSource#getConnection()使用接口关键字AdatperWrapperContext核心作用Adpter、AdapteeBridgeFacade适配器模式、桥接模式和外观模式属于结构设计模式。他们三个在概念上非常相似。它们都是通过建立接口来为类方法建立或重构关系。比如,貌似我们用appearance的角度来解释adapter,也是可以解释的。Adapter是帮助Adaptee建立一个统一的接口,或者说建立一个桥梁。设计模式是这样的。如果你必须认真的话,所有的设计模式都是相似的(至少在一种类型中)。这是学习设计模式的一个误区。回到上面三种设计模式,它们的核心区别更多体现在时机和出发点上:Adapter强调兼容性,Bridge强调抽象与实现的分离,Facade强调复杂化的简单化。我们还应该从区分这些模式的意图出发。Spring的三层结构也体现了Facade和Bridge的设计。Service和Repository的关系体现了Bridge模式的概念,而Controller和Service的关系更像是Facade模式:Controller集成Service并对外提供API:我们再看几个常见行为模式的类比分析:代理装饰中介UML范例-JavaReflectAPI:代理-JavaEJB:EnterpriseJavaBean、JavaXInject、JavaXPersistenceContext-ActivityManager和ActivityManagerService-PerformanceInspectionService和PerformanceTestManagementService-JavaIO:GZIPOutputStream和OutputStream、Reader和BufferedReader)-java.util.Collections,checkedXXX(),synchronizedXXX()和unmodifiableXXX()系列方法,扩展集合-HttpServletRequestWrapper和HttpServletResponseWrapper-JScrollPane-JavaMessageService,JMSbyOracle-ulejava.util.Timer(allsched)方法),java.util.concurrent.ExecutorService(invokeXXX()和submit()方法)概述通过代理控制对象的访问动态地为对象添加功能封装对象之间的交互(麦克风)关键字DelegateWrapperMessageQueue,Dispatcher核心作用ProxyDecoratorMediator这里,从类之间的关系,proxy和decoration比较类似,但是intermediary不同,只是名字上类似proxy。代理(访问和控制)和装饰(增强和扩展)的区别也可以从目的和意图的角度来区分。以代理为例,其主要功能是建立访问通道。例如在Android中,Binder用于应用程序与系统之间进行IPC,而应用进程与系统进程之间,大量代理方式用于这种IPC调用,到处都出现名为Proxy的对象。在设计HydraLab的过程中,为了让测试用户能够在测试实例中方便的通过SDK访问一些HydraLabTestAgent服务方法,我们还应用了简洁的静态代理来实现这种在不同环境下的访问。在代理模式下,有了访问通道,自然要控制通信,比如基于权限或者基于格式的验证。装饰模式侧重于增强和扩展,比如BufferedRead对Reader的增强。从这个角度来说,如果一个类叫AuthWrapper就奇怪了,AuthProxy比较常见,因为授权的操作显然更强调控制。当然要看具体情况。中介其实是一个很宽泛的概念,将通信中的两方或多方解耦,各种流行的MQ框架其实都是这种模型的衍生。ObserverVisitorUMLParadigm-java.util.Observer,Observable-java.util.EventListener-ReactiveX接口观察者-AnnotationValueVisitor-ElementVisitor-TypeVisitor-SimpleFileVisitor-VisitCallback-ClassVisitor(ASM9.4)Observable,Observer,Subject,SubscriptionVisitor核心作用Observer,SubjectVisitor,Element这两种模式在实现上没有太多联系。但是他们都想“读”,不会直接改变被读对象的状态。观察者通过订阅和聆听被动阅读,而访问者则是主动视角,以独特的方式阅读。与观察者非常相似的“听者”是一个更普遍的概念,更轻巧,因此更广泛。责任链备忘录UML示例-OkHttp拦截器-java.util.logging.Logger-javax.servlet.Filter-Activity#onSaveInstanceState(...))-Java可序列化概述建立处理链传递请求捕获对象状态并将其保存以用于stateRecovery关键词Chain,Interceptor,Filter,proceed,ResponseState,Lifecycle,Context核心角色HandlerMemonto,Originator,Caretaker职责链和memo模式虽然意图和设计不同,但两者都有很强的IoCcontrolinversionTaste密切相关生命周期的设计。玩游戏的同学最容易建立对备忘录模式的理解。保存是一种持久状态。游戏本身的存读服务就充当看门人的角色,帮助你确保你的肝脏进度不会白费。所以备忘录模式其实很常见,在软件世界里随处可见。命令解释器UML示例-IShellOutputReceiver-JavaRunnable-java.util.Pattern-java.text.Normalizer-java.text.Format-javax.el.E??LResolver概述将请求封装为对象,以便于参数化和请求队列管理定义语法和表达式关键字ExecutorExpression核心作用Command,Receiver,Invoker(Executor)Interpretor,Expression以上两者不能直接比较,但是两者结合起来,命令的解释和执行是一气呵成的,一个脚本语言c执行器的原型出生。这里的命令方式其实比“命令”本身在设计上更贴心,也包含了对执行结果接收接口的预留。AbstractFactoryFactoryMethodUML实例-DocumentBuilderFactory(JavaX)-TransformerFactory(JavaX)-XPathFactory(JavaX)-BeanFactory#getBeanProvider(Spring)java.util.Calendar#getInstance()概述将一个类的接口转换为满足要求的另一个类的接口另一个接口由工厂的子类决定要创建的实例对象关键字Factory,new...,create...Factory,newInstance,Creator核心角色AbstractFactoryCreator其他模式包括:建造者模式,原型模式,享元模式),单例;作品;模板方法,迭代器。这些模式要么不常用,要么太普通太普通,而且比较简单。限于篇幅,本文不再一一详述。通过这个触类旁通的过程,我们可能会逐渐觉得设计模式的重点不在类之间关系的严格定义、罗列和排列上,无意义的争论和争论会陷入“把设计模式当成严格的”的误区。“学术理论”。更多的是,我们应该从问题的意图出发,发散性地思考解决方案中可能包含的设计元素,然后根据实际情况将其缩小到一个合理的尺度。因此,我们不不必执着于对两种相似模式的严格定义和区分,比如我们不需要反驳一种实现是作为代理还是装饰,而是要了解两者的视角和意图应用:如果强调功能扩展,那么设计方案就是装饰;如果强调访问控制,那就是代理。很多初学者觉得很多模式都是相似的,冗余的。这是一个正常的感受和学习阶段;在更多的应用和实战中,你会成长并洞察更多模式的意义;后来你成了设计高手,可以灵活运用设计模式、AOP、函数公式、算法,甚至ML解决各种问题,讲述和推动解决方案的实现,设计模式的讨论和争论只是闲聊晚饭后。这一点在原著中“如何选择设计模式”一章中也有提到。综上所述,初学者在学习设计模式时,重点可以放在:这个设计模式解决什么类型的问题,意图是什么,它是如何抽象概念(关键角色)和解决(接口、关系)的。将设计模式作为大家交流软件设计的语言,掌握这些术语,降低交流成本。Howtolearnandusedesignpatterns这部分内容来源于GoF原著《Howtousedesignpatterns》第1.8章内容,将原书7步简化为6步,并去除翻译重音:一次浏览模式,掌握关键模式元素的名称是什么,意图是什么,其中的关键作用是什么,常用关键字?回过头来研究结构部分、参与者部分和协作部分,进一步了解角色的职责和关系,有哪些接口,以及模型的适用性:这个模型更适合解决什么类型的问题?查看示例代码示例可以让我们了解该模式解决的实际问题,并成为我们实施的参考。参考模式中的命名方法。例如在Strategy模式下,可以直接在算法名的末尾加上Strategy来体现这种模式;再比如,你可以在方法的开头使用create作为前缀来突出工厂方法。定义类和接口选择模式并完成命名后,下一步就是建立类和接口之间的继承/实现关系,定义代表数据和对象引用的实例变量。实施模式开始实施基于模式的解决方案。设计模式的引入伴随着一定的成本,其中之一就是学习成本和复杂度的增加,也可能会有性能损失(虽然可以忽略不计),但是它给架构带来了灵活性,使其更加清晰和可维护。接下来,作为延伸阅读,我们可以讨论一下设计模式的含义,加深理解。设计模式的意义和批判在谈到为什么需要设计模式时,首先要回答什么设计才是好的设计。软件是对现实问题复杂性的抽象和管理。Bob大叔说:“软件应该是多变的”,正如“现实世界中唯一不变的就是不断变化”一样,软件应该能够灵活应对现实世界的需求。所以我们会讨论软件架构的可扩展性、可维护性、高可用性、可重用性、可移植性等。如果您只是一个接一个地编写脚本、一次性工具或编程练习,当然不要使事情复杂化。但是如果你想让你的软件有更强的生命力和更广阔的前景,那么你就必须认真对待软件设计,防止代码损坏。另外,一个人的力量是有限的。如果要通过协作来扩大软件的服务范围和影响力,那么可读性也很重要。“好的代码就像写好的散文”,好的代码应该像优美的散文;至少不言自明。引用GoF的原文,“所有结构良好的面向对象软件架构都包含很多模式……专家设计者都知道:不是要从头开始解决任何问题……这些模式解决特定的设计问题,使面向对象设计更灵活、更优雅,最终更可重用。”所以这里的两层意思一方面强调模式对软件设计本身的好处,另一方面说明这些模式在面向对象设计中建立了大家的共识和交流基础。另外,大师们还总结了一些designprinciples构架一个好的设计SOLIDdesignprinciples虽然很多教程把设计原则和设计模式放在一起讨论,暗示设计模式遵循设计原则,但实际上它们不是一个家族的。而且设计原则有很多版本。这里我们分享一下Bob大叔提出的最难忘的版本,SOLIDprinciple:Singleresponsibility,单一职责原则SRP:就一个类而言,它的变化应该只有一个原因open-close,open-closedprincipleOCP:软件实体应该对扩展开放,对修改关闭。里氏代换,里氏代换原则LSP。子类型必须能够替换它们的父类型。用子类实例替换父类实例,程序行为不应该改变。Interfacesegregation,接口隔离原则ISP。一个类对另一个类的依赖应该以最小的接口为准。客户端程序不应该依赖于它不需要的接口方法(函数)。Dependencyinverse,依赖倒置原则DIP:高层模块不应该依赖低层模块。两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。我们可以把这些设计模式看作是思考设计问题的准绳,也可以认为它们只是一个概念。正如bob大叔所说:“SOLID原则不是规则。它们不是法律。它们不是完美的真理……这是一个很好的原则,这是一个很好的建议……”。总之,了解这些可以帮助我们把握思考的方向,但不能帮助我们解决问题。也就是说,对于初学者来说,设计原则可能没有设计模式那么强的实际意义。对设计模式的批评来自于它产生的那个时代的主流对编程语言局限性的挑战,以及对面向对象本身的质疑。有人认为设计模式反映了Java和C++本身缺乏语言特性;有那么多冗长的设计模式。对此,笔者认为纯粹理论上的对错并没有那么重要。软件开发是科学与艺术的结合,设计模式是一个时代开发者思维的精髓。它们带给我们的不仅仅是具体的解决方案,更多的是一种解决问题的思维方式,这本身就存在于大量的编程实践中。GoF对它们进行了提炼和总结,这本身就是一项重大成就,更不用说它们已经成为工程师文化的一部分,成为Terminology。比如我们不仅可以看到AOP的应用,函数式编程的应用,还有builder,工厂模式,策略等等从Spring中的应用。编程高手应该知识渊博,兼收并蓄。代码的艺术在于灵活及时的使用。绝对不可因为固执的信念而拒绝经典或新知识。其他常见问题FAQQ:设计模式和后来流行的Reactive、函数式编程、AOP、IoC、DI有什么关系?A:总的来说,这些都是不同维度的概念,总结如下表:注入是一种设计模式AOPAspect-orientedprogramming,面向方面的编程编程范式(object-oriented)functionalprogramming函数式编程编程范式(declarative)ReactiveReactiveprogramming,响应式编程编程范式(declarative)microservicesMicroservicesArchitecturalPatterns(ServiceOrientedArchitecture)Q:还有其他设计模式吗?A:是的,随着软件开发实践的演进,总结出越来越多的设计模式,但可能还没有经典将它们整理成书。例如,常见的锁双重检查也被认为是一种语言无关的并发设计模式。DI也是一种设计模式ConcurrencyPatterns,其作用包括注入器Injector、服务Service、客户端Client和接口Interfaces。DI也是一种创建型Creationaldesignpattern,其意图是优化类之间的依赖关系,因此与整个软件或模块的架构关系更为密切。参考设计模式,可重用面向对象软件的元素https://en.wikipedia.org/wiki...http://butunclebob.com/Articl...https://en.wikipedia.org/wiki...https://sites.google.com/site...https://en.wikipedia.org/wiki...https://www.jianshu.com/p/8cb...https://coderanch.com/t/99717...https://stackoverflow.com/que...https://stackoverflow.com/que...https://stackoverflow.com/que...关于我我是风云行走,目前在微软中国担任研发经理。希望在这个空间与大家分享和交流技术经验、职业、团队和项目管理、趋势动态。目的是畅所欲言,畅所欲言,畅所欲言,不拘细节;但也不排除问底线、钻牛角尖的情况。热爱技术和编码,尤其享受用技术解决实际问题的过程和结果;我相信创造力是一种顶级能力,是人类价值的放大器。此外,我多年来一直关注软件和代码质量、工程效率和研发能效。目前在微软与团队合作,推动2023年新开源项目HydraLab的完善和发展;欢迎在开源世界中与我合作创建Wheels或构建块。
