zoukankan      html  css  js  c++  java
  • 设计模式之美学习-接口隔离原则(七)

    客户端不应该强迫依赖它不需要的接口。其中的“客户端”,可以理解为接口的调用者或者使用者。

    看起来和单一职责很像  单一职责是约束类和模块  接口隔离原则是约束接口或函数

    三种表现形式

    "接口"理解为一组API

    微服务接口比如dubbo Service

    反例

    public interface UserService {
      boolean register(String cellphone, String password);
      boolean login(String cellphone, String password);
      UserInfo getUserInfoById(long id);
      UserInfo getUserInfoByCellphone(String cellphone);
      boolean deleteUserByCellphone(String cellphone);
      boolean deleteUserById(long id);
    }
    
    public class UserServiceImpl implements UserService {
      //...
    }

    比如我们铭感操作 如删除 只在当前服务能够使用,这个时候对外暴露 其他服务也会有这些铭感操作,因为我们接口涉及粒度过大

    正例

    因为我们对外暴露

    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 {
      // ...省略实现代码...
    }

    优化后 当前服务能操作的铭感接口抽出一个新的接口,但是我们也不能过度设计 比如 一个方法一个接口

    把“接口”理解为单个 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等方法...
    }
    
        /**
         * 实现max min average sum 的计算
         * @param dataSet
         * @return
         */
        public Statistics count(Collection<Long> dataSet) {
            Statistics statistics = new Statistics();
            //...省略计算逻辑...
            return statistics;
        }

    如果计算max min average 每一步都非常耗时   外部调用这个接口 只需要计算max 这个时候就强迫调用方依赖了  min average 违反原则

    正例

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

    调用方根据需求调用对应的接口

    把“接口”理解为 OOP 中的接口概念

    我们有一组需求 将对应的config配置实现热部署  以及将配置信息输出到指定端

    反例

    public interface Config {
        //热部署
        void update();
        //将对应配置输出出到对应的数据源 前端做展示
        String outputInPlainText();
        Map<String, String> output();
    }
    
    public class RedisConfig implements Config {
        //...需要实现Config的三个接口update/outputIn.../output
    }
    
    public class KafkaConfig implements Config {
        //...需要实现Config的三个接口update/outputIn.../output
    }
    
    public class MysqlConfig implements Config {
        //...需要实现Config的三个接口update/outputIn.../output
    }
    
    public class ScheduledUpdater {
        //...省略其他属性和方法..
        private Config config;
    
        public ScheduleUpdater(Config config, long initialDelayInSeconds, long periodInSeconds) {
            this.config = config;
            //...
        }
        //...
    }
    
    public class SimpleHttpServer {
        private String host;
        private int port;
        private Map<String, List<Config>> viewers = new HashMap<>();
    
        public SimpleHttpServer(String host, int port) {//...}
    
            public void addViewer (String urlDirectory, Config config){
                if (!viewers.containsKey(urlDirectory)) {
                    viewers.put(urlDirectory, new ArrayList<Config>());
                }
                viewers.get(urlDirectory).add(config);
            }
    
            public void run () { //... }
            }
        }
    }

    如果我们出于安全考虑mysqlConfig不能支持热部署以及输出到远端 因为我们接口粒度过粗 就会导致MySqlConfig强行依赖了它不需要实现的接口

    正例

    public interface Updater {
      void update();
    }
    
    public interface Viewer {
      String outputInPlainText();
      Map<String, String> output();
    }
    
    public class RedisConfig implemets Updater, Viewer {
      //...省略其他属性和方法...
      @Override
      public void update() { //... }
      @Override
      public String outputInPlainText() { //... }
      @Override
      public Map<String, String> output() { //...}
    }
    
    public class KafkaConfig implements Updater {
      //...省略其他属性和方法...
      @Override
      public void update() { //... }
    }
    
    public class MysqlConfig implements Viewer {
      //...省略其他属性和方法...
      @Override
      public String outputInPlainText() { //... }
      @Override
      public Map<String, String> output() { //...}
    }
    
    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() { //... }
    }
    
    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();
        }
    }

    将热部署和推送配置到远端拆分成2个接口 实现类 根据需求选择实现

    二种方式比较

    正例设计思路更加灵活、易扩展、易复用。因为 Updater、Viewer 职责更加单一,单一就意味了通用、复用性好。比如,我们现在又有一个新的需求,开发一个 Metrics 性能统计模块,并且希望将 Metrics 也通过 SimpleHttpServer 显示在网页上,以方便查看。这个时候,尽管 Metrics 跟 RedisConfig 等没有任何关系,但我们仍然可以让 Metrics 类实现非常通用的 Viewer 接口

    反例因为 Config 接口中包含两类不相关的接口,一类是 update(),一类是 output() 和 outputInPlainText()。理论上,KafkaConfig 只需要实现 update() 接口,并不需要实现 output() 相关的接口。同理,MysqlConfig 只需要实现 output() 相关接口,并需要实现 update() 接口。但第二种设计思路要求 RedisConfig、KafkaConfig、MySqlConfig 必须同时实现 Config 的所有接口函数(update、output、outputInPlainText)。除此之外,如果我们要往 Config 中继续添加一个新的接口,那所有的实现类都要改动。相反,如果我们的接口粒度比较小,那涉及改动的类就比较少。

  • 相关阅读:
    windows批处理命令笔记
    linux 配置互访免密登录 sshkeygen
    jenkins 中 pipeline 管理部署服务到k8s 插件总结
    求教:Net环境导致WPF程序无法启动
    读《C程序设计语言》笔记11
    求教:.Net Framework 3.5 SP1安装失败
    设计模式
    flash基本操作二库面板和元件创建
    AUTOCAD自学教程一
    flash基本操作
  • 原文地址:https://www.cnblogs.com/LQBlog/p/12133316.html
Copyright © 2011-2022 走看看