架构-设计原则

news2024/11/25 6:32:26

1、面向对象的SOLID

1.1 概述

        SOLID是5个设计原则开头字母的缩写,其本身就有“稳定的”的意思,寓意是“遵从SOLID原则可以建立稳定、灵活、健壮的系统”。5个原则分别如下:

  • Single Responsibility Principle(SRP):单一职责原则。
    • 一个类,只做一件事,并把这件事做好,其只有一个引起它变化的原因。
  • Open Close Principle(OCP):开闭原则。
  • Liskov Substitution Principle(LSP):里氏替换原则。
  • Interface Segregation Principle(ISP):接口隔离原则。
  • Dependency Inversion Principle(DIP):依赖倒置原则。

        SOLID最早由Robert C. Martin在2000年的论文“Design Principles and Design Patterns”中引入。在2004年前后,Michael Feathers提醒Martin可以调整一下这些原则的顺序,那么它们的首字母的缩写就可以排列成SOLID。这个新名字的确促进了SOLID思想的传播,再一次证明了命名
的重要性。

1.2 关系

        SOLID原则之间并不是相互孤立的,彼此间存在着一定关联,一个原则可以是另一个原则的加强或基础;违反其中的某一个原则,可能同时违反了其他原则。

  • 设计目标:开闭原则和里氏代换原则。
  • 设计方法:单一职责原则、接口分隔原则和依赖倒置原则

1.3 职责单一原则(SRP-Single Responsibility Principle)

        任何一个软件模块中,应该有且只有一个被修改的原因。

        SRP要求每个软件模块职责要单一,衡量标准是模块是否只有一个被修改的原因。职责越单一,被修改的原因就越少,模块的内聚性(Cohesion)就越高,被复用的可能性就越大,也更容易被理解。

示例

非SRP

        例如,有一个Rectangle类(如图1-1所示),该类包含两个方法,一个方法用于把矩形绘制在屏幕上,另一个方法用于计算矩形的面积。

                                                                1-1

        按照SRP的定义,Rectangle类是违反了SRP原则的。因为Rectangle类具有至少两个职责,不管是改变绘制逻辑,还是面积计算逻辑,都要改动Rectangle类。

SRP-贫血

        为了遵从SRP原则,我们需要把两个职责分离出来,放在两个不同的类中,这样就可以互相不影响了。最简单的解决方案是将数据与函数分离,如图1-2所示。设计两个用来做逻辑处理的类,每个类只包含与之相关的函数代码,互相不可见,这样就不存在互相依赖的情况了。

                                                                 1-2

SRP-充血

        1-2的方式有点“贫血”模式的味道。我们也可以采用面向对象的做法,把重要的业务逻辑与数据放在一起,然后用Rectangle类来调用其他没那么重要的函数,如图1-3所示。

                                                               1-3 

        另外,SRP不仅在模块和类级别适用,在函数级别同样适用。

函数单一职责

        下面是一个给员工发工资的简单方法

public void pay(List<Employee> employees){
    for (Employee e: employees){
        if(e.isPayDay()){
            Money pay = e.calculatePay();
            e.deliverPay(pay);
        }
    }
}

做了3件事情

  • 遍历所有雇员
  • 检查是否该发工资
  • 支付薪水。

按照SRP的原则,以下面的方式改写更好

//遍历所有雇员
public void pay(List<Employee> employees) {
    for (Employee e : employees) {
        payIfNecessary(e);
    }
}

//检查是否该发工资
private void payIfNecessary(Employee e) {
    if (e.isPayDay()) {
        calculateAndDeliverPay(e);
    }
}

// 支付薪水
private void calculateAndDeliverPay(Employee e) {
    Money pay = e.calculatePay();
    e.deliverPay(pay);
}

        虽然原来的方法并不复杂,但按照SRP分解后的代码显然更加容易让人读懂,这种拆分是有积极意义的。基本上,遵循SRP的函数都不会太长,再配上合理的命名,就不难得到我们想要的短小的函数。

