<commit_before>---
layout: post

title: "面向对象设计SOLID创业经"
date: 2019-08-15
categories: [OOD]
tag: [OO]

author: "袁慎建"
published: true

brief: "
做了这么多年的面向对象编程还是写出违背SOLID原则的代码，对这些原则不敏感，看过一遍又一遍，还是会忘记，经常在Code Review中被怼，如何是好？难到SOLID原则本身就有错？难道我不应该涉水OOD？
<br/><br/>
编程和培训经验告诉我，原则一般都比较抽象，理解起来不是那么友好。别说是刚接触OO的程序员，有过一定经验的人也不一定能吃透这几个原则。想要提高对OO原则的敏感度，第一步，我们要弄清楚这五大原则到底在讲什么。本文，袁Sir的创业历程将为你揭晓SOLID，一起来看看它到底有多坚固（SOLID）。

"

---

* content
{:toc}


做了这么多年的面向对象编程还是写出违背SOLID原则的代码，对这些原则不敏感，看过一遍又一遍，还是会忘记，经常在Code Review中被怼，如何是好？

- 难到SOLID原则本身就有错？
- 难道我不应该涉水OOD？



既然是对SOLID原则不敏感，我们来先看看SOLID的组成：

- **S**ingle Responsibility Principle，单一职责原则
- **O**pen Close Principle，开闭原则
- **L**iskov Substitution Principle，里氏替换原则
- **I**nterface Segregation Principle，接口隔离原则
- **D**ependency Inversion Principle，依赖倒置原则

编程和培训经验告诉我，原则一般都比较抽象，理解起来不是那么友好。别说是刚接触OO的程序员，有过一定经验的人也不一定能吃透这几个原则。要提高对OO原则的敏感度，第一步，我们要弄清楚这五大原则到底在讲什么。接下来，袁Sir的创业历程将为你揭晓SOLID，一起来看看它到底有多坚固（SOLID）。


---


## ISP：设计小而精美的工具箱，提高顾客体验

己所不欲勿施于人！接口隔离原则诠释着一个与人相处另一个道理：*投其所好*


```
袁Sir创业初期经营了一家五金器具共享租用店，遇到两类顾客，A顾客需要剪刀、锤子、扳手、电锯，B顾客需要锤子、扳手、水果刀、梅花刀。袁Sir为了图省事，把这个六把工具同时装在一个工具箱里，每次都把装有6把工具的工具箱给A或B租用。

经过几次来回借还，袁Sir收到顾客的抱怨，为什么呢？因为袁Sir这么做增加了A和B的负担，首先多了两把工具，扛来抗去的累得多，其次他们拿回去之后会发现："咦，这两个是啥，我没要这个呀？莫非是增值服务？但我要用来来做啥呢？" 结果A从未用过水果刀和梅花刀，但A还要保管好这两工具，增加了保管的成本。同理，B顾客也会面临同样的问题。为了提高顾客的体验，袁Sir把这两类顾客的工具单独用两个更小的工具箱装好，分别提供给A和B，并得到了两类顾客的一致好评。
```

接口隔离原则对我们的行为做了约束，不应该是我们自身为了图一时的方便，塞给用户一些他本需要的东西，而应该是用户要什么，我们就给他什么。

***在代码设计上，尽量不要将一个大而全的接口扔给调用者使用，而是将每个调用者关注的接口进行隔离，封装打包后分别提供给他们。***


---

## LSP：让客户坚信徒弟能代替师傅干活
里氏替换原则则重申了一句古言：*有其父，必有其子*。同样也适用于*有其师，必有其徒*。

