面向对象设计原则与设计模式 - 如何写出更优雅的代码


前段时间有幸参加了麦思博优雅代码养成指南的培训,故借此记录总结一番,希望能有助于他人。
一个软件、一段代码从合格到优雅或者说优秀,可以归纳为逐级满足以下特质,学术名叫ISO/IEC 软件质量特质:

  • 功能:软件符合需求所必需的条件
  • 可靠性:满足某种指定级别稳定性能的能力
  • 易用性:易理解、使用并吸引客户的能力
  • 性能:表示软件在响应时间和资源占用方面的性能
  • 可维护性:表示软件支持修改的能力
  • 可移植性:表示软件从一个平台迁移到另一个平台的能力

我们通俗一点就是说首先是把需求描述的功能实现了,并且能用;接着得能在基本环境下稳定运行,不要一上生产就崩了;然后是易使用易理解,从系统实现上为用户提供更好的操作方式和操作体验,再然后就是性能、可维护(毕竟唯一不变的就是需求一直会变,所以你的代码要可维护、可扩展)、可移植(作为Java开发者而言,这个特质已经自带了)。对于以上这些特质,读者可以思考一下关于优雅代码,我们关注哪个特质?——自然是可维护性了
可维护性代码的核心原则:
在这里插入图片描述
高内聚、低耦合,同样的内容内聚到同个模块,不同模块间低耦合互不影响。当然说起来简单,不然人人都是架构师了,具体怎么实现呢?如何实现我们“高大上”的高内聚低耦合呢?——分离关注点原则
那怎么个分离关注点呢?我们举个栗子:大家应该都写过或者接触过web前端代码吧,就是HTML、JS、CSS,大家有没有想过为什么是这三个?看下它们分别负责什么,HTML是整个网页的结构,JS是事件、动作,CSS是修饰。有没有发现刚好从三个视角把一个网页拆成了三部分,每个部分互相独立,内部又高度集中、可任意扩展。这就是分离关注点,从而实现高内聚、低耦合。
基于分离关注点这个原则,大神们帮我梳理了一些更为具体的心法,下面我们来一一介绍。
面向对象设计 5大心法

  • SRP – The Single Responsibility Principle (单一职责原则)
  • OCP – The Open Closed Principle (开放封闭原则)
  • LSP – The Liskov Substitution Principle (Liskov替换原则)
  • ISP – The Interface Segregation Principle (接口隔离原则)
  • DIP – The Dependency Inversion Principle (依赖倒置原则)
    在这里插入图片描述

单一职责原则

通俗的来讲就是让你每个模块只做一件事,只负责一个职责。相信刚开始大家编写代码或者因为赶项目,把很多功能都写在同一个类或者同一个模块里,这就造就了我们说的“上帝类”,一个知道很多、做很多、依赖很多的类。如果不去动它,可以一直很好的运行,但是一旦需要修改,看到这里想必大家已经回想起去接手那些写得又长、逻辑又复杂、代码跳来跳去的代码了吧,这时候“上帝”就变成了“魔鬼”了。
要求别人容易,自己写代码的时候如何做到单一职责呢?
可以将类进行提炼,一个类不应该做两个类做的事
例如:
在这里插入图片描述
一个数据删除操作的类里面包含了一个权限判断,这样就不符合单一职责原则了,并且如果我增加一个数据查询操作的类,也需要判断权限,这样又得重写一遍权限判断的代码。(有人会说我复制粘贴一下就好了,很快的。那是不是每增加一个操作就要复制粘贴。记住复制粘贴是很好的文本操作,但是很坏的编码操作,一旦出现复制粘贴就应该考虑代码优化了)。
这里插播介绍一下代码坏味道
典型的代码坏味道

  • 重复的代码
    重复的代码是多数潜在BUG的温床!
  • 过长的函数
    当需要以注释来说明什么的时候,
    就把要说明的东西写进独立的函数。
  • 过大的类
    一个类如果拥有太多的代码,
    也是代码重复、混乱、绝佳的滋生地点。
  • ……
    详细可以翻这本书
    在这里插入图片描述
    那如何避免代码坏味道呢?——从名字开始,让名字承载更多信息
    相信大家都会写注释,写注释是个很好的习惯,也有人说写注释麻烦,那有没有更好的方法呢?——有,好的命名比注释更易于理解,让你的代码自己告诉别人它是干嘛的。
    好名字还能有助于发现缺陷,有没有读者发现下面这个代码有什么问题?
    在这里插入图片描述
    循环变量用错了,所以我们应该避免使用这样范范的名字。
    在这里插入图片描述
    更好的命名可以及时帮助我们发现问题。
    在这里插入图片描述
    类似的给名字加上单位,如Redis方法参数的过期时间,是秒还是毫秒;
    命名最好避免使用反义词,如
    bool notExit = false (到底存不存在)
    我并非不是一个不傻的人

