聊聊软件设计原则

待兔
• 阅读 1355

软件设计知识是一名软件开发人员必须要懂的知识,最近几天今天看了bob大叔的《敏捷软件开发》一书和软件设计相关的一些blog和资料,自己做了一个学习笔记

设计目标

正确性、健壮性、灵活性、可重用性、高效性

降低复杂性

  • 所谓复杂性,就是任何使得软件难于理解和修改的因素。

  • 复杂性的来源主要有两个:代码的含义模糊和互相依赖

    • 模糊指的是,代码里的重要信息看不出来;
    • 依赖指的是,某个模块的代码,不结合其他模块的代码,就无法理解
  • 危害:复杂性的危害在于,它会递增。如果做错了一个决定,导致后面的代码都基于前面的错误实现,只会越来越复杂。"常听人说,我们先把产品做出来,后面再改进",这很难做到

  • ==关键==:找出易变化的部分,合理抽象。
    用抽象构建框架、用实现扩展细节

代码抽象三原则:

1. Don’t Repeat Yourself (DRY)

  • 系统的每一个功能都应该有唯一的实现。也就是说,如果多次遇到同样的问题,就应该抽象出一个共同的解决方法,不要重复开发同样的功能
    不要重复自己

2. You Ain’t Gonna Need It (YAGNI)

  • 定义:你不会需要它,只考虑和设计必须的功能,避免过度设计
  • 这是"极限编程"提倡的原则,指的是你自以为有用的功能,实际上都是用不到的。因此,除了最核心的功能,其他功能一概不要部署,这样可以大大加快开发
  • 你会发现DRY原则和YAGNI原则并非完全兼容。前者追求"抽象化",要求找到通用的解决方法;后者追求"快和省"

3.Rule Of Three

  • 称为"三次原则",指的是当某个功能第三次出现时,才进行"抽象化"。

  • 这样做有几个理由:

    • 省事。如果一种功能只有一到两个地方会用到,就不需要在"抽象化"上面耗费时间了。

    • 容易发现模式。"抽象化"需要找到问题的模式,问题出现的场合越多,就越容易看出模式,从而可以更准确地"抽象化"。

    • 防止过度冗余。如果一种功能同时有多个实现,管理起来非常麻烦,修改的时候需要修改多处。在实际工作中,重复实现最多可以容忍出现一次,再多就无法接受了。

  • 小结:综上所述,"三次原则"是DRY原则和YAGNI原则的折衷,是代码冗余和开发成本的平衡点,值得我们在"抽象化"时遵循

面向对象的S.O.L.I.D原则

1. SRP职责单一原则

  • 核心思想是一个类只做一件事,把事情做好,其只有一个引起它变化的原因,职责过多,引起它变化的原因就越多,将导致责任依赖增加耦合性
  • 遵循单一职责原的优点有:
    • 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
    • 提高类的可读性,提高系统的可维护性;
    • 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
  • 应用场景:迭代器

2. 里氏替换原则

  • 定义:所有引用基类的地方必须能透明地使用其子类的对象,替换之后,代码还能正常工作。它是使代码符合开闭原则的重要保证.
  • 问题:有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障
  • 解决:当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法
  • 换个说法是,子类可以扩展父类的功能,但不能改变父类原有的功能(不能破坏继承体系)
    1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
    2. 子类中可以增加自己特有的方法
    3. 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
    4. 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格

3. 接口隔离原则

  • 定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
  • 规约:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少,但不要过度。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。接口是设计时对外部设定的“契约”,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
  • 示例:人们对电脑有不同的使用方式:如上网、看电影、写文档、玩游戏、通讯、计算和存储等.如果都把这些功能都定义在电脑的抽象类里面。那各种功能类型的电脑(如上网本、服务器、PC、智能学习机)都要现实这些接口。所以,应该把这些接口隔离开,这样不同功能的电脑只需要实现自己需要的接口.

接口隔离2.jpg

接口隔离1.jpg

