前几节课,我们学习了SOLID原则中的单一职责原则,开闭原则,里氏替换原则,今天则是接口隔离原则

如何理解呢?

其直译含义为 客户端不应该强迫依赖它不需要的接口(只给客户端他需要的功能)

那么对应到我们实际开发中,接口这个概念,可以理解为三种,一组API接口或者集合,单个API接口或者方法函数,OOP中的接口概念

1.一组接口或者集合

微服务系统提供了一组和用户相关的API给其他系统使用,比如用户服务系统,提供了注册,登录,获取用户信息等,接口API如下

public interface UserService{

boolean register (String cellphone,String password);

boolean login(String cellphone,String password);

UserInfo getUserInfoById(long id)

UserInfo getUserInfoByCellphone(String cellphone)

}

一个标准的用户登录登出系统,方便其他的微服务使用,但是,如果后台管理系统需要一个删除用户的功能,希望用户系统提供一个删除用户的功能

如果只是在UserServcie中暴露这个新功能,那么是直接解决了,但是对于很多本来不应该提供删除用户功能的微服务,也能直接使用这个删除用户的功能了,

最好的方式,是使用接口鉴权的方式,进行限制接口的调用工作,但是如果不使用接口鉴权,而是从代码设计的角度来说,调用者不应该强迫其依赖不需要的接口,那么将删除接口放到单独的接口中

RemoveUserServcie中,然后讲这个接口只提供给后天管理系统使用,即可

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 {

// …省略实现代码…

}

上面的例子中,将接口隔离原则的一个接口,理解为一组接口集合,如果某类接口只被一部分调用者使用,那么就应该隔离起来,单独的给调用者使用,而不是强迫其他调用者也依赖这部分不需要的接口

2.把接口理解为单个API或者方法函数

现在我们换一种理解方式,理解为单个接口或者函数,那么这个接口隔离原则就可以认为是函数的设计功能要单一,不要讲多个不同的功能逻辑放到一个函数中实现

假如有一个如下的count函数

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;

}

这个函数count中,会一口气的计算 求最大值 最小值 平均值等,按照单一职责原则,似乎有点冗余

但如果在项目中,这几个统计信息一同显示的话,这就并不显的冗余了,相反,如果每个统计需求都只涉及一部分数据,那么就显得多余了

于是,我们应该将其拆分成多个,粒度更细的统计函数

接口隔离原则和单一职责原则,接口有类似,但是单一职责原则针对的是类,模块,接口的设计,接口隔离原则是单一职责原则,更侧重于接口的设计

3.把接口理解为OOP的接口概念

加入项目中有了三个外部系统,Redis,MySQL,Kafka,每个系统都对应一系列的配置信息,为了存储着写配置信息,每个系统都对应着一系列的配置信息

分别设计了三个Configuration,RedisConfig MySqlConfig,KafkaConfig

public class RedisConfig {

private ConfigSource configSource; //配置中心(比如zookeeper)

private String address;

private int timeout;

private int maxTotal;

//省略其他配置: maxWaitMillis,maxIdle,minIdle…

public RedisConfig(ConfigSource configSource) {

this.configSource = configSource;

}

public String getAddress() {

return this.address;

}

//…省略其他get()、init()方法…

public void update() {

//从configSource加载配置到address/timeout/maxTotal…

}

}

public class KafkaConfig { //…省略… }

public class MysqlConfig { //…省略… }

那么,我们有一新的功能需求,希望支持Redis和Kafka配置信息的热更新,即为可以更改了配置信息,不直接重启系统的情况下,直接将最新的信息加载到内存中

为了实现这个功能需求,设计实现了一个ScheduledUpdate类,定期调用RedisConfig和KafkaConfig的update()方法更新配置信息

那么我们就设计一个Update接口,然后让RedisConfig和KafkaConfig来进行实现

public interface Updater {

void update();

}

public class RedisConfig implemets Updater {

//…省略其他属性和方法…

@Override

public void update() { //… }

}

public class KafkaConfig implements Updater {

//…省略其他属性和方法…

@Override

public void update() { //… }

}

public class MysqlConfig { //…省略其他属性和方法… }

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);

}

}

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();

}

}

刚刚我们的需求已经搞定了,现在有了一个新的监控功能需求,只针对MySQL和Resdis,通过命令行来查看Zookeeper中的配置化信息比较麻烦,于是我们设计了一个Viewer接口

然后RedisCong和MySQLConfig实现着这个Viewer接口

如果不遵循单一职责原则和接口隔离原则,将所有Update和Viewer接口放到了一个Config接口中,让KafkaConfig和RedisConfig和MySQLConfig实现这个Config接口

将ScheduledUpdate的Update和SimpleHttpServer的Viewer,都替换为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() { //… }

}

这样就意味着,需要实现的代码量子增加了,所有的都需要实现Config接口

而且,复用性不如拆分的好,开发一个Metrics性能统计模块,并且希望将Metrics也通过Metrics显示到SimpleHttpServer显示到页面上,这样可以复用Viewer接口

如果是使用了Config接口,还需要实现Update方法

那么总结一下

如何理解接口隔离原则

从三个层面上理解

如果理解为一组接口集合,那么可以将这部分接口隔离出来,单独的给调用者使用

如果理解为单一一个接口的话,如果调用者只需要部分功能,可以将一个函数拆分为粒度更细的函数

如果理解为OOP中的接口的话,可以理解为面向对象编程语言中的抽象,设计更加单一

接口隔离原则和单一职责原则的区别

接口隔离原则相对于单一职责原则,更加侧重于接口逇设计,接口隔离原则提供了一种判断接口职责是否单一的标准,通过调用者如何使用接口来判定,如果只使用接口的部分功能,那么可以认为是职责不单一

Java.util.concurrent提供了AtomicInteger原子类,其中有一个函数getAndIncrement(),是定义为,给整数增加一,并且返回未增之前的值,是否符合单一职责原则和接口隔离原则

针对这个问题,我的理解是,对于单一职责原则和接口隔离原则,是看一个方法的功能是否单一,能不能再继续拆分为粒度更小的接口,而这个方法,其粒度已经足够小了,而且其内部是使用CAS指令来进行替换的,对于CPU来说,是一个没法再拆分的原子操作,而且AtomicInteger内部也提供了get()和set()方法,也针对不同场景提供了incrementAndGet()等方法,方法多而不杂,都是对CAS指令的封装,足够应对多种场景下的使用,我认为是可以的

发表评论

邮箱地址不会被公开。 必填项已用*标注