1.4 开闭原则(OCP-Open Close Principle)

        其核心的思想是:模块是可扩展的,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。

  • 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
  • 对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。这样可以保证稳定性和延续性。

        OCP 建议我们应该对系统进行重构,那么以后再进行同样改动,只需添加新代码而不必改动已正常运行的代码。

        在很多方面,OCP 都是面向对象设计的核心所在,可增强灵活性、可重用性、可维护性等。

        OCP 的关键是抽象,其背后的主要机制是抽象和多态。模块应该依赖于一个固定的抽象体,因此,它对于更改可以是关闭的,同时,通过从这个抽象体派生,也可以扩展此模块的行为。

        实际上,很多的设计模式都以达到OCP目标为目的。例如,装饰者模式,可以在不改变被装饰对象的情况下,通过包装(Wrap)一个新类来扩展功能;策略模式,通过制定一个策略接口,让不同的策略实现成为可能;适配器模式,在不改变原有类的基础上,让其适配(Adapt)新的功能;观察者模式,可以灵活地添加或删除观察者(Listener)来扩展系统的功能。

注意
        当然,要想做到绝对地“不修改”是比较理想主义的。因为业务是不确定的,没有谁可以预测到所有的扩展点,因此这里需要一定的权衡,如果提前做过多的“大设计”,可能会犯YAGNI(You Ain’t Gonna NeedIt)的错误。

1.5 里氏替换原则(LSP-Liskov Substitution Principle)

        软件工程大师罗伯特·马丁(Robert C. Martin)把里氏代换原则最终简化为一句话:“Subtypes must be substitutable for their base types”。也就是,子类必须能够替换成它们的基类。即子类应该可以替换任何基类能够出现的地方,并且经过替换以后,代码还能正常工作

        另外,不应该在代码中出现 if/else 之类对子类类型进行判断的条件。里氏替换原则 LSP 是使代码符合开闭原则的一个重要保证。正是由于子类型的可替换性才使得父类型的模块在无需修改的情况下就可以扩展。

注意:
一般而言,无论模块是多么的“封闭“,都会存在一些无法对之封闭的变化,没有对于所有的情况都贴切的模型。所以,必须有策略地对待这个问题。
设计人员必须对他所设计的模块应该对哪种变化封闭做出选择,必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离那些变化。但大多数情况,猜测都是错误的。后续即使不使用这些抽象也必须去支持和维护它们,这不是一件好事,所以,通常我们会一直等到确实需要那些抽象时再去进行抽象

1.6 接口隔离原则(ISP-Interface Segregation Principle ) 

        不能强迫用户去依赖那些他们不使用的接口。换句话说就是使用多个专门的接口比使用单一的总接口要好。

        举个例子,我们对电脑有不同的使用方式,比如:写作、通讯、看电影、打游戏、上网、编程、计算和数据存储等。如果我们把这些功能都声明在电脑的抽象类里面,那么,我们的上网本、PC 机、服务器和笔记本的实现类都要实现所有的这些接口,这就显得太复杂了。所以,我们可以把这些功能接口隔离开来,如工作学习接口、编程开发接口、上网娱乐接口、计算和数据服务接口,这样,我们的不同功能的电脑就可以有所选择地继承这些接口。

        同时,小接口更容易实现,提升了灵活性和重用的可能性。由于很少的类共享这些接口,相应接口的变化而需要变化的类数量就会降低,增加了鲁棒性。

1.7 依赖倒置原则(DIS-Dependency Inversion Principle)

        高层模块不应该依赖于底层模块(高层与低层是相对而言,也就是调用者与被调用者的关系),二者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

        举个例子:墙面的开关不应该依赖于电灯的开关实现,而是应该依赖于一个抽象的开关的标准接口

        这样,当我们扩展程序的时候,开关同样可以控制其它不同的灯,甚至不同的电器。也就是说,电灯和其它电器继承并实现我们的标准开关接口,而开关厂商就可以不需要关于其要控制什么样的设备,只需要关心那个标准的开关标准。这就是依赖倒置原则。

2、其他