```
袁Sir勤勤恳恳经营的五金租用店2年，挣了一大把辛苦钱，正好赶上房地产兴起的年头，家装市场跟着兴起，他果断买下一家知名的家具厂，并跟一家私人工匠所展开了合作。

SJ是工匠所的一名木匠学徒，从师3年有余，书架、衣柜、餐桌，门、窗这些家具已经能够到跟他的师傅如出一辙了。

袁Sir每次合作都是直接跟SJ的师傅对接，而在实际执行过程中，师傅有时候因体力跟不上，就交给SJ去做，好在每次生产出来的家具都能够让袁Sir满意。几次合作下来，袁Sir便相信：徒弟继承了师傅的手艺，师傅能做的这些家具，徒弟也一定能做得一模一样。

2017年，房地产彻底疯了，袁Sir一次性采购订单多出了以往2倍。为了如期交付，SJ的师弟小Y也参与进来。不幸的是，个性十足的小Y打造衣柜和门窗的方式跟师傅的风格差别很大，设计出来的家具让袁Sir一脸懵逼，始料不及的三丈大火烧尽后，袁Sir果断地中断了跟这家工匠会所的合作...
```

***在代码设计中，里氏替换原则对子类进行了约束，子类不应该去重写父类的具体功能。对调用者来说，能够调用父类的地方，一定调用其子类，并且预期结果是一致的。***

换句话说，子类不应该去修改父类已有的功能（在Java中，体现为子类重写了父类中非抽象的方法）。

所以，如果你的继承体系中出现来这种不一致的现象，很可能是你的抽象不够恰当。需要进一步去思考如果去做一个更合理的抽象。关于代码示例，请在文末点击阅读我的文章 *让里氏替换原则为你效力[1]*


---

## DIP：清晰良好的合作协议，让彼此更加信任
依赖导致原则其实揭露了一个工作中非常重要协作准则：部门之间通过约定好的协议来合作，而不关心对方内部由谁做协议所规定的事项。

```
经过上一次不愉快的合作，袁Sir虽然少挣了一笔钱，还生了一肚子气。但台风来，猪想飞你也挡不住。在房地产彻底疯了的这几年里，袁Sir又捞了好几大桶金。有了钱能做什么呢？当然把公司做大，再挣更多的钱呀。

袁Sir秉着 '麻雀虽小，五脏不能不全' 的原则，成立了财务部、法务部、行政部、人事部、市场部、销售部，由于期初业务量没有大到招架不住，这些部门都是光杆司令，而且人事、行政、财务、法务四个部门是由小蔡掌管（袁Sir也流露出了资本家的面貌）。

袁Sir想核对一个报销单，就直接找财务部的小蔡办了，想举办一场隆重年会还得找小蔡，想了解上个月的销售业绩，他去找了销售部的老肖，当然找的最多的还是市场部老史。

经过大半年的快速发展，每个部门从光杆司令变成了群狼作战。袁Sir仍然按照之前的方式办事，但遇到了烦恼 -- 前天报销单还是小蔡在处理，昨天变成了小吴，今天又换成了小任。当然，其他部门也产生了相同的现象。苦思冥想，袁Sir出了一招：部门的负责人制定一个固定的服务列表，比如财务部小蔡提供服务列表：
1. 核对报销单
2. 处理报销
3. 发工资
4. 招聘新同事
5. 评审绩效
6. 举办年会
7. 营业执照审核

袁Sir此时要处理报销、发工资，只用联系小蔡就好，当然小蔡交给谁去做，袁Sir压根不知道，也不需要知道。当然其他部门跟财务部合作也轻松了很多。此后袁Sir对所有部门都进行了整改。

此时袁Sir终于才体会到当老板的一丁点美好了。
```

依赖倒置原则说的是，你本来认识的是一个提供某项服务并且具体做事的人，为了解放自己和他人，你只用认识一个提供服务清单的接口人，而谁去做具体的事情你不用关心。

***在代码设计中，调用者应该依赖一个抽象的服务接口，而不是去依赖一个具体的服务实现，这样就把依赖的关系倒置过来。***


---

## SRP：各司其职，工作效率会大大提升
单一职责原则告诉我们一个重要的处世态度 -- *勿贪多，贪多你将可能踏入欲望的深渊，重者万劫不复。*


