认识六大设计原则-浅谈单一职责原则

161 阅读7分钟

一、单一职责的定义

稍微有点文化的人,都能理解单一是什么意思,这里不做过多的阐述。这里首先引出三个问题如下:

  1. 在我们日常软件开发过程中为什么要遵循这个原则?
  2. 遵循这个原则的好处是什么?
  3. 在软件设计开发过程中如何去思量这个单一原则?

二、从亲身经历带你认识单一原则

小菜鸟毕业后,进入公司开始写工程代码的时候,拿着new出来的对象,为了功能实现,写着过程式的代码,一个方法里面动不动就写超过100行的代码,变量满处飞。假如你接手这样的代码,首先心里面不知道骂了多少次,别反驳,反驳就是你没见过这种代码,哈哈哈。下面一个简单的例子模仿一下当时的坏味道。
小菜鸟之前是个比较注重发型的人(头发还健在,只是少了,哈哈哈),每天早上第一件事就是看需不需要洗头,这里把洗头这件事分为如下的步骤:

  1. 早上起来用手摸摸头发,看看头油多不多
  2. 如果头发油的话,就烧水洗头
  3. 洗头过程中,涂抹洗发水
  4. 洗完头发拿毛巾擦干头发,然后在用吹风机吹干头发,使用吹飞机之前,检查吹风机的是否可用
  5. 整理好发型,然后打点发胶定定发型。

我们用代码来模拟一下洗头这件事情:
头发长在头上,所以我们首先定义一个head。

public class Head {

    private String hair;

    public String getHair() {
        return hair;
    }

    public void setHair(String hair) {
        this.hair = hair;
    }
}

由于代码充满着坏味道,所以我们新增定义BadCode类

public class BadCode {

    public static void main(String[] args) {
        shampoo(new Head(), "合适水温", "海非丝", "洗头毛巾", "正常使用");
    }

    /**
     * 洗头
     *
     * @param head
     */
    public static void shampoo(Head head, String water, String shampooWater, String maojin, String chuifengji) {
        String hair = head.getHair();
        if (hair == "油头") {
            System.out.println("打开热水器,放水准备洗头");
            //判断水温是否合适
            if (water == "合适水温") {
                System.out.println("用水洗头发");
                System.out.println("将洗发水挤出来,然后涂抹到头发上,让子弹飞一会,作用一下头皮屑");
                //冲水
                System.out.println("将洗发水泡沫清洗掉");
                System.out.println("使用" + maojin + "擦头");
                if (chuifengji.equals("正常使用")) {
                    System.out.println("使用" + chuifengji + "吹头发");
                } else {
                    chuifengji = "正常使用";
                    System.out.println("使用正常吹风机吹头发");
                }
            } else {
                System.out.println("水温不合适,调整水温");
                System.out.println("用水洗头发");
                if (shampooWater.equals("海非丝")) {
                    //重新选择洗发水
                    shampooWater = "清场";
                    System.out.println("将洗发水挤出来,然后涂抹到头发上,让子弹飞一会,作用一下头皮屑");
                    //冲水
                    System.out.println("将洗发水泡沫清洗掉");
                }
                System.out.println("使用" + maojin + "擦头");
                if (chuifengji.equals("正常使用")) {
                    System.out.println("使用" + chuifengji + "吹头发");
                } else {
                    chuifengji = "正常使用";
                    System.out.println("使用正常吹风机吹头发");
                }
                System.out.println("整理发型,喷点发胶定型");
            }
        } else {
            System.out.println("整理发型,喷点发胶定型");
        }
    }
}

上面这段代码只是一个模拟,想必你是看都不想看,实际业务代码比这个要复杂的多的多,比如涉及到RPC接口调用、服务内部调用等等一系列操作。假如每个人都这么写代码,三天之后,他自己估计都得在那里思考一会他写的代码。 这就是所谓的流水账式的坏味道。其次,方法入参这么多于三个后,就不能这么定义方法签名了,太混乱,扩展性差。
那么我们如果遵循这个单一职责的原则,去实现这个东西,该是怎么样的呢,来看看改变?下面我们就遵循单一原则对如上代码进行优化,由于我们是对这个代码进行优化,所以,我们新增一个PerformanceCode,示例代码如下:

public class PerformanceCode {
    public static void main(String[] args) {
        Head head = new Head();
        head.setHair("油头");
        ShampooHeadTool shampooHeadTool = new ShampooHeadTool("合适水温","清场洗发水","海绵宝宝毛巾","正常使用");
        shampoo(head,shampooHeadTool);
    }