2.1 DRY 原则(Don’t Repeat Yourself)

        DRY原则可理解为不要写重复的代码。简单来讲,写代码的时候,如果出现雷同片段,就要想办法把他们提取出来,成为一段独立的代码。

        DRY 是一个最简单的法则,也是最容易被理解的,但它也可能是最难被应用的(因为要做到这样,我们需要在泛型设计上做相当的努力,这并不是一件容易的事)。它意味着,当在两个或多个地方发现一些相似代码的时候,我们需要把它们的共性抽象出来形成一个唯一的新方法,并且改变现有地方的代码让它们以一些合适的参数调用这个新的方法。

        代码重复有三种典型情况

  • 实现逻辑重复
    • 重复的代码被敲了两遍或者简单复制粘贴一下代码。
  • 功能语义重复
    • 功能重复。代码可能不同,但是实现的功能是相同的。
      • 例如:两个同事写的同一个工具方法。
  • 代码执行重复。
    • 例如,多个地方对同样的参数做参数校验。

2.2 YAGNI原则(You Ain’t Gonna Need It)

        你是否有个这样的经历,臆想某个功能以后可能会用到,然后就顺手把它实现了,实际到了后面并没用上,反而造成了代码冗余。

        这个原则只考虑和设计必须的功能,避免过度设计。只实现目前需要的功能,在以后你需要更多功能时,可以再进行添加。如无必要,勿增复杂性。软件开发是一场 取舍(trade-off)的博弈。

        因此,我们不能闭门臆想需要的功能,但是在架构上又要洞察趋势。

2.3 Rule of Three

        Rule  of  Three也被称为“三次原则”,是指当某个功能第三次出现时,就有必要进行“抽象化”了。这也是软件大师Martin  Fowler在《重构》一书中提出的思想。
        三次原则指导我们可以通过以下步骤来写代码。

  1. 第一次用到某个功能时,写一个特定的解决方法。
  2. 第二次又用到的时候,复制上一次的代码。
  3. 第三次出现的时候,才着手“抽象化”,写出通用的解决方法。

        这3个步骤是对DRY原则和YAGNI原则的折中,是代码冗余和开发成本的平衡点。同时也提醒我们反思,是否做了很多无用的超前设计、代码是否开始出现冗余、是否要重新设计。软件设计本身就是一个平衡的艺术,我们既反对过度设计(Over  Design),也绝对不赞成无设计(No Design)。

2.4 KISS 原则(Keep It Simple, Stupid)

        保持每件事情都尽可能的简单,用最简单的解决方案来解决问题。

        KISS 原则在设计上可能最被推崇,在家装设计、界面设计和操作设计上,复杂的东西越来越被众人所鄙视了,而简单的东西越来越被人所认可。

  • 宜家简约、高效的家居设计和生产思路;
  • 微软“所见即所得”的理念;
  • 谷歌简约、直接的商业风格,无一例外地遵循了“KISS”原则。
  • 而苹果公司的 iPhone 和 iPad 将这个原则实践到了极至。 也正是“KISS”原则,成就了这些看似神奇的商业经典。

2.5 好莱坞原则(Hollywood Principle)

        好莱坞原则就是一句话:“don’t call us, we’ll call you.”。意思是,好莱坞的经纪人不希望你去联系他们,而是他们会在需要的时候来联系你。也就是说,所有的组件都是被动的,所有的组件初始化和调用都由容器负责。

        简单来讲,就是由容器控制程序之间的关系,而非传统实现中,由程序代码直接操控。

        这也就是所谓“控制反转”的概念所在:

  1. 不创建对象,而是描述创建对象的方式。
  2. 在代码中,对象与服务没有直接联系,而是容器负责将这些联系在一起。控制权由应用代码中转到了外部容器,控制权的转移,是所谓反转。

        好莱坞原则就是IoC(Inversion of Control) 或DI(Dependency Injection)]的基础原则。