```
袁Sir起初让小蔡统管了人事、行政、财务、法务四个部门，业务扩张后也只是给小蔡多招了10来个人。小蔡大脑里就不得不绷紧四根弦，他要给部门各个同事合理分配任务，确保公司能够正常开展日常活动、人员考核培养、工资发放等工作，当然还有一些诸如工商督查、账务审核、信息安全审核等敏感的活动。

暂且不说小蔡有多累，关键是有一次捅了个篓子，差点没葬送公司前程。

一天，工商局来了几个人审核公司的营业资质，就在小蔡跟工商局负责人交涉的过程中，其他管理账目的两位同事因为一项数据没法对齐的数据起了点小争执，全然忘记还有工商局调查人员在，口角中抖了一些账目相关的敏感事情，好在小蔡反应快，及时进行了制止，当然也做了一些额外的安抚工作，把这事给糊弄过去。

这事传到袁Sir耳中，加上小蔡多次跟袁Sir反馈业务太杂，不但累且容易出篓子，袁Sir这才把几个部门进行了拆分，人事部交给了小任，行政部交给了小邢，法务由老发掌管。

经过几个月的验证，让袁Sir欣喜的是，运营成本不但没有增加，员工的工作状态也比之前大有改善。

从此，财务部门小蔡提供的服务列表：
1. 核对报销单
2. 处理报销
3. 发工资

人事部门小任提供的服务列表：
1. 招聘新员工
2. 员工绩效考核
3. 员工培养

行政部小邢提供的服务列表是：
1. 举办年会
2. 接待来宾

法务部提供的服务列表是：
1. 审核营业执照
2. 审核信息安全


周五晚上10点，袁Sir走出办公室大门，哼着小曲自言自语道：还得专门的人干专门的事呀，勿贪多，贪多必失...
```

分离关注点是提高效率的一个很好的途径，如果你同时需要兼顾多项关系不大的工作，你的注意力经常在不同的上下文切换，会降低工作效率，企业为你付出的成本就会增加，更糟糕的是可能会出现一些意外的灾难。

***在代码设计上，让一个类只处理一组相关的事情，控制了它的变化方向，后期也能更好的定位。如果引发变化的因素很多，会导致类的职责过多，导致难以维护，上帝类就是这么形成的。***
<commit_msg>Update content<commit_after>---
layout: post

title: "面向对象设计SOLID创业经"
date: 2019-08-15
categories: [OOD]
tag: [OO]

author: "袁慎建"
published: true

brief: "
做了这么多年的面向对象编程还是写出违背SOLID原则的代码，对这些原则不敏感，看过一遍又一遍，还是会忘记，经常在Code Review中被怼，如何是好？难到SOLID原则本身就有错？难道我不应该涉水OOD？
<br/><br/>
编程和培训经验告诉我，原则一般都比较抽象，理解起来不是那么友好。别说是刚接触OO的程序员，有过一定经验的人也不一定能吃透这几个原则。想要提高对OO原则的敏感度，第一步，我们要弄清楚这五大原则到底在讲什么。本文，袁Sir的创业历程将为你揭晓SOLID，一起来看看它到底有多坚固（SOLID）。

"

---

* content
{:toc}


做了这么多年的面向对象编程还是写出违背SOLID原则的代码，对这些原则不敏感，看过一遍又一遍，还是会忘记，经常在Code Review中被怼，如何是好？

- 难到SOLID原则本身就有错？
- 难道我不应该涉水OOD？



既然是对SOLID原则不敏感，我们来先看看SOLID的组成：

- **S**ingle Responsibility Principle，单一职责原则
- **O**pen Close Principle，开闭原则
- **L**iskov Substitution Principle，里氏替换原则
- **I**nterface Segregation Principle，接口隔离原则
- **D**ependency Inversion Principle，依赖倒置原则

编程和培训经验告诉我，原则一般都比较抽象，理解起来不是那么友好。别说是刚接触OO的程序员，有过一定经验的人也不一定能吃透这几个原则。要提高对OO原则的敏感度，第一步，我们要弄清楚这五大原则到底在讲什么。接下来，袁Sir的创业历程将为你揭晓SOLID，一起来看看它到底有多坚固（SOLID）。


---


## ISP：设计小而精美的工具箱，提高顾客体验

己所不欲勿施于人！接口隔离原则诠释着一个与人相处另一个道理：*投其所好*


