【设计模式】三、面向对象设计原则之单一职责原则

134 阅读5分钟

这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天,点击查看活动详情

系列文章|源码

github.com/tyronczt/de…

定义-是什么

单一职责原则(Single Responsibility Principle,SRP)是指:所有的对象都应该有单一的职责,它提供的所有的服务也都仅围绕着这个职责。换句话说就是:一个类而言,应该仅有一个引起它变化的原因,永远不要让一个类存在多个改变的理由。

单一职责原则的核心就是 解耦增强内聚性

思考-为什么

为什么要遵守单一职责原则?

  1. 提高类的可维护性和可读写性

一个类的职责少了,复杂度降低了,代码就少了,可读性也就好了,可维护性自然就高了。

  1. 提高系统的可维护性

系统是由类组成的,每个类的可维护性高,相对来讲整个系统的可维护性就高。当然,前提是系统的架构没有问题。

  1. 降低变更的风险

如果在一个类中可能会有多个发生变化的东西,这样的设计会带来风险, 我们尽量保证只有一个可以变化,其他变化的就放在其他类中,这样的好处就是提高内聚,降低耦合。

违反单一职责原则有什么坏处?

如果一个类承担的职责过多,等于把这些职责耦合在了一起;一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。

这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。

应用-怎么用

判断标准

不同的应用场景,不同阶段的需求背景,不同的业务层面,对同一个类的职责是否单一,可能会有不同的判定结果。实际上,一些侧面的判断指标更具有指导意义和可执行性,比如出现下面这些情况可能说明这类的设计不满足单一职责原则:

  1. 类中的代码行数,函数或者属性过多;
  2. 类依赖的其他类过多,或者依赖的类的其他类过多;
  3. 私有方法过多;
  4. 比较难给类起一个合适的名字;
  5. 类中大量的方法都集中操作类的某几个属性;

方法层面--单一职责原则

需求:完成用户的用户名和密码修改功能

针对上述需求,有多种实现方案,实现如下:

代码仓库地址:github.com/tyronczt/de…

第一种方法
public enum OperateEnum {
    UPDATE_USERNAME,
    UPDATE_PASSWORD;
}

public interface IUserOperateService {

    /**
     * 修改用户信息
     *
     * @param type     操作类型
     * @param userInfo 用户信息
     */
    void updateUserInfo(OperateEnum type, UserInfo userInfo);
}


public class UserOperateServiceImpl implements IUserOperateService {

    @Override
    public void updateUserInfo(OperateEnum type, UserInfo userInfo) {
        if (type == OperateEnum.UPDATE_PASSWORD) {
            // 修改密码
            System.out.println("正在修改密码");
        } else if (type == OperateEnum.UPDATE_USERNAME) {
            // 修改用户名
            System.out.println("正在修改用户名");
        }
    }
}
第二种方法
public interface IUserOperateService {

    /**
     * 修改用户名
     *
     * @param userInfo 用户信息
     */
    void updateUserName(UserInfo userInfo);

    /**
     * 修改密码
     *
     * @param userInfo 用户信息
     */
    void updateUserPassword(UserInfo userInfo);
}

public class UserOperateServiceImpl implements IUserOperateService {
    @Override
    public void updateUserName(UserInfo userInfo) {
        System.out.println("正在修改用户名");
    }

    @Override
    public void updateUserPassword(UserInfo userInfo) {
        System.out.println("正在修改密码");
    }
}

以上方法都可以实现上述的需求,第一种方法根据方法中的参数去区分执行不同的逻辑,将两个逻辑耦合在了一起,这种实现的弊端也很明显:一是如果客户端传错类型,会导致后续的逻辑无法执行;二是这种实现的可维护性不高,当新增逻辑后会变动原来代码;第二种方法是相对推荐的方法,修改用户名和密码的逻辑分开,各自执行各自的职责,互补干扰,也有利于后期的维护;

由此可见,第二种方法是符合单一职责原则的,这是在方法层面实现的单一职责原则。

接口层面--单一职责原则

需求:大家一起做家务, 张三扫地, 李四买菜. 李四买完菜回来还得做饭。

仓库代码地址:github.com/tyronczt/de…

第一种方法
public interface HouseWork {

    // 扫地
    void sweepFloor();

    // 购物
    void shopping();
}

public class Zhangsan implements HouseWork {
    @Override
    public void sweepFloor() {
        // 扫地
    }

    @Override
    public void shopping() {
        
    }
}

public class Lisi implements HouseWork {
    @Override
    public void sweepFloor() {
        
    }

    @Override
    public void shopping() {
        // 买菜
    }
}

方法一实现了需求的前半句话,由于设计不合理导致后半句需求无法实现。在实现类有不同的需求时,要拆分接口,而不是将功能设计到一个接口中,不符合接口设计的单一职责原则。

第二种方法
public interface HouseWork {
}

public interface Shopping extends HouseWork {
    // 购物
    void shopping();
}

public interface SweepFloor extends HouseWork {
    // 扫地
    void sweepFlooring();
}

public interface Cooking extends HouseWork {
    // 做饭
    void cooking();
}

public class Zhangsan implements SweepFloor {
    @Override
    public void sweepFlooring() {
        // 张三扫地
    }
}

public class Lisi implements Shopping, Cooking {
    @Override
    public void shopping() {
        // 李四购物
    }

    @Override
    public void cooking() {
        // 李四做饭
    }
}

方法二将每个动作定义为接口并进行实现,符合单一职责原则,一个类只做一件事,并且他的修改不会带来其他的变化,便于后续的系统维护。

类层面--单一职责原则

从类的层面来讲, 没有办法完全按照单一职责原来来拆分,换种说法: 类的职责可大可小,不像接口那样可以很明确的按照单一职责原则拆分,只要符合逻辑有道理即可。

需求:完成用户相关逻辑。

仓库代码地址:github.com/tyronczt/de…

第一种方法:
public interface IUserOperateService {

    // 用户登录
    void login(UserInfo userInfo);

    // 用户注册
    void register(UserInfo userInfo);

    // 用户登出
    void logout(UserInfo userInfo);
}

public class UserOperateServiceImpl implements IUserOperateService {
    @Override
    public void login(UserInfo userInfo) {
        // 用户登录
    }

    @Override
    public void register(UserInfo userInfo) {
        // 用户注册
    }

    @Override
    public void logout(UserInfo userInfo) {
        // 用户登出
    }
}
第二种方法:
public interface ILoginService {
    void login();
}

public interface ILogoutService {
    void logout();
}

public interface IRegisterService {
    void register();
}

public class LoginServiceImpl implements ILoginService {
    @Override
    public void login() {
        // 用户登录
    }
}

public class LogoutServiceImpl implements ILogoutService {
    @Override
    public void logout() {
        // 用户登出
    }
}

public class RegisterServiceImpl implements IRegisterService{
    @Override
    public void register() {
        // 用户注册
    }
}

方法1和方法2均可以实现上述的需求,在实际项目开发中,往往两种方法都会用到,当接口实现逻辑相对简单时,往往会使用第一种方法,当接口实现逻辑复杂时,则会采用第二种方法,具体情况还需具体分析。

参考

设计模式六大原则(一)----单一职责原则 - 掘金