asp.net网站开发教程 pdf,网站建设项目书,免费购物网站系统,WordPress禁用f12导语#xff1a;在软工程中#xff0c;设计模式#xff08;design pattern#xff09;是对软件设计中普遍存在#xff08;反复出现#xff09;的各种问题#xff0c;所提出的解决方案。这个术语是由埃里希伽玛#xff08;Erich Gamma#xff09;等人在1990年代从建筑设… 导语在软工程中设计模式design pattern是对软件设计中普遍存在反复出现的各种问题所提出的解决方案。这个术语是由埃里希·伽玛Erich Gamma等人在1990年代从建筑设计领域引入到计算机科学的设计模式是针对软件设计中常见问题的工具箱其中的工具就是各种经过实践验证的解决方案。即使你从未遇到过这些问题了解模式仍然非常件有用因为它能指导你如何使用面向对象的设计原则来解决各种问题。大家好我是Alex今天谈一谈设计模式一名优秀的开发应该多少都需要了解一些常用的设计模式和使用场景让我们一起来重温一下那些年经典设计模式本文主要内容为什么要掌握设计模式历史的教训时间回到 20 世纪 80 年代当时的软件行业正处于第二次软件危机中。根本原因是随着软件规模和复杂度的快速增长如何高效高质的构建和维护这样大规模的软件成为了一大难题。无论是开发何种软件产品成本和时间都最重要的两个维度。较短的开发时间意味着可比竞争对手更早进入市场较低的开发成本意味着能够留出更多营销资金因此能更广泛地覆盖潜在客户。设计模式是银弹吗代码复用是减少开发成本减低复杂度最常用的方式之一这个想法表面看起来很棒但实际上要让已有代码在全新的上下文中工作通常还是需要付出额外努力的。组件间紧密的耦合、对具体类而非接口的依赖和硬编码的行为都会降低代码的灵活性使得复用这些代码变得更加困难。设计模式目标就是帮助软件提高内聚减低耦合使用设计模式是增加软件组件灵活性并使其易于复用的方式之一。变化是程序员生命中唯一不变的事情客户需求可能经常会变紧急上线的版本要不要下次重构一下还是继续打各种补丁 技术债会越积越多因此在设计程序架构时所有有经验的开发者会尽量选择支持未来任何可能变更的方式。可扩展性成为了程序设计必须要考虑指标而设计模式是可以借鉴的成熟的优化程序设计的解决方案总体来说深刻理解设计模式会给我们带来很多好处可以和面试官畅谈设计模式相关问题.很多开源软件框架大量使用了设计模式比如Linux系统RedisSpringCSTL等等可以把帮你加快理解开源软件框架。当你写的代码越来越优美后你的代码鉴赏能力就会提高对团队code review贡献也会更大在个人影响力也会提高。你不会再畏手畏脚你的工具箱里面工具很多后可以帮助你应对各种大型项目的代码设计和开发。每个领域都会一些成熟套路, 编程也不例外熟悉这些套路可以更好方便交流和更快速地解决问题为了更好理解设计模式我们首先要理解一些重要的设计原则而不是片面理解设计模式哪些模式名词要看清楚这背后的原理这个才是最重要的。代码设计原则代码设计原则贯穿在整个设计模式之中是理解其中的精华本文讨论了一些重要的设计原则包括通用设计原则DRY原则KISS原则SOLID原则等通用设计原则隔离变化找到程序中的变化内容并将其与不变的内容区分开该原则的主要目的是将变更造成的影响最小化。面向接口编程面向接口进行开发 而不是面向实现依赖于抽象类型而不是具体类要求接口标准化设计只要对外的接口没有变内部实现就可以任意变化为以后留有更多优化空间方便以后更新迭代可以说这样的设计是灵活的。组合优于继承继承可能是类之间最明显、最简便的代码复用方式。如果你有两个代码相同的类 就可以为它们创建一个通用的基类然后将相似的代码移动到其中。但继承可能带来的问题子类不能减少超类的接口。你必须实现父类中所有的抽象方法即使它们没什么用。在重写方法时你需要确保新行为与其基类中的版本兼容。这一点很重要因为子类的所有对象都可能被传递给以超类对象为参数的任何代码相信你不会希望这些代码崩溃的。继承打破了超类的封装因为子类拥有访问父类内部详细内容的权限。此外还可能会有相反的情况出现那就是程序员为了进一步扩展的方便而让超类知晓子类的内部详细内容。子类与超类紧密耦合。超类中的任何修改都可能会破坏子类的功能。通过继承复用代码可能导致平行继承体系的产生。继承通常仅发生在一个维度中。只要出现了两个以上的维度你就必须创建数量巨大的类组合从而使类层次结构膨胀到不可思议的程度。组合是代替继承的一种方法。继承代表类之间的“是”关系汽车是交通工具而组合则代表“有”关系汽车有一个引擎。DRY 原则DRY-Dont Repeat Yourself不要重复代码降低可管理单元的复杂度的基本策略是将系统分成多个部分。理解这一原理是如此重要它通常以首字母缩写词DRY来指代并出现在Andy Hunt和Dave Thomas的书《实用程序员》中但是这个概念本身已经有很长时间了。它指的是软件的最小部分。当您构建一个大型软件项目时通常会因整体复杂性而感到不知所措。人类不善于管理复杂性他们擅长为特定范围的问题找到有创意的解决方案。降低可管理单元的复杂性的基本策略是将系统分成更方便的部分。首先您可能希望将系统分为多个组件其中每个组件代表其自己的子系统其中包含完成特定功能所需的一切。KISS 原则KISS是使它保持简单愚蠢的首字母缩写是美国海军在1960年提出的设计原则。KISS原则指出大多数系统如果保持简单而不是变得复杂则效果最佳。因此简单性应该是设计的主要目标并且应该避免不必要的复杂性。SOLID 原则SOLID 原则是在罗伯特·马丁的著作《敏捷软件开发原则、模式与实践》中首次提出的SOLID 是让软件设计更易于理解、更加灵活和更易于维护的五个原则的简称。尽量让每个类或者函数只负责软件中的一个功能这条原则的主要目的是减少复杂度你不需要费尽心机地去构思如何仅用200 行代码来实现复杂设计实际上完全可以使用十几个清晰的方法这里核心是 通过实现最基本原子函数, 其他复杂功能都可以通过这些原子函数构建每一层的函数语义都是单一的通过层层封装最终构建一个庞大可控的系统。 本原则的主要理念是在实现新功能时能保持已有代码不变为什么呢主要是修改存量代码很可能会影响软件稳定性很多线上代码跑了好多年了经历很多轮迭代各种补丁如果考虑不全面很容易带来风险下图比较形象说明替换原则是用于预测子类是否与代码兼容以及是否能与其超类对象协作的一组检查。这一概念在开发程序库和框架时非常重要 因为其中的类将会在他人的代码中使用——你是无法直接访问和修改这些代码的。里氏替换原则的重点在不影响原功能。根据接口隔离原则你必须将“臃肿”的方法拆分为多个颗粒度更小的具体方法。客户端必须仅实现其实际需要的方法。否则对于“臃肿”接口的修改可能会导致程序出错即使客户端根本没有使用修改后的方法。通常在设计软件时你可以辨别出不同层次的类。• 低层次的类实现基础操作例如磁盘操作、传输网络数据和连接数据库等。• 高层次类包含复杂业务逻辑以指导低层次类执行特定操作。经典设计模式这里列举了22种设计模式大致分为三类创建型模式结构型模式行为模式创建型模式提供创建对象的机制增加已有代码的灵活性和可复用性结构型模式介绍如何将对象和类组装成较大的结构并同时保持结构的灵活和高效:行为模式负责对象间的高效沟通和职责委派:推荐一个经典学习网站https://refactoring.guru上面每种模式配有形象图比如工厂方法模式 还提供对应的设计类图也提供了对应代码示例支持9种语言的实现 代码在https://github.com/RefactoringGuru推荐给大家拿走别谢更多请参考《设计模式可复用面向对象软件的基础》 https://refactoring.guruLinux经典设计模式内核面向对象设计模式Linux虽然是面向过程的c语言写成的但是却可以表达面向对象的思想Linux内核大量使用面向对象的编码风格我们可以从中至少学习到两点说明在大型软件开发中OOP编程思想很重要和具体语言无关同时展示了怎么用c语言实现OOP编程值得广大C语言开发者学习。我们用例子来说明。封装以内核proto定义为例struct proto 定义传输层接口方法和相应成员数据类似C的class定义可以根据这个class生产很多实例比如TCP实例可以通过统一接口访问TCP实例的方法和数据。继承以内核套接字体系为例基于此继承体系对于一些接受 struct sock* 形参的接口就可以直接把上述的子类套接字实例 struct udp_sock* sk作为实参传进去当然这里需要指针强转一次(struct sock*)sk。这里就是OOP中“is a的public继承关系子类对象可以直接作为父类对象使用并且这种实现只支持单继承。多态用C实现多态需要自己维护继承关系中的虚函数体系C有编译器自动生成、维护vtbl与vptr。Linux内核的实现中将系列函数指针放入结构体即视其为“虚函数”亦或是专门定义一个xxx_ops结构里面放上一堆函数指针作为“虚函数表”。仍以套接字体系为例在基类 sock 中有协议结构体指针 struct proto *skc_prot; 这个proto即可大体上视为一个虚函数表vtbl内有具体协议的函数指针而这个skc_prot指针即可视为虚指针vptr。在套接字创建时根据参数中的协议族、协议类型、协议号信息调用协议族的create函数执行创建绑定具体协议proto指针到该vptr上自此实现了静态类型到动态类型的绑定。之后当调用虚函数时即可直接通过这些函数指针进行多态的调用 , 比如下面例子socket调用connect接口这里第一个参数sk即可看做this指针不同socket对象会访问对应协议接口,从而实现多态访问list 设计模式list作为常用数据结构写代码时候经常会遇到可以看一下传统list设计和内核list设计有什么不一样。一般的双向链表一般是如下的结构:有个单独的头结点(head)每个节点(node)除了包含必要的数据之外还有2个指针(pre,next)pre指针指向前一个节点(node)next指针指向后一个节点(node)头结点(head)的pre指针指向链表的最后一个节点最后一个节点的next指针指向头结点(head)传统list如下图传统的链表不同node类型需要重新定义结构不够通用化还需要为node实现脱链、入链操作等。我们需要抽象出一个“基类”来实现链表的功能其他数据结构只需要简单的继承这个链表类就可以了。内核list设计如下链表不是将用户数据保存在链表节点中而是将链表节点保存在用户数据中链表节点只有2个指针(prev和next)prev指针指向前一个节点的链表节点next指针指向后一个节点(node)的链表节点如下图这样设计的好处是链表的节点将独立于用户数据之外便于把链表的操作独立出来和具体数据节点无关这里可能有些人会问数据节点怎么访问呢 内核通过一个container_of的宏从链表节点找到数据节点起始地址找到数据节点起始地址后通过数据节点定义就可以访问数据了内核红黑树rbtree也是同样的设计。设备驱动框架设计模式 从Linux2.6开始Linux加入了一套驱动管理和注册机制—platform平台总线驱动模型:当调用platform_device_register或platform_driver_register注册platform_device或platform_driver时首先会将其加入platform总线上依次匹配platform总线上的platform_driver或platform_device然后调用platform_driver的.probe函数。其中platform_device存放设备资源硬件息息相关代码易变动platform_driver则使用资源比较稳定的代码这样当改动硬件资源时我们的上层使用资源的代码部分几乎可以不用去改动。这里设计通过中间bus层把强耦合Device和对应Driver进行了解耦隔离定好matchprobe等标准通信接口就可以独立开发通过总线bus进行关联通信有点类似中介模式。C Idioms设计习语由于篇幅优先这里列举一些非常重要且非常实用的C专有的设计模式。RAII-Resource Acquisition Is Initialization‘资源获取即初始化‘简称 RAII是C防止内存泄露一个很好解决方案它结合构造函数和析构函数把资源生命周期和对象生命周期绑定起来在构造函数中获取资源这些错误会引发异常然后将其释放到析构函数中永不抛出并且不需要显式清理从而防止忘记释放资源 C STL库很多类遵循RAII设计原则比如std :: stringstd :: vectorstd :: thread等。Policy-based class Design基于策略设计又名policy-based class design 是一种基于C计算机程序设计模式以策略Policy为基础并结合C的模板元编程。就是将原本复杂的系统拆解成多个独立运作的“策略类别”每一组policy class都只负责单纯如行为或结构的某一方面。多重继承由于继承自多组 Base Class故缺乏型别消息而Templetes基于型别拥有丰富的型别消息。多重继承容易扩张而Templetes的特化不容易扩张。Policy-Based Class Design 同时使用了 Template 以及 Multiple Inheritance 两项技术结合两者的优点看下面例子ResourceManager则称为宿主类别host class只需要切换不同 Policy ClassReadPolicy or WritePolicy就可以得到不同的功能实体。Policy不一定要被宿主继承只需要用委托完成这一工作。但policies必须遵守一个隐含的constraint接口必须一样故参数不能有巨大改变policy 的一个重要的特征是宿主类别经常并不一定要使用多重继承的机制去使用多个 policy classes. 因此在进行 policy 拆解时必须要尽可能达成正交分解policy之间最好彼此独立运作不相互影响。Pimpl - Pointer to implementationPimpl是一种广泛使用的削减编译依赖项的技术, 看下面例子可能就明白了 因为Widget的成员变量有std::stringstd::vector和Gadget那么这些类型的头文件在Widget编译时必须出现这意味Widget的用户必须包含“gadget.h”。这些增加的头文件会增加Widget用户的编译时间而且这使得用户依赖于这些头文件即如果某个头文件的内容被改变了Widget的用户就要重新编译。标准库头文件不会经常改变但是“gadget.h”可能会经常修改。所以需要Pimp技术来消除这种变化影响--隔离变化 这样Widget头文件里面就不需要包含“gadget.h”文件了再CPP文件中再声明具体的类型 在这里我展示了“#include”指令只为了说明所有对头文件的依赖即std::stringstd::vector和Gadget依然存在。不过呢依赖已经从“widget.h”Widget用户可见的和使用的转移到“widget.cpp”(只有Widget的实现者才能看见和使用)这样就把widget头文件变化影响隔离在内部实现中对外接口不变这里就体会到这种设计模式的好处。CRTP -The curiously recurring template pattern CRTP (奇异递归模板模式是一种在编译期实现多态方法是对运行时多态一种优化多态是个很好的特性但是动态绑定比较慢因为要查虚函数表。而使用 CRTP完全消除了动态绑定降低了继承带来的虚函数表查询开销。CRTP包含从模板类继承使用派生类本身作为基类的模板参数。 这样做的目的是在基类中使用派生类。从基础对象的角度来看派生对象本身就是对象但是是向下转换的对象。因此基类可以通过将static_cast自身放入派生类来访问派生类。总结为什么要掌握设计模式软件危机带来刚性要求设计模式提倡的高内聚低耦合代码复用可扩展性等思想可以给我们软件设计带来一些思考有了思考就会产生一些积极变化理解设计模式前提是要理解背后的设计原则这是整个设计模式的精华经典的设计模式包含22种设计模式没有解释器模式日常开发中很少使用大致分为三类创建型模式结构型模式行为模式Linux系统里面包含大量设计模式思想面向对象设计List/Rbtree抽象设计驱动框架bus总线解耦设计都值得我们学习每种编程语言都会有一些独特特殊习惯用法Java的MVCGolang的对象池模式(Object Pool)等文中列举的C一些常见的惯用法RAIIPolicy-based Design PimplCRTP等对C开发来说了解和掌握他们对于特定场景问题多了一些好的解决方案设计模式是银弹吗不是就像软件工程也不是银弹一样这些都只是工具关键还是看是否真正理解其背后反射出的设计精髓我们需要多一些批判性的思考没有绝对好坏软件设计的最终方案很多时候都是权衡trade-off结果但我们的长期目标始终没有变化。更多精彩推荐
☞三个月前被 K8S 弃用Docker 火了获 2300 万美元融资☞一招上手这样设计扛住亿级流量活动系统☞Kubernetes 稳定性保障手册极简版点分享点收藏点点赞点在看