除了上面的提炼类,让独立的功能成为独立的模块,我们还可以用代理模式(代理模式就是“真实对象”的代表,在访问对象时引入一定程度的间接性,而这种间接性可以附加功能)
在这里插入图片描述
在这里插入图片描述
代理其实引入了另外一种思想,我们不仅可以使用分离,还可以变成环绕,一旦删除后需要做一些事情,比如发送通知,可以直接在我们的代理里加。
至于用哪种方式就见仁见智了。(这里提醒一下偏向使用分离的读者,不同级别的分离才有意义,不要过度抽象。比如检验权限的时候校验角色和校验岗位是同级别的事情,就不需要分离成两个方法了。)

开放封闭原则

对扩展开放,对修改关闭。相信很多程序猿都曾经历修改现有功能、现有代码的痛苦经历,如何不修改源代码而改变它的功能呢?——抽象和多态
举个栗子,一个累计图形面积的需求,一开始是长方形
伪代码:
在这里插入图片描述
后来加了个圆形,我们抽象一下,并且使用多态,让长方形和圆形共同实现一个图形接口,计算代码变成
伪代码:
在这里插入图片描述
还是有问题,如果再加上三角形呢?可怕的多层if/else地狱出现了。我们可以把计算逻辑变成接口shape的一个方法,让子类各自实现,将面积作为返回值,计算的时候直接调用该方法累加返回的值就可以了。这样就实现了我们的对扩展开放,对修改关闭。实现代码就由读者自行完成了,其实这就是策略模式,每个图形有自己的面积计算方式,使用接口提供抽象,将具体实现细节埋藏在实现类中。
在这里插入图片描述

Liskov替换原则

支持抽象和多态的关键机制:继承。怎样才算好的继承关系呢?这里就要介绍一下Liskov替换原则了。Liskov替换原则通俗描述就是所有引用父类的地方必须能透明地使用其子类。
在这里插入图片描述
BigCar 不能替换Car,违反了Liskov替换原则
当你使用多态,用子类动态替换,发现需要强制转型才能使用时,你的代码就不再通用,每增加一个子类就得增加一个判断(违反了对修改关闭),也就是违反了Liskov替换原则。
插播优雅代码简单技巧。
(1)优化条件判断与循环
在这里插入图片描述
这里会出什么问题呢?假如字符串 str 是前端传过来的。对的,如果str为null,这里就会报空指针异常。那如何处理呢?有人会说加个null判断就好了
在这里插入图片描述
没错,这样确实可行,但有没有更优的写法?
在这里插入图片描述
换一下位置就好了——犹达表达式
在这里插入图片描述
在循环中提前返回——卫语句
在这里插入图片描述
换成这样是不是明了得多
在这里插入图片描述

接口隔离原则

不知道读者有没有经历实现了一个接口,必须实现它的一个方法,然后这个方法对于你来说又完全没有意义,只能来个空实现。当然自己写代码一般不会,但是接手别人的代码呢?这时候就违反了我们的接口隔离原则,那我们可以如何解决呢?这里介绍一个适配器模式。本想自己码字,但案例篇幅较长,网上找到一篇挺好的,请戳https://www.runoob.com/design-pattern/adapter-pattern.html

依赖倒置原则

高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。请戳https://blog.csdn.net/zhengzhb/article/details/7289269

一些觉得值得记录的小笔记:
正向逻辑很复杂的时候,试着从“反方向”解决问题。(程序员天然会从怎么实现的角度来考虑问题,有时候从怎么使用的角度来思考,反向思考)

复杂度守恒定律:复杂度由业务决定,这边复杂度降低,那边复杂度就要上升,比如依赖倒置。选择合适的代码优化方式,把代码放在它应该在的位置。

复杂对象的创建(如构造函数复杂,有不同的参数),将构造函数设为私有,使用不同的静态方法去生成对象,防止出错。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值