3、总结

  • 原则是指导我们写出更好的代码,但不要教条,任何东西都是适用场景的。
  • 原则不是目的,实现业务逻辑才是目的,不要本末倒置。
  • 原则是降低复杂度,不是增加复杂度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1097733.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++香甜的黄油(acwing)

农夫John发现了做出全威斯康辛州最甜的黄油的方法&#xff1a;糖。 把糖放在一片牧场上&#xff0c;他知道 N 只奶牛会过来舔它&#xff0c;这样就能做出能卖好价钱的超甜黄油。 当然&#xff0c;他将付出额外的费用在奶牛上。 农夫John很狡猾&#xff0c;就像以前的巴甫洛夫…

双十一购物指南:电视盒子哪个牌子好?口碑电视盒子品牌排行榜

双十一可以说是年度最低价&#xff0c;我们都会在此时买买买&#xff0c;电视盒子作为日常必备销量同样火爆&#xff0c;近来很多朋友咨询小编电视盒子哪个牌子好&#xff0c;因此我整理了用户评价最高的电视盒子品牌排行榜&#xff0c;看完就知道哪些电视盒子最值得买了。 推荐…

mysql优化(关联表查询效率低下问题)

场景:表A和表B关联 A.id B.a_id 查询,并用表A的字段create_time排序(查询条件附带between create_time) 造成结果:explain结果如下,(查询结果耗时长) 原因:尚不明确. 解决方案:给B的a_id字段加上索引(create index index_a_id on B(a_id)),就解决了一部分问题.查询速度提升上…

pg 时间操作方法

1.概念 本质就是个宏定义&#xff0c;只是动态的 2.常用使用形式和函数 current_date &#xff1a;当前时间 current_timestamp&#xff1a;时间戳&#xff0c;更细 date ‘当前日期’ &#xff1a;指定日期 age&#xff08;日期1&#xff0c;日期2&#xff09;&#xf…

OpenWrt如何公网ssh远程连接【内网穿透】

文章目录 1.安装cpolar内网穿透2.配置SSH远程访问隧道3.公网远程连接4.固定远程连接地址 简单几步实现openWRT结合cpolar内网穿透工具实现远程ssh连接 1.安装cpolar内网穿透 cpolar是一个非常棒的跨平台、内网穿透工具&#xff0c;可以通过安全隧道将NAT或防火墙后面的本地服…

“5G+北斗”赋能千行百业,中海达亮相2023中国移动全球合作伙伴大会

10月12日&#xff0c;2023中国移动全球合作伙伴大会在广州保利世贸博览馆召开。本次活动以“算启新程 智享未来”为主题&#xff0c;重点展示中国移动全球合作伙伴在5G、终端、数智城市等方面的新产品、新技术和新服务。作为中国移动“朋友圈”战略合作企业及“北斗时空子链”链…

Python实现简易过滤删除数字的方法

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 如果想从一个含有数字&#xff0c;汉字&#xff0c;字母的列表中滤除仅含有数字的字符&#xff0c; 当然可以采取正则表达式来完成&#xff0c;但是有点太麻烦了…

【前端】vue在Windows平台IIS的部署

系列文章 【C#】IIS平台下&#xff0c;WebAPI发布及异常处理 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/126539836 【Vue】vue2与WebApi跨域CORS问题 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/133808959 文章目…

了解容器运行时安全:保护你的容器应用

前言 容器是一种虚拟化技术&#xff0c;用于封装和运行应用程序及其依赖项&#xff0c;以便在不同的计算环境中保持一致性和可移植性。自2013年容器诞生至今&#xff0c;容器Docker镜像的下载量超20亿&#xff0c;虽然容器行业发展如火如荼&#xff0c;但是其安全风险却不容乐…

软件测试定位bug方法+定位案例(详解)

1、问题bug定位技巧 首先&#xff0c;作为开发也好&#xff0c;测试也好&#xff0c;定位问题有一个总的思路&#xff0c;而这个思路是和数据的走向一致的。 大致是这样&#xff1a; 用户层面问题 -> Web页面/软件界面 -> 中间件 -> 后端服务 -> 代码 -> 数据…