4. 依赖倒置原则

  • 定义:高层模块不应该依赖底层模块的实现,而是依赖高层抽象。而且,二者都应该依赖于抽象。抽象不应该依赖细节;细节应该依赖抽象
  • 问题:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险
  • 解决:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率
  • 核心思想:面向接口编程,在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
  • 好处:可以降低类之间的耦合性,提高系统的稳定性,降低修改程序造成的风险。
  • 应用模式:工厂模式
  • 规范:实际编程中,最好做到如下3点:
    1. 低层模块尽量都要有抽象类或接口,或者两者都有。
    2. 变量的声明类型尽量是抽象类或接口。
    3. 使用继承时遵循里氏替换原则

如果依赖的是一个稳定的具体类,那么可以直接依赖它

  • 层次化
    • 方式1:高层模块依赖底层实现模块,这种依赖性是传递的
    依赖倒置1.png
  • 方式2
    - 解除传递依赖关系
    - 接口所有权倒置:客户拥有抽象接口,它的服务者从这些抽象接口中派生,底层模块实现了在高层模块声明并被高层模块调用的接口
依赖倒置2.png

5. 开放/封闭原则

  • 对扩展开放,对修改关闭。如果有新的需求和变化可以对现有代码进行扩展,以适应新的情况.而不是对原有代码进行修改.
  • 解决:关键在于抽象、预测和刺激变化
  • 应用模式:装饰者模式(如java.io包),不改变原有代码扩展对象的行为

其他原则

1. Keep it sample,stupid(Kiss)

  • 保持简单、直接,不要复杂化
  • 模块分成接口和实现。接口要简单,实现可以复杂。
  • 好的 class 应该是"小接口,大功能",大量的功能隐藏在简单接口之下,对用户不可见,用户感觉不到这是一个复杂的 class.比如Unix 的文件读写接口

2. Program to an interface, not an implementation

  • 面向接口编程,而不是实现
  • 工厂模式、策略模式等等

3. 高内聚低耦合

  • 将模块间的耦合降到最低,努力让一个模块做到精益求精.内聚意味着独立和重用,耦合意味着多米诺骨牌效应.

4. 迪米特法则

又称最少知识原则,该原则告诉我们要降低耦合。

  • 定义:一个对象应该对其他对象保持最少的了解

  • 问题:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。

  • 解决:尽量降低类与类之间的耦合

  • 对于对象 ‘O’ 中一个方法’M’,M 应该只能够访问以下对象中的方法:

    • 对象O本身
    • 参数对象
    • 与对象O直接相关的对象
    • 方法中创建或实例化的对象
  • 应用模式:外观模式,提供一个统一的接口来访问子系统中的一群接口。外观定义了一个高层接口,让系统更容易使用

5. 好莱坞原则

  • 你不要找我,我会找你 。 高层组件对待底层组件的方式:别调用我们,我们会调用你
  • 应用场景:
    • 观察者模式,以通知替代轮询
    • 工厂模式
    • Ioc依赖注入,DI控制反转设计的基础,所有组件都是被动的,初始化和调用都由容器负责
    • 模板模式

6. 无环依赖原则

  • 包、服务之间的依赖结构必须是一个直接的无环图形,不能出现循环依赖
  • 打破循环依赖关系,解决关系耦合问题:
    • 使用依赖倒置原则和接口隔离原则
    • 创建新的包,将共同类抽象出来放在新的包里

7. 减少抛异常

  • 除了那些必须告诉用户的错误,其他错误尽量在软件内部处理掉,不要抛出

总结

  • 如何去遵守这些原则。对这些原则的遵守并不是是和否的问题,而是多和少的问题,也就是说,我们一般不会说有没有遵守,而是说遵守程度的多少。任何事都是过犹不及,设计模式的六个设计原则也是一样,制定这六个原则的目的并不是要我们刻板的遵守他们,而需要根据实际情况灵活运用。对他们的遵守程度只要在一个合理的范围内,就算是良好的设计。
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这