• 【Java设计模式 SOLID设计原则】四 ISP接口隔离原则


    书接前文,在了解了SRP、OCP、LSP之后,再来看看ISP接口隔离原则的定义和真实的内涵

    理解接口隔离原则

    接口隔离原则的英文翻译是Interface Segregation Principle,缩写为ISP客户端不应该被强迫依赖它不需要的接口。其中的客户端,可以理解为接口的调用者或者使用者,这里的接口不仅指Java里的接口类,实际上它有三种含义: 一组 API 接口或方法集合单个 API 接口或方法OOP 中的接口概念

    一组 API 接口或方法集合

    如果把接口当做一组方法集合,那么接口隔离原则就是,仅暴露给调用者他关心的接口。在设计微服务或类库时,我们经常需要提供一组操作给用户使用,例如用户的相关信息可以存储为一组方法集合:

    
    public interface UserService {
      boolean register(String cellphone, String password);
      boolean login(String cellphone, String password);
      UserInfo getUserInfoById(long id);
      UserInfo getUserInfoByCellphone(String cellphone);
    }
    
    public class UserServiceImpl implements UserService {
      //...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    此时我们需要增加删除用户这样的操作,这样的操作较为危险,不能提供给普通用户操作,同时普通用户也不需要操作,这个功能是给管理员设计的,所以为了避免暴露这样的方法给普通用户,我们对接口进行隔离:

    // 普通用户操作行为
    public interface UserService {
      boolean register(String cellphone, String password);
      boolean login(String cellphone, String password);
      UserInfo getUserInfoById(long id);
      UserInfo getUserInfoByCellphone(String cellphone);
    }
    // 超管操作行为
    public interface RestrictedUserService {
      boolean deleteUserByCellphone(String cellphone);
      boolean deleteUserById(long id);
    }
    
    public class UserServiceImpl implements UserService, RestrictedUserService {
      // ...省略实现代码...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    如果部分接口只被部分调用者使用,那我们就需要将这部分接口隔离出来,单独给对应的调用者使用,而不是强迫其他调用者也依赖这部分不会被用到的接口

    单个 API 接口或方法

    如果把接口理解为单个方法,那接口隔离原则就可以理解为:函数的设计要功能单一,不要将多个不同的功能逻辑在一个函数中实现

    以一个统计方法为例:

    
    public class Statistics {
      private Long max;
      private Long min;
      private Long average;
      private Long sum;
      private Long percentile99;
      private Long percentile999;
      //...省略constructor/getter/setter等方法...
    }
    
    public Statistics count(Collection<Long> dataSet) {
      Statistics statistics = new Statistics();
      //...省略计算逻辑...
      return statistics;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在上面的代码中,count() 函数的功能不够单一,包含很多不同的统计功能,比如,求最大值、最小值、平均值等等。按照接口隔离原则,我们应该把 count() 函数拆成几个更小粒度的函数,每个函数负责一个独立的统计功能

    
    public Long max(Collection<Long> dataSet) { //... }
    public Long min(Collection<Long> dataSet) { //... } 
    public Long average(Colletion<Long> dataSet) { //... }
    // ...省略其他统计函数...
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这样的好处是如果我们常用的统计是sum,那么其它的方法都需要耗费无意义的算力,不过判定功能是否单一,除了很强的主观性,还需要结合具体的场景。如果我们确实每次都需要所有的统计逻辑,那么这个方法也不需要拆分。看使用者如何去看待了。

    OOP 中的接口概念

    如果把“接口”理解为 OOP 中的接口,也可以理解为面向对象编程语言中的接口语法。那接口的设计要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数

    热更新接口

    
    public interface Updater {
      void update();
    }
    
    • 1
    • 2
    • 3
    • 4

    监控接口

    public interface Viewer {
      String outputInPlainText();
      Map<String, String> output();
    }
    
    • 1
    • 2
    • 3
    • 4

    Redis,Kafka,Mysql配置实现

    // Redis支持热更新和监控
    public class RedisConfig implemets Updater, Viewer {
      //...省略其他属性和方法...
      @Override
      public void update() { //... }
      @Override
      public String outputInPlainText() { //... }
      @Override
      public Map<String, String> output() { //...}
    }
    // Kafka只支持热更新
    public class KafkaConfig implements Updater {
      //...省略其他属性和方法...
      @Override
      public void update() { //... }
    }
    // Mysql只支持监控
    public class MysqlConfig implements Viewer {
      //...省略其他属性和方法...
      @Override
      public String outputInPlainText() { //... }
      @Override
      public Map<String, String> output() { //...}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    热更新管理器,基于接口的方式依赖注入

    public class ScheduledUpdater {
        private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();;
        private long initialDelayInSeconds;
        private long periodInSeconds;
        private Updater updater;
    
        public ScheduleUpdater(Updater updater, long initialDelayInSeconds, long periodInSeconds) {
            this.updater = updater;
            this.initialDelayInSeconds = initialDelayInSeconds;
            this.periodInSeconds = periodInSeconds;
        }
    
        public void run() {
            executor.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    updater.update();
                }
            }, this.initialDelayInSeconds, this.periodInSeconds, TimeUnit.SECONDS);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    监控管理器,基于接口的方式依赖注入

    public class SimpleHttpServer {
      private String host;
      private int port;
      private Map<String, List<Viewer>> viewers = new HashMap<>();
      
      public SimpleHttpServer(String host, int port) {//...}
      
      public void addViewers(String urlDirectory, Viewer viewer) {
        if (!viewers.containsKey(urlDirectory)) {
          viewers.put(urlDirectory, new ArrayList<Viewer>());
        }
        this.viewers.get(urlDirectory).add(viewer);
      }
      
      public void run() { //... }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    基于不同的需求使用热更新和监控

    public class Application {
        ConfigSource configSource = new ZookeeperConfigSource();
        public static final RedisConfig redisConfig = new RedisConfig(configSource);
        public static final KafkaConfig kafkaConfig = new KakfaConfig(configSource);
        public static final MySqlConfig mysqlConfig = new MySqlConfig(configSource);
        
        public static void main(String[] args) {
            ScheduledUpdater redisConfigUpdater =
                new ScheduledUpdater(redisConfig, 300, 300);
            redisConfigUpdater.run();
            
            ScheduledUpdater kafkaConfigUpdater =
                new ScheduledUpdater(kafkaConfig, 60, 60);
            redisConfigUpdater.run();
            
            SimpleHttpServer simpleHttpServer = new SimpleHttpServer(127.0.0.1, 2389);
            simpleHttpServer.addViewer("/config", redisConfig);
            simpleHttpServer.addViewer("/config", mysqlConfig);
            simpleHttpServer.run();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    ISP和SRP的区别

    通过拆分方法让代码粒度变细的方式,ISP和SRP有点类似,不过稍微还是有点区别。单一职责原则针对的是模块、类、接口的设计。而接口隔离原则相对于单一职责原则,一方面它更侧重于接口的设计,另一方面它的思考的角度不同。它提供了一种判断接口是否职责单一的标准:通过调用者如何使用接口来间接地判定。如果调用者只使用部分接口或接口的部分功能,那接口的设计就不够职责单一。也就是说ISP的判定更为主观一些。

    总结一下

    ISP中的接口对于不同功能诉求的使用者来说,可以当做一组 API 接口或方法集合,按照使用者分类来暴露给不同使用者差异化的接口集,不要给使用者它不care的功能;对于单一功能的使用者来说,可以当做一个API接口或方法集合,按照使用者的场景诉求主观判断是否需要拆分,不要给使用者他不care的复杂方法实现;对于一个固定的需求实现而言,可以当做一个OOP的接口去看待,按照需求定义接口,不要让接口的实现类和调用者,依赖它不care的功能的接口

  • 相关阅读:
    补题2.0
    基于Spider的全站数据爬取
    多线程&并发篇---第十六篇
    Java使用FileChannel进行文件拷贝(提升拷贝效率)
    [LeetCode]-链表-2
    Vuex的使用
    你不了解的二进制0X01
    马斯克对话“虚拟版”马斯克,脑机交互技术离我们有多远
    计算机毕设 flink大数据淘宝用户行为数据实时分析与可视化
    Netty 学习(六):创建 NioEventLoopGroup 的核心源码说明
  • 原文地址:https://blog.csdn.net/sinat_33087001/article/details/126880696