Mac电脑日历怎么看状农历和事件?小历TinyCal怎么看农历日期

怎么设置电脑日期显示农历&#xff1f;电脑中如何查看万年历&#xff0c;电脑中快速查看万年历的方法。小历TinyCal mac版&#xff0c;是一款菜单栏日历软件&#xff0c;软件简洁大方、使用简单、操作方便&#xff0c;显示农历、节日节气和法定节假日&#xff0c;支持日历事件的…

轻松学会这招,给大量视频批量添加滚动字幕不求人

想要给大量视频批量添加滚动字幕不求人吗&#xff1f;下面就教你一个简单的方法。首先你需要下载并安装一款名为“固乔剪辑助手”的软件&#xff0c;这是一款非常专业的视频剪辑软件&#xff0c;它可以帮助你快速地给大量视频添加滚动字幕。 打开固乔剪辑助手软件后&#xff0c…

4年前,当我进入这家公司,便深感管理无力

有形的东西&#xff0c;看得见摸得着&#xff0c;只要有一道有形的围墙&#xff0c;管理也乱不到哪里去&#xff0c;可是这个行业的核心&#xff0c;是以合同为纽带的信息流管理。以长租合同为例&#xff0c;履约期限长达3年&#xff0c;涉及合同签约、归档、应收登记、实收登记…

Goland Cannot use ‘err‘ (type error) as the type any

问题描述&#xff1a; 用Goland写代码的时候&#xff0c;使用panic总是报错&#xff0c;官方用法也是报错&#xff0c;最后找到官方回复的链接&#xff0c;https://youtrack.jetbrains.com/issue/GO-12179/Cannot-use-err-type-error-as-the-type-any 问题解决方式&#xff1…

idea禁用双击ctrl

Run anything | IntelliJ IDEA Documentation Disable double modifier key shortcuts

Adaptive Homogeneity-DirectedDemosaicing Algorithm

Abstract 经济高效的数码相机使用单图像传感器&#xff0c;将红色、绿色和蓝色滤色镜的交替图案应用到每个像素位置。通过估计每个颜色平面中缺失的像素分量来重建彩色图像的完整三色表示的方法称为去马赛克算法。本文提出了通常与结合二维 (2-D) 方向插值的去马赛克算法相关的…

香港专家联名呼吁港府聚焦港元稳定币

2023年以来&#xff0c;市场对于 RWA&#xff08;Real World Assets&#xff09;即真实世界资产“代币化”的讨论愈发频繁&#xff0c;一些观点认为 RWA将在下一轮加密资产牛市中成为焦点&#xff0c;部分Web3创业者和传统金融企业也快速将业务方向瞄准相关赛道&#xff0c;而被…

平台系统老板驾驶舱的重要性,我选云表

平台系统老板驾驶舱的重要性在于它是一个集成的管理和分析工具&#xff0c;能够提供对平台系统运行情况的全面和实时的监控、分析和管理功能。以下是平台系统老板驾驶舱的重要性&#xff1a; 老板驾驶舱 该表单可供老板实时把控企业运营情况&#xff0c;包括销售业绩、…

博途PLC浮点数拆分为高低16位字(AT覆盖指令应用)

博途PLC对双整型数据进行高低16位拆分,还可以参考下面文章方法: 博途双字高低位转换32位双字拆分/合并操作(博途SCL源代码)_RXXW_Dor的博客-CSDN博客博途PLC的位、字节拆分和合并操作还可以参考下面的文章链接:博途PLC 位/字/字节 Bit/ Word/Byte拆分与合并_博途的bit-CSDN…

小黑第一次参加主持活动,没有出错被得到了鼓励,周日完赛人生中第一次山道马拉松的leetcode之旅:167. 两数之和 II - 输入有序数组

小黑代码 class Solution:def twoSum(self, numbers: List[int], target: int) -> List[int]:# 数组长度n len(numbers)# 定义双指针head 0tail n - 1# 开始双指针操作while head < tail:if numbers[head] numbers[tail] < target:head 1elif numbers[head] nu…