```
袁Sir创业初期经营了一家五金器具共享租用店，遇到两类顾客，A顾客需要剪刀、锤子、扳手、电锯，B顾客需要锤子、扳手、水果刀、梅花刀。袁Sir为了图省事，把这个六把工具同时装在一个工具箱里，每次都把装有6把工具的工具箱给A或B租用。

经过几次来回借还，袁Sir收到顾客的抱怨，为什么呢？因为袁Sir这么做增加了A和B的负担，首先多了两把工具，扛来抗去的累得多，其次他们拿回去之后会发现："咦，这两个是啥，我没要这个呀？莫非是增值服务？但我要用来来做啥呢？" 结果A从未用过水果刀和梅花刀，但A还要保管好这两工具，增加了保管的成本。同理，B顾客也会面临同样的问题。为了提高顾客的体验，袁Sir把这两类顾客的工具单独用两个更小的工具箱装好，分别提供给A和B，并得到了两类顾客的一致好评。
```

接口隔离原则对我们的行为做了约束，不应该是我们自身为了图一时的方便，塞给用户一些他本需要的东西，而应该是用户要什么，我们就给他什么。

***在代码设计上，尽量不要将一个大而全的接口扔给调用者使用，而是将每个调用者关注的接口进行隔离，封装打包后分别提供给他们。***


---

## LSP：让客户坚信徒弟能代替师傅干活
里氏替换原则则重申了一句古言：*有其父，必有其子*。同样也适用于*有其师，必有其徒*。

```
袁Sir勤勤恳恳经营的五金租用店2年，挣了一大把辛苦钱，正好赶上房地产兴起的年头，家装市场跟着兴起，他果断买下一家知名的家具厂，并跟一家私人工匠所展开了合作。

SJ是工匠所的一名木匠学徒，从师3年有余，书架、衣柜、餐桌，门、窗这些家具已经能够到跟他的师傅如出一辙了。

袁Sir每次合作都是直接跟SJ的师傅对接，而在实际执行过程中，师傅有时候因体力跟不上，就交给SJ去做，好在每次生产出来的家具都能够让袁Sir满意。几次合作下来，袁Sir便相信：徒弟继承了师傅的手艺，师傅能做的这些家具，徒弟也一定能做得一模一样。

2017年，房地产彻底疯了，袁Sir一次性采购订单多出了以往2倍。为了如期交付，SJ的师弟小Y也参与进来。不幸的是，个性十足的小Y打造衣柜和门窗的方式跟师傅的风格差别很大，设计出来的家具让袁Sir一脸懵逼，始料不及的三丈大火烧尽后，袁Sir果断地中断了跟这家工匠会所的合作...
```

***在代码设计中，里氏替换原则对子类进行了约束，子类不应该去重写父类的具体功能。对调用者来说，能够调用父类的地方，一定调用其子类，并且预期结果是一致的。***

换句话说，子类不应该去修改父类已有的功能（在Java中，体现为子类重写了父类中非抽象的方法）。

所以，如果你的继承体系中出现来这种不一致的现象，很可能是你的抽象不够恰当。需要进一步去思考如果去做一个更合理的抽象。关于代码示例，请在文末点击阅读我的文章 *让里氏替换原则为你效力[1]*


---

## DIP：清晰良好的合作协议，让彼此更加信任
依赖倒置原则其实揭露了一个工作中非常重要协作准则：部门之间通过约定好的协议来合作，而不关心对方内部由谁做协议所规定的事项。