    /**
     * 每天早上起来第一件事就是洗头
     * @param head
     * @param shampooHeadTool
     */
    public static void shampoo(Head head,ShampooHeadTool shampooHeadTool) {
        if (isYouTou(head.getHair())){
            //油头就需要用洗头工具洗头了
            shampooHead(shampooHeadTool);
        }else{
            hairStyle();
        }
    }

    /**
     * 洗头发开始与结束
     *
     * @param shampooHeadTool
     * @return
     */
    public static void shampooHead(ShampooHeadTool shampooHeadTool) {
        if (waterIsAble(shampooHeadTool.getWater())) {
            //水温合适就开始洗头吧
            processHead(shampooHeadTool);
        }else{
            //水温不合适就调整水温,调整完水温继续洗头
            System.out.println("调整水温");
            processHead(shampooHeadTool);
        }
        //整理发型
        hairStyle();
    }

    /**
     * 洗头的过程
     *
     * @param shampooHeadTool
     */
    public static void processHead(ShampooHeadTool shampooHeadTool) {
        System.out.println("将洗发水挤出来,然后涂抹到头发上,让子弹飞一会,作用一下头皮屑");
        //冲水
        System.out.println("将洗发水泡沫清洗掉");
        System.out.println("使用" + shampooHeadTool.getMaoJin() + "擦头");
        hairHot(shampooHeadTool.getChuiFengJi());
    }

    /**
     * 使用吹风机吹头发
     * @param chuifengji
     */
    public static void hairHot(String chuifengji) {
        if (chuifengji.equals("正常使用")) {
            System.out.println("使用正常吹风机吹头发");
        } else {
            //修理吹风机
            System.out.println("修理吹风机");
            System.out.println("使用正常吹风机吹头发");
        }
    }


    /**
     * 水温是否合适
     *
     * @param water
     * @return
     */
    public static boolean waterIsAble(String water) {
        if (water.equals("合适水温")) {
            return true;
        }
        return false;
    }


    /**
     * 判断是否是油头
     *
     * @param hair
     * @return
     */
    public static boolean isYouTou(String hair) {
        if (hair == "油头") {
            return true;
        }
        return false;
    }

    /**
     * 整理发型
     */
    public static void hairStyle() {
        System.out.println("整理发型,喷点发胶定型");
    }
}

可能细心的朋友看出来了,针对方法参数的传递,小菜鸟也进行了小优化,对其进行了封装,这其实也可以认为是遵循单一职责的一种思想体现-对象的单一职责。

public class ShampooHeadTool {

    private String water;
    private String shampooWater;
    private String maoJin;
    private String chuiFengJi;
    
    //此处省略setter、getter方法
    public ShampooHeadTool(String water, String shampooWater, String maoJin, String chuiFengJi) {
        this.water = water;
        this.shampooWater = shampooWater;
        this.maoJin = maoJin;
        this.chuiFengJi = chuiFengJi;
    }
}

这样优化之后,当时洗头完了之后想听会音乐,那么你只需要新增一个听音乐的动作就可以了,原有的动作都不会变,这样我们就做到了函数层面的单一职责。从某种程度上讲,这种优化方式,可以在需求变更的时候,尽可能小的降低对代码的侵入度,其次,每个函数都有自己的职责,对于维护和扩展,比较友好。

三、思想打开

网上写的最多的一段话对判断是不是符合单一原则的的定义标准如下:

  • 类中的私有方法过多
  • 你很难给类起一个合适的名字
  • 类中的代码行数、函数或者属性过多
  • 类中大量的方法都是集中操作类中的某几个属性
  • 类依赖的其他类过多,或者依赖类的其他类过多

个人觉得这几个方面总结的不错,大家在实际运用中可以当此作为参考。

除了以上标准,个人觉得还是要分使用场景。比如你在写单纯的复杂业务功能实现的时候,个人觉得函数的单一原则比较重要,起码会让人很清楚整个流程,包括整个流程的功能边界和数据边界等,再比如,你在定义具体的功能类的时候,需要遵循单一职责,可以让你更好的去控制和维护不同抽象的模型领域的不同职责;再比如,在进行服务设计的时候,就要考虑服务的职责问题,这个就是从一个比较大的层面去遵循这个原则。这里就是想告诉刚入门的同学,思想一定要打开,不要把自己限制在一些现存的条条框框中,要根据自己的实际需求,放开思想去实现需求。

四、总结

通过简单的一番介绍,对于开头抛出来的问题,我们已经对其做了解答,如果看了之后,还问答案是什么的话,建议读第二遍,然后自己去写一遍充满坏味道的代码,然后再去思考一下,哈哈哈。写在最后,这里对单一职责做个赞美:

单一职责执其真,

聚焦任务不纷繁。

责任分明如星斗,

代码清晰如潭泉。

模块分离各有位,

程序世界展宏愿。