什么是SOLID原则

SOLID = SRP(职责单一原则) + OCP(对扩展开发,修改关闭原则)+ LSP(里氏替换原则)+ ISP(接口隔离原则)+ DIP(依赖反转原则)

  • SRP: Single Responsibility Principle,一个类或者模块只负责完成一个职责;
  • OCP:Open Closed Principle,软件实体(模块、类、方法等)应该“对扩展开发,对修改关闭”;
  • LSP:Liskov Substitution Principle,子对象能够替换程序中父类对象出现的任何地方,并且保证原来的程序逻辑行为不变及正确性不被破坏;
  • ISP: Interface Segregation Principle,客户端应该不强迫依赖它不需要的接口;
  • DIP: Dependency Inversion Principle,高层模块不要依赖底层模块,高层模块和底层模块之间应该通过抽象来相互依赖,除此之外,抽象不要依赖具体的实现细节,具体实现细节依赖抽象。

SOLID原则评判标准

SRP(职责单一原则)

评判标准

一个类或者模块只负责完成一个职责,不要设计大而全的类,要设计的粒度小、功能单一的类,也就是说一个类包含两个或者两个以上业务不相关的功能,就可以说它的职责不够单一。

  • 类中的代码行数、函数或属性过多,影响代码的可读性和维护性,就需要考虑对类进行拆分;
  • 类依赖其它类过多,或者依赖的类的其它类过多,不符合高内聚、低耦合的设计思想,就需要考虑对类就行拆分;
  • 私有方法过多,考虑能否将私发方法到新的类中,设置为public方法,供更多的类使用,提高代码的复用性;
  • 比较难给定一个适合的名字,很难用一个业务名词概括,说明职责定义的不够清晰;
  • 类中大量的方法都是集中在操作类的几个属性,其它的属性就可以拆分出来。

代码示例

public class User
{
    private String id;
    private String userName;
    private String pwd;
    private String phone;
    private String city;
    private String province;
    private String town;
    private BigDecimal balance;

    public int addUser(User user){
        return 1;
    }
    public int deleteUser(String id){
        return 1;
    }
    public List<User> queryUserByRoleId(String id){
        return new ArrayList<>();
    }
}
  • User类对用户基本信息操作比较频繁,但是地址相关信息只在订单相关功能才用到city、province、town可以单独拆分成一个实体;
  • User基本操作基本不用到账号余额,放到这里有些多余;
  • addUser和deleteUser在一定场景下符合职责单一原则,但是业务扩展可能需要对删除做权限控制,就不太符合这个原则,没有一定的准则;
  • queryUserByRoleId虽然查询的是用户相关信息,但引用了其它实体的内容,我们可以认为是职责不单一。

OCP(对扩展开发,修改关闭原则)

评判标准

一段代码是否易于扩展,某段代码在应未来需求变化的时候,能够做到“对外开放,对修改关闭”,那就说明这段代码的扩展性比较好。

添加一个新功能,不可能任何模块、类、方法不做修改。类需要创建、组装、并且做一些初始化操作,才能构建可运行的程序,修改这部分代码是再所难免的。我们要做的经量修改的时间操作更集中、更少、更上层、经量让最核心、最复杂的那部分逻辑代码满足开闭原则。

代码示例

public class Ocp {
    public boolean pkgValidate(String userName, String phone){
        if(StringUtils.isEmpty(userName)){
            return false;
        }
        if(phone.length() == 1){
            return false;
        }
        return true;
    }
}
  • 如果我们需要新增一个报文校验,就会对这个接口的代码做修改,对应的单元测试全部需要改;
  • 这块属于校验的核心功能,每次改还要把之前的功能测试一遍;

示例代码重构

public class OcpUser
{
    private String id;
    private String userName;
    private String pwd;
    private String phone;
    private BigDecimal balance;
    //get set方法省略
}
-------------------------------------------------------------
public abstract class OcpHandler {
    public abstract boolean check(OcpUser ocpUser);
}
-------------------------------------------------------------
public class PhoneOcpHandler extends OcpHandler{
    @Override
    public boolean check(OcpUser ocpUser) {
        if(ocpUser.getPhone().length() == 1){
            return false;
        }
        return true;
    }
}
-------------------------------------------------------------
public class UserNameOcpHandler extends OcpHandler{
    @Override
    public boolean check(OcpUser ocpUser) {
        if(StringUtils.isEmpty(ocpUser.getUserName())){
            return false;
        }
        return true;
    }
}
-------------------------------------------------------------
public class OcpC {
    private List<OcpHandler> ocpHandlers = new ArrayList<>();
    public void addAlertHandler(OcpHandler ocpHandler) { this.ocpHandlers.add(ocpHandler); }
    public void check(OcpUser ocpUser) {
        for (OcpHandler handler : ocpHandlers) {
            handler.check(ocpUser);
        }
    }
}
-------------------------------------------------------------
public class ApplicationContext {
    private OcpC ocpC;
    // 饿汉式单例
    private static final ApplicationContext instance = new ApplicationContext();
    private ApplicationContext() {
        instance.initializeBeans();
    }
    public static ApplicationContext getInstance() {
        return instance;
    }
    public void initializeBeans() {
        ocpC.addAlertHandler(new PhoneOcpHandler());
        ocpC.addAlertHandler(new UserNameOcpHandler());
    }
    public OcpC getOcpC() {
        return ocpC;
    }
}
-------------------------------------------------------------
public class Demo {
    public static void main(String [] args){
        OcpUser ocpUser = new OcpUser();
        ocpUser.setUserName("张三");
        ocpUser.setPhone("13288888888");
        ApplicationContext.getInstance().getOcpC().check(ocpUser);
    }
}
  • 现在扩展需要改OcpUser新增一个属性,需要校验的属性;
  • 添加一个handler实现check;
  • 在初始化initializeBeans新增一个handler;
  • 在调用的时候加入对应的属性。