```
经过上一次不愉快的合作，袁Sir虽然少挣了一笔钱，还生了一肚子气。但台风来，猪想飞你也挡不住。在房地产彻底疯了的这几年里，袁Sir又捞了好几大桶金。有了钱能做什么呢？当然把公司做大，再挣更多的钱呀。

袁Sir秉着 '麻雀虽小，五脏不能不全' 的原则，成立了财务部、法务部、行政部、人事部、市场部、销售部，由于期初业务量没有大到招架不住，这些部门都是光杆司令，而且人事、行政、财务、法务四个部门是由小蔡掌管（袁Sir也流露出了资本家的面貌）。

袁Sir想核对一个报销单，就直接找财务部的小蔡办了，想举办一场隆重年会还得找小蔡，想了解上个月的销售业绩，他去找了销售部的老肖，当然找的最多的还是市场部老史。

经过大半年的快速发展，每个部门从光杆司令变成了群狼作战。袁Sir仍然按照之前的方式办事，但遇到了烦恼 -- 前天报销单还是小蔡在处理，昨天变成了小吴，今天又换成了小任。当然，其他部门也产生了相同的现象。苦思冥想，袁Sir出了一招：部门的负责人制定一个固定的服务列表，比如财务部小蔡提供服务列表：
1. 核对报销单
2. 处理报销
3. 发工资
4. 招聘新同事
5. 评审绩效
6. 举办年会
7. 营业执照审核

袁Sir此时要处理报销、发工资，只用联系小蔡就好，当然小蔡交给谁去做，袁Sir压根不知道，也不需要知道。当然其他部门跟财务部合作也轻松了很多。此后袁Sir对所有部门都进行了整改。

此时袁Sir终于才体会到当老板的一丁点美好了。
```

依赖倒置原则说的是，你本来认识的是一个提供某项服务并且具体做事的人，为了解放自己和他人，你只用认识一个提供服务清单的接口人，而谁去做具体的事情你不用关心。

***在代码设计中，调用者应该依赖一个抽象的服务接口，而不是去依赖一个具体的服务实现，这样就把依赖的关系倒置过来。***


---

## SRP：各司其职，工作效率会大大提升
单一职责原则告诉我们一个重要的处世态度 -- *勿贪多，贪多你将可能踏入欲望的深渊，重者万劫不复。*


```
袁Sir起初让小蔡统管了人事、行政、财务、法务四个部门，业务扩张后也只是给小蔡多招了10来个人。小蔡大脑里就不得不绷紧四根弦，他要给部门各个同事合理分配任务，确保公司能够正常开展日常活动、人员考核培养、工资发放等工作，当然还有一些诸如工商督查、账务审核、信息安全审核等敏感的活动。

暂且不说小蔡有多累，关键是有一次捅了个篓子，差点没葬送公司前程。

一天，工商局来了几个人审核公司的营业资质，就在小蔡跟工商局负责人交涉的过程中，其他管理账目的两位同事因为一项数据没法对齐的数据起了点小争执，全然忘记还有工商局调查人员在，口角中抖了一些账目相关的敏感事情，好在小蔡反应快，及时进行了制止，当然也做了一些额外的安抚工作，把这事给糊弄过去。

这事传到袁Sir耳中，加上小蔡多次跟袁Sir反馈业务太杂，不但累且容易出篓子，袁Sir这才把几个部门进行了拆分，人事部交给了小任，行政部交给了小邢，法务由老发掌管。

经过几个月的验证，让袁Sir欣喜的是，运营成本不但没有增加，员工的工作状态也比之前大有改善。

从此，财务部门小蔡提供的服务列表：
1. 核对报销单
2. 处理报销
3. 发工资

人事部门小任提供的服务列表：
1. 招聘新员工
2. 员工绩效考核
3. 员工培养

行政部小邢提供的服务列表是：
1. 举办年会
2. 接待来宾

法务部提供的服务列表是：
1. 审核营业执照
2. 审核信息安全


周五晚上10点，袁Sir走出办公室大门，哼着小曲自言自语道：还得专门的人干专门的事呀，勿贪多，贪多必失...
```

分离关注点是提高效率的一个很好的途径，如果你同时需要兼顾多项关系不大的工作，你的注意力经常在不同的上下文切换，会降低工作效率，企业为你付出的成本就会增加，更糟糕的是可能会出现一些意外的灾难。

***在代码设计上，让一个类只处理一组相关的事情，控制了它的变化方向，后期也能更好的定位。如果引发变化的因素很多，会导致类的职责过多，难以维护，上帝类就是这么形成的。***

{'fraction_non_alphanumeric': 0.1146623554284293, 'fraction_numerical': 0.009451560751150355, 'mean_word_length': 13.892592592592592, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2761329', 'n_tokens_mistral': 7972, 'n_tokens_neox': 9254, 'n_words': 245}