设计原则--单一职责原则

设计原则--单一职责原则

大家好,我是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.

相关推荐
代码山河2 小时前
Python就业方向全解析:后端、爬虫、数据分析、AI你选哪个?
python·学习·架构·教程·面向对象·项目
学长毕设2 小时前
基于SpringBoot的实验室资产管理系统
java·spring boot·后端
微订校园外卖平台3 小时前
校园外卖平台选模板还是定制:架构、数据流与验收清单
大数据·架构·外卖系统·本地生活·微订
努力的小雨4 小时前
给 AI 眼镜开发应用有多难?我从零做了一个陪练,还上架了
后端
ZhengEnCi4 小时前
用 AI 写代码为什么拼到最后还是拼人:算力瓶颈与领域专家提示词的本质差异
算法·架构
Decay__5 小时前
04_MySQL DBA 日常运维手册 —— 会话管理、锁与阻塞、事务监控、慢查询与日志巡检
运维·数据库·sql·mysql·架构
excel5 小时前
双 Token 与前端使用 Crypto 公私钥鉴权换 Token,哪种方案更好?
前端·后端
AI入职记6 小时前
AI 代码审查召回率实测:20 个骗过单元测试的变异体,抓到 13 个
后端
浮链序6 小时前
别让 AI 客服把用户当猴耍:百来行 Python 把「转人工」做成一条高可用链路
后端
属于自己的天空6 小时前
Claude Code 插件完全指南:官方市场怎么用,怎么自己写(v2.1.269+)
后端