我们不能做到完全的对内扩展开放、对修改关闭,我们只可能在最上面抽象尽可能不该动原逻辑的前提下做扩展。

LSP(里氏替换原则)

评判标准

里式替换原则和多态类似,但是多态是实现方式,是面向对象的一个特性,是一种编程语言的语法,原则是规范,规范你怎么开发。

  • 子类违背父类声明要实现的功能,比如说我父类从小到大排序,子类重新这个方法后是从大到小的顺序排序;
  • 子类违背父类对出入、输出、异常约定,入参和出参类型一样,抛的异常类型也必须完全一样;
  • 子类违背父类注释中所罗列的任何特殊说明,实现方法很父类注释方式说明不符。

代码示例

public class Parent {
    public void sort(List<Integer> list){
        //重小到大排序逻辑
    }
    public void query(String userName) throws NullPointerException{
        //查询逻辑
    }
    /**
     * 加1操作
     * @param num
     */
    public void add(int num){
        num++;
    }
}
public class Lsp extends Parent{
    @Override
    public void sort(List<Integer> list){
        //重大道小排序逻辑
    }
    @Override
    public void query(String userName) throws NotBoundException{
        //查询逻辑
    }

    /**
     * 加1操作
     * @param num
     */
    @Override
    public void add(int num){
        num--;
    }
}
  • sort排序的逻辑完全很父类相反;
  • 抛出异常类型不一致;
  • 和注解标识做不相关操作;

ISP(接口隔离原则)

评判标准

接口可以理解成三类,一组API接口集合(业务具体功能点)、单个API接口或者函数(类中的某一个方法)、OOP中的接口(Inteface)概念。

  • 设计职责更加单一的接口,单一就意味着通用、复用性好;
  • 某些类在实现接口的时候,需要重写一些不需要的方法,做了一些无用功。

代码示例

public interface CommonService {
    public List<String> queryUserName(String status);
    public int deleteUser(Long id);
    public List<String> queryRoleName(String userName);
    public int deleteRole(Long id);
}
public class RoleImpl implements CommonService{
    @Override
    public List<String> queryUserName(String status) {
        return null;
    }
    @Override
    public int deleteUser(Long id) {
        return 0;
    }
    @Override
    public List<String> queryRoleName(String userName) {
        return null;
    }
    @Override
    public int deleteRole(Long id) {
        return 0;
    }
}
public class UserImpl implements CommonService{
    @Override
    public List<String> queryUserName(String status) {
        return null;
    }
    @Override
    public int deleteUser(Long id) {
        return 0;
    }
    @Override
    public List<String> queryRoleName(String userName) {
        return null;
    }
    @Override
    public int deleteRole(Long id) {
        return 0;
    }
}
  • 接口不灵活,用户和角色的接口放在一起要多做一个工作量
  • 复用性很差
  • 不利于代码拆分、管理

DIP(依赖反转原则)

评判标准

依赖反转原则也可以叫依赖倒置原则,高优先级的类不应该强行依赖低优先级的类,而是通过一个抽象类间接的操作,子类也基于抽象类做实现。

  • 代码是否易于拆分,在代码重构的时候很难对模块之间解耦,就说明依赖关系太死;
  • 低层次模块提供的接口要足够的抽象、通用,在设计时需要考虑高层次模块的使用种类和场景;
  • 高层次模块没有依赖低层次模块的具体实现,方便低层次模块的替换。

代码示例

interface IWorker{
    public void work();
}

class Worker implements IWorker{
    public void work(){
        // ... working 
    }
}

class SuperWorker implements IWorker{
    public void work(){
        // ... working much more
    }
}

class Manager{
    IWorker worker;

    public void setWorker(IWorker w){
        this.worker = w;
    }

    public void manage(){
        this.worker.work();
    }
}
  • Manager具体实现不依赖Worker和SuperWorker类;
  • 通过抽象接口IWorker相互依赖;
  • 具体实现类依赖接口IWorker;

个人网站:http://www.cnzcb.cn/

公众号:java圈

本文由博客一文多发平台 OpenWrite 发布!

itrickzhang
18 声望3 粉丝