面向对象的SOLID例子学习
这些年,月小升同学发现自己不会读书于是买了一本《如何阅读一本书》,发现自己不会做笔记就买了一本《如何做笔记》,写代码久了,发现自己一直在用的面向对象不是很了解,经常把代码写成一坨一坨的,于是回头来学习怎么面向对象。那些不熟练的基础,总要还债的。
出来混总是要还的
SOLID 是Michael Feathers推荐的便于记忆的首字母简写,它代表了Robert Martin命名的最重要的五个面对对象编码设计原则
S: 单一职责原则 (SRP) Single Responsibility Principle
O: 开闭原则 (OCP) Open/Closed Principle
L: 里氏替换原则 (LSP) Liskov Substitution Principle
I: 接口隔离原则 (ISP) Interface Segregation Principle
D: 依赖反转原则 (DIP) Dependency Inversion Principle
1. 单一责任原则:
当需要修改某个类的时候原因有且只有一个(THERE SHOULD NEVER BE MORE THAN ONE REASON FOR A CLASS TO CHANGE)。换句话说就是让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。
举例子:不要让一个类,负责发邮件,还负责修改客户名称
不良代码:
class Email{public function sendemail(){//...}public function changeUsername(){//...} }
改进版:
class Email{public function sendemail(){//...} }class User{public function changeUsername(){//...} }
2. 开放封闭原则 Open/Closed Principle (OCP)
软件实体应该是可扩展,而不可修改的。也就是说,对”扩展是开放的,而对修改是封闭的”。这个原则是在说明应该允许用户在不改变已有代码的情况下增加新的功能。
实现开闭原则的关键就在于“抽象”。把系统的所有可能的行为抽象成一个抽象底层,这个抽象底层规定出所有的具体实现必须提供的方法的特征。作为系统设计的抽象层,要预见所有可能的扩展,从而使得在任何扩展情况下,系统的抽象底层不需修改;同时,由于可以从抽象底层导出一个或多个新的具体实现,可以改变系统的行为,因此系统设计对扩展是开放的。
看例子
<?phpabstract class vehicle {protected $name;public function __construct(){}public function getName(){return $this->name;} }class car extends vehicle {public function __construct(){parent::__construct();$this->name = 'car';} }class bike extends vehicle {public function __construct(){parent::__construct();$this->name = 'bike';} }class task {private $vehicle;public function __construct($vehicle){$this->vehicle = $vehicle;}public function dotask($kilometer){$vName = $this->vehicle->getName();if ($vName === 'car') {return $this->CarRun($kilometer);} elseif ($vName === 'bike') {return $this->BikeRun($kilometer);}}private function CarRun($kilometer){echo '小汽车跑了'.$kilometer.'公里<hr>';}private function BikeRun($kilometer){echo '自行车跑了'.$kilometer.'公里<hr>';} }echo '<meta charset="utf-8">'; $car = new car; $bike = new bike; $task = new task($car); $task->dotask(10); $task = new task($bike); $task->dotask(10); ?>
这个例子,如果增加一个车,bus,那么就要改动task任务这个类的底层代码
改良版本
<?phpinterface vehicle {public function run($url); }class car implements vehicle {public function run($kilometer){echo '小汽车跑了'.$kilometer.'公里<hr>';} }class bike implements vehicle {public function run($kilometer){echo '自行车跑了'.$kilometer.'公里<hr>';} }class task {private $vehicle;public function __construct($vehicle){$this->vehicle = $vehicle;}public function dotask($kilometer){$this->vehicle->run($kilometer);}}echo '<meta charset="utf-8">'; $car = new car; $bike = new bike; $task = new task($car); $task->dotask(10); $task = new task($bike); $task->dotask(10); ?>
改良后的车辆,任意增加新车型,都不会改动task的内容
3. 里氏替换原则
当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系
可以理解为:只要有父类出现的地方,都可以使用子类来替代。而且不会出现任何错误或者异常。但是反过来却不行。子类出现的地方,不能使用父类来替代。
定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
理解为“只要有父类出现的地方,都可以使用子类来替代”
定义2:所有引用基类的地方必须能透明地使用其子类的对象。
问题由来:有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。
解决方案:当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。
不良的设计:违背了里氏替换原则
假设一个父亲厨师会个炒鸡蛋的手艺,辣椒炒鸡蛋,儿子应该会这个手艺,但是儿子不会用辣椒,只会用大葱,所以炒出来的鸡蛋不一样了。
<?php class father{public function cookeEgg(){echo '辣椒炒鸡蛋<br/>';} }class son1 extends father{public function cookeEgg(){echo '大葱炒鸡蛋<br/>';}} echo '<meta charset="utf-8">'; $f = new father; $f->cookeEgg(); $s1 = new son1; $s1->cookeEgg(); ?>
辣椒炒鸡蛋 大葱炒鸡蛋
违背了原则1:只要有父类出现的地方,都可以使用子类来替代。
现在父亲会辣椒炒鸡蛋,换成儿子来炒,结果儿子因为不敢碰辣椒,炒成了大葱烧鸡蛋。 这个儿子就不是好儿子。我们假设这个是大儿子。
改良版本:出来一个好的二儿子的样子
<?php class father{public function cookeEgg(){echo '辣椒炒鸡蛋<br/>';} } class son2 extends father{public function cookeEggWithScallion(){echo '大葱炒鸡蛋<br/>';} } echo '<meta charset="utf-8">'; $f = new father; $f->cookeEgg(); $s2 = new son2; $s2->cookeEgg(); $s2->cookeEggWithScallion(); ?>
辣椒炒鸡蛋 辣椒炒鸡蛋 大葱炒鸡蛋
这个儿子,复合了定义1,父亲出来的地方,儿子出来就能提到,父亲会辣椒炒鸡蛋,儿子也会,所以儿子自动继承,但是二儿子还会大葱炒鸡蛋,二儿子就会两个炒蛋了
按解决方案:不要重写父亲的方法
规则1: 不要重写父亲的方案
规则2: 所有孩子都会有父亲的技能(父亲能辣椒炒鸡蛋,儿子就会,父亲出现的地方,儿子就可以替代)
规则3: 孩子会额外的技能,自己单独再写函数。(子类出现的地方,父亲不一定能替代)
4. 依赖反转原则
1. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
2. 抽象不应该依赖于细节,细节应该依赖于抽象
实际理解为:高级逻辑层代码,不要因为底层的模块代码改变而变动。
高层模块:业务逻辑层,比如群发邮件,我决定发给购买者和没有购买者,那么群发邮件这个send的工作就是业务逻辑层的高层模块,而决定哪些用户时购买者的底层数据库查询操作属于底层模块。
举例子:老张开宝马,这个动作,开车是业务逻辑层,宝马车跑动是底层。
不良设计
<?php class Bwm{public function run(){echo "开动宝马汽车";} }class Audi{public function run(){echo "开动奥迪汽车";} }//高层模块Driver 依赖了底层模块Bwm , 出来模块C Audi,我就只好改Driver了。 class Driver{public function drive(Bwm $car){$car->run();} }echo '<meta charset="utf-8">'; $bwm = new Bwm; $zhang = new Driver(); $zhang->drive($bwm);$audi = new Audi; $zhang->drive($audi); //奥迪我没法开了。 此处代码会报错。 ?>
有个办法就是业务层,再写一个函数funciton driveAudi() 是不是很熟悉,我们因为要负责处理额外的情况,又写了个看起来很重复函数。
改良的版本,把车做成接口
宝马和奥迪都是来实现车的底层逻辑函数。这样再新车进入的时候,就不用改动逻辑层的代码了。
<?php interface Car{public function run(); } class Bwm implements Car{public function run(){echo "开动宝马汽车";} } class Audi implements Car{public function run(){echo "开动奥迪汽车";} } class Driver{public function drive(Car $car){$car->run();} }echo '<meta charset="utf-8">'; $bwm = new Bwm; $zhang = new Driver(); $zhang->drive($bwm);$audi = new Audi; $zhang->drive($audi); ?>
现在老张可以开宝马也可以开奥迪,你拿个大众,我也照样开。
再次理解这句话:高级逻辑层代码,不要因为底层的模块代码改变而变动。
5. 接口分离原则
不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。
interface Employee {public function work();public function eat(); }class Human implements Employee {public function work(){// ....working}public function eat(){// ...... eating in lunch break} }
机器人雇员不能吃,但是被强迫必须实现吃的接口
class Robot implements Employee {public function work(){//.... working much more}public function eat(){//.... robot can't eat, but it must implement this method} }
改良版本
interface Workable {public function work(); }interface Feedable {public function eat(); }interface Employee extends Feedable, Workable { }class Human implements Employee {public function work(){// ....working}public function eat(){//.... eating in lunch break} }// robot can only work class Robot implements Workable {public function work(){// ....working} }
对面向对象的领悟,有助于在大型代码量的工程里,实现有效分离函数,互不干扰,团队协作。
https://java-er.com/blog/solid-class-study/
面向对象的SOLID例子学习相关推荐
- C#坏习惯:通过不好的例子学习如何制作好的代码——第5部分
目录 介绍 定义和历史 我如何理解OCP? 我如何理解OCP? 3个级别 当代码关闭时 预测未来和YAGNI 让我们编码 不好的例子 更好的方法 SOLID恰当的结合在一起 更多例子 修改或扩展 什么 ...
- 面向对象之SOLID
本文结合b站和网上的一些资料,主要用于纪录知识.有错则请各位大哥多多指点. 文章目录 前言 一.SOLID 二.详解SOLID 1.单一职责原则(SRP) 反例 改进 2. 开闭原则(OCP) 反例 ...
- C#坏习惯:通过不好的例子学习如何制作好的代码——第3部分
目录 这篇文章的目标 什么是c#中的静态类? 我们去看看代码 问题 单元测试 未来的变化 如何修复?? !! 第一步 第二步 第三步 第四步 这次改变后我们获得了什么? 单元测试问题--已解决 未来的 ...
- 数百个 HTML5 例子学习 HT 图形组件 – 拓扑图篇
HT 是啥:Everything you need to create cutting-edge 2D and 3D visualization. 这口号是当年心目中的产品方向,接着就朝这个方向慢慢打 ...
- Liferay例子学习,如何部署简单的jsp portlet
http://blog.chinaunix.net/space.php?uid=9195812&do=blog&id=2006478 Liferay例子学习 (2007-07-23 1 ...
- 质量属性效用树例子_数百个 HTML5 例子学习 HT 图形组件 – 拓扑图篇
HT 是啥:Everything you need to create cutting-edge 2D and 3D visualization. 这口号是当年心目中的产品方向,接着就朝这个方向慢慢打 ...
- PyTorch 1.0 中文官方教程:用例子学习 PyTorch
译者:bat67 最新版会在译者仓库首先同步. 作者:Justin Johnson 这个教程通过自洽的示例介绍了PyTorch的基本概念. PyTorch主要是提供了两个核心的功能特性: 一个类似于n ...
- C#坏习惯:通过不好的例子学习如何制作好的代码——第4部分
目录 介绍 没有"一个真正的来源" 它为什么如此重要? 文章的形式 1.吃异常 2.不正确的日志记录 3.重新抛出异常和方法上下文日志记录 4.控制程序流程的异常 全局异常处理 总 ...
- C#坏习惯:通过不好的例子学习如何制作好的代码——第2部分
目录 介绍 这篇文章的目标 Switch case与字典模式 第一个问题 第二个问题 更有力的例子 了解对象的生命周期 有用的字典 每次调用相同的实例 基础(Basic)版本 代码外的配置 在单独的源 ...
最新文章
- Linux那些事儿之我是Sysfs(6)文件系统
- mysql 特殊函数_MySQL中sleep函数的特殊现象示例详解
- TypeScript里的工具类型Partial的用法
- 从易到难,写一个JavaScript加载器之一
- 【python数字信号处理】——循环卷积(也叫圆圈卷积)
- 人口增量超过北上广!二线城市是怎样逆袭的?
- Transitions Among the Processor’s Operating Modes
- Elasticsearch 实战2:ES 项目实战(二):基本操作、批处理、高级查询
- 通俗编程——白话NIO之Buffer
- php 图片扣背景透明,php 处理透明背景的图片时的问题
- 淘宝优惠券去哪里领?
- MTK 6589充电模块分析
- 计算机内 云盘图标,如何关闭我的电脑中百度网盘图标
- z-blogPHP在西部数码虚拟主机上遇到WTS-WAF错误拦截情况,协商好久他们还是妥协了...
- brctl配置linux bridge及虚拟bridge实现
- ansible之when条件语法、处理任务失败、jinja2模板和项目管理
- 第二十四题——[ZJCTF 2019]NiZhuanSiWei
- 三级分销系统要如何进行推广以及提升曝光度?
- kali渗透综合靶机(三)--bulldog2靶机
- 2022年最赚钱地推项目-WiFi贴项目(月入10个W)