设计原则--单一职责原则
大家好,我是Ant,今天我们来学习的是设计原则之一的单一职责原则(Single Responsibility Principle) .
什么是单一职责原则
单一职责原则,顾名思义,就是只有一个职责,只负责一件事。
单一职责原则:有且仅有一个原因引起类的变化
为什么用单一职责原则
如果一个类的职责类型过多,就像一个人同时做两件事一样。比如:在一个饭店里小A既做点菜员又做厨师, 那么我们可以想象一下,一个顾客进来了,小A出来给顾客点菜,然后顾客点完菜后去厨房做菜, 此时又进来一个顾客,小A是出来还是不出来呢?不出来顾客点不了菜,可能等不及就走了; 出来帮顾客点完菜,回到厨房一看上个顾客的菜糊了。
这就是因为职责的类型过多,导致了多个职责之前互相影响,因为修改其中的一个职责导致另一个职责出现错误。
同时我们需要注意,单一职责并不是代表只做一件事,而是做相同类型的事,比如厨师炒菜,不可能只炒一个菜,而是炒一个菜系的菜。
单一职责原则的实现
我们来实现一下上面提到的例子: 首先是点菜员:
arduino
public class OrderTaker {
/**
* 厨师
*/
Chef chef;
public OrderTaker(Chef chef) {
this.chef = chef;
}
/**
* 点菜
* @param name 菜名
*/
public void takeOrder(String name) {
System.out.println("顾客点菜:" + name);
chef.addFood(name);
}
}
然后我们再实现厨师类,要注意,厨师做菜的时候,点菜员还是可以工作的,所以厨师烹饪使用了线程:
csharp
import java.util.LinkedList;
public class Chef {
private final LinkedList<String> foodList = new LinkedList<>();
public Chef() {
this.cook();
}
/**
* 做菜
*/
public void cook(){
new Thread(() -> {
while (true) {
if (foodList.isEmpty()) {
System.out.println("等待点菜");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
String name = foodList.pollFirst();
if (StringUtils.isNotBlank(name)) {
System.out.println(name + " is cooking!");
Thread.sleep(1000);
System.out.println(name + "is cooked");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
}
/**
* 添加菜单
* @param name
*/
public void addFood(String name) {
foodList.add(name);
}
}
最后我们使用测试类来实验一下:
csharp
public class Test {
public static void main(String[] args) {
Chef chef = new Chef();
OrderTaker orderTaker = new OrderTaker(chef);
System.out.println("顾客A点菜");
orderTaker.takeOrder("鱼香肉丝");
System.out.println("顾客B点菜");
orderTaker.takeOrder("肉末茄子");
System.out.println("顾客C点菜");
orderTaker.takeOrder("酸辣土豆丝");
}
}
---------
输出:
等待点菜
顾客A点菜
顾客点菜:鱼香肉丝
顾客B点菜
顾客点菜:肉末茄子
顾客C点菜
顾客点菜:酸辣土豆丝
鱼香肉丝 is cooking!
鱼香肉丝is cooked
肉末茄子 is cooking!
肉末茄子is cooked
酸辣土豆丝 is cooking!
酸辣土豆丝is cooked
等待点菜
此时,我们修改OrderTasker的takeOrder方法,让他也支持收钱找零的功能(这么做是不对的,不符合单一职责也不符合开闭原则,为了更容易理解单一职责带来的好处,所以这么做),而这样也丝毫不影响厨师做菜。:
csharp
...
public void takeOrder(String name) {
System.out.println("顾客点菜:" + name);
chef.addFood(name);
System.out.println("收您100,找您50");
}
--------
输出:
等待点菜
顾客A点菜
顾客点菜:鱼香肉丝
收您100,找您50
顾客B点菜
顾客点菜:肉末茄子
收您100,找您50
顾客C点菜
顾客点菜:酸辣土豆丝
收您100,找您50
鱼香肉丝 is cooking!
鱼香肉丝is cooked
肉末茄子 is cooking!
肉末茄子is cooked
酸辣土豆丝 is cooking!
酸辣土豆丝is cooked
等待点菜
如何判断一个类的职责是否单一呢?
以我们厨师的类来举例:他是否职责单一呢?看一个类职责是否单一,其实不能只看设计,还需要结合实际的业务。
比如一个夫妻店,点菜员点完菜,将点菜的单子给厨师就完事了,厨师看着单子去做菜,那么我们上面的例子就符合单一职责原则。因为处理单子和根据单子做菜都是他的职责。
后来店越做越大,厨师也越来越多,那么厨师再去处理单子就不合适了,因为每个厨师都有自己的单子,那么就很容易出问题了。
此时就需要将单子的职责划分出来,做一个FoodManager
类,专门的人(厨师长、主厨)或工具来管理顾客的单子, 这样才能有效避免单子重复传给不同的厨师或者厨师做重了菜品。
那么我们可以这样重构代码: 首先将厨师管理单子的职责拆分出来:
csharp
import java.util.LinkedList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class FoodManager {
private final LinkedList<String> foodList = new LinkedList<>();
private Lock lock = new ReentrantLock();
public void addFood(String name) {
lock.lock();
try {
foodList.add(name);
} finally {
lock.unlock();
}
}
public boolean isEmpty() {
lock.lock();
try {
return foodList.isEmpty();
} finally {
lock.unlock();
}
}
public String poll() {
lock.lock();
try {
return foodList.pollFirst();
} finally {
lock.unlock();
}
}
}
然后厨师不再管理单据,而是从单据管理中查看是否有没做的菜品:
csharp
public class Chef {
private FoodManager foodManager;
private String name;
public Chef(FoodManager foodManager, String name) {
this.foodManager = foodManager;
this.name = name;
this.cook();
}
public void cook(){
new Thread(() -> {
while (true) {
if (foodManager.isEmpty()) {
System.out.println("厨师 "+ name + "等待点菜");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
String food = foodManager.poll();
if (StringUtils.isNotBlank(food)) {
System.out.println(name + " is cooking " + food);
Thread.sleep(1000);
System.out.println(name + "is cooked " + food);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
}
}
点菜员也不再和厨师直接对接,而且将单据交给单据管理者:
csharp
public class OrderTaker {
FoodManager foodManager;
public OrderTaker(FoodManager foodManager) {
this.foodManager = foodManager;
}
public void takeOrder(String name) {
System.out.println("顾客点菜:" + name);
foodManager.addFood(name);
System.out.println("收您100,找您50");
}
}
然后我们再测试一下,因为店大了吗,我们多弄两个厨师:
csharp
public class Test {
public static void main(String[] args) {
FoodManager foodManager = new FoodManager();
new Chef(foodManager, "Ant");
new Chef(foodManager, "Victor");
new Chef(foodManager, "小王");
OrderTaker orderTaker = new OrderTaker(foodManager);
System.out.println("顾客A点菜");
orderTaker.takeOrder("鱼香肉丝");
System.out.println("顾客B点菜");
orderTaker.takeOrder("肉末茄子");
System.out.println("顾客C点菜");
orderTaker.takeOrder("酸辣土豆丝");
System.out.println("顾客D点菜");
orderTaker.takeOrder("酸辣土豆丝");
System.out.println("顾客F点菜");
orderTaker.takeOrder("酸辣土豆丝");
}
}
-----
输出:
厨师 Ant等待点菜
厨师 小王等待点菜
厨师 Victor等待点菜
顾客A点菜
顾客点菜:鱼香肉丝
收您100,找您50
顾客B点菜
顾客点菜:肉末茄子
收您100,找您50
顾客C点菜
顾客点菜:酸辣土豆丝
收您100,找您50
顾客D点菜
顾客点菜:风味茄子
收您100,找您50
顾客F点菜
顾客点菜:双头鲍
收您100,找您50
Ant is cooking 鱼香肉丝
Victor is cooking 肉末茄子
小王 is cooking 酸辣土豆丝
Victoris cooked 肉末茄子
Antis cooked 鱼香肉丝
Ant is cooking 双头鲍
小王is cooked 酸辣土豆丝
厨师 小王等待点菜
Victor is cooking 风味茄子
Antis cooked 双头鲍
Victoris cooked 风味茄子
厨师 Victor等待点菜
厨师 小王等待点菜
厨师 Ant等待点菜
结语
至此我们完成单一原则代码的示例,并模拟业务升级重构了代码,让代码耦合度更低,代码逻辑更清晰。
上面的代码有缺点吗?那定然是有的,比如FoodManager
是否可以考虑升级为接口呢?
下一篇我们来学习接口隔离原则
,看看如何使用接口隔离原则重构我们的代码。
如果有表达有误的地方,欢迎评论区讨论,我会及时回复并修改有误的内容,感谢观看!
同时也欢迎到我的公众号迷途架构
, 也欢迎链接我的个人微信CodeJourneyTop
.