Spring 服务中的接口隔离 SOLID 原则
interface segregation SOLID principle in Spring services
我尽可能精简我的服务接口,通常它们是@FunctionalInterface。我尝试遵循那里的接口隔离原则。
如果我的服务实现共享大部分依赖项,那么我的服务实现实现多个接口是一个好习惯吗?或者我应该为每个接口创建一个单独的实现?
// find the T scheduled between the given instants.
interface TaskPeriodFinder<T> {
List<T> find(Instant initial, Instant end);
}
// schedule a task to be execute in a given time.
interface TaskScheduler<T> {
void scheduleOn(T task, Instant scheduledOn);
}
// execute the scheduled task when the scheduledOn is reached.
interface TaskExecutor<T> {
void execute(T task);
}
@Service
class TaskService implements TaskScheduler<String>, TaskExecutor<>, TaskPeriodFinder<> {
private final TaskFinder taskFinder;
private final TaskCreator taskCreator;
public void scheduleOn(String task, Instant scheduledOn) {
// TODO
}
public void execute(String task) {
// TODO
}
public List<T> find(Instant initial, Instant end) {
// TODO
}
}
在我的例子中; TaskService 实现这两个接口是一个好习惯吗?或者我应该为每个接口单独设置 class 吗?
如果您创建的每个界面都只有一个方法,那么您就做错了。
假设有人想要找到一段时间内的任务数量,如果数量高于或低于某个任意阈值则添加一个新任务。用你的设计,这是不可能的。您可以与发现者或执行者交谈,但不能与 FinderAndExecutor 交谈。在这种情况下,您只需要直接与 TaskService
对话,完全破坏了接口的意义。
假设 Java 的 Collection
接口被分解成这些接口:
interface Clearable {
void clear();
}
interface Containable<T> {
boolean contains(T obj);
}
interface Sized {
int size();
}
interface Removable<T> {
boolean remove(T obj);
}
// etc...
如果大小 > 100,现在你需要做一些清除的事情。你使用什么类型?
public static void clearIfTooBig(??? collection) {
if (collection.size() > 100) collection.clear();
}
你可以用类型交集来做一些事情,但大多数人不知道它,你的方法签名会很笨重。
我认为,您的方法是跟上 SOLID 的好方法。
在您当前的示例中,我建议您在必要时将接口捆绑在一起。我建议您执行以下操作,将 TaskExecutor 和 TaskSchedular 分组到一个名为 TaskService 的新接口中。
interface TaskService extends TaskSchedular<String>, TaskExecutor<String> {}
并且您的class可以实现TaskService接口。我认为这种方法有利于组合而不是继承。
public class TaskServiceImpl implements TaskService { ... }
如何申请ISP没有统一的规则。这一切都是为了为客户制作有用的抽象并找到合适的平衡点。如果所有客户端都同时使用操作 A 和 B,并且这些操作具有内聚性,那么通过分离行为就没有真正的价值。
过于细化可能会迫使客户依赖于同一概念的多个“视图”来实现他们的目标,而另一方面,过于粗略会迫使客户依赖比他们需要的更多的东西.
但是,如果您最终只是将细粒度的接口捆绑在一个更大的外观中并且仅仅依赖于外观,那么您的粒度接口在任何方面都没有帮助。
除非你正在构建一个必须从一开始就具有灵活性的库,否则我建议让抽象根据实际使用情况自行出现,而不是默认使用一个方法接口并在你进行时进行重构.
找到合适的域抽象通常是一个迭代过程,很少从一开始就完成。例如,Java 设计师未能及早意识到他们应该为列表提供一个只读界面。
我尽可能精简我的服务接口,通常它们是@FunctionalInterface。我尝试遵循那里的接口隔离原则。
如果我的服务实现共享大部分依赖项,那么我的服务实现实现多个接口是一个好习惯吗?或者我应该为每个接口创建一个单独的实现?
// find the T scheduled between the given instants.
interface TaskPeriodFinder<T> {
List<T> find(Instant initial, Instant end);
}
// schedule a task to be execute in a given time.
interface TaskScheduler<T> {
void scheduleOn(T task, Instant scheduledOn);
}
// execute the scheduled task when the scheduledOn is reached.
interface TaskExecutor<T> {
void execute(T task);
}
@Service
class TaskService implements TaskScheduler<String>, TaskExecutor<>, TaskPeriodFinder<> {
private final TaskFinder taskFinder;
private final TaskCreator taskCreator;
public void scheduleOn(String task, Instant scheduledOn) {
// TODO
}
public void execute(String task) {
// TODO
}
public List<T> find(Instant initial, Instant end) {
// TODO
}
}
在我的例子中; TaskService 实现这两个接口是一个好习惯吗?或者我应该为每个接口单独设置 class 吗?
如果您创建的每个界面都只有一个方法,那么您就做错了。
假设有人想要找到一段时间内的任务数量,如果数量高于或低于某个任意阈值则添加一个新任务。用你的设计,这是不可能的。您可以与发现者或执行者交谈,但不能与 FinderAndExecutor 交谈。在这种情况下,您只需要直接与 TaskService
对话,完全破坏了接口的意义。
假设 Java 的 Collection
接口被分解成这些接口:
interface Clearable {
void clear();
}
interface Containable<T> {
boolean contains(T obj);
}
interface Sized {
int size();
}
interface Removable<T> {
boolean remove(T obj);
}
// etc...
如果大小 > 100,现在你需要做一些清除的事情。你使用什么类型?
public static void clearIfTooBig(??? collection) {
if (collection.size() > 100) collection.clear();
}
你可以用类型交集来做一些事情,但大多数人不知道它,你的方法签名会很笨重。
我认为,您的方法是跟上 SOLID 的好方法。
在您当前的示例中,我建议您在必要时将接口捆绑在一起。我建议您执行以下操作,将 TaskExecutor 和 TaskSchedular 分组到一个名为 TaskService 的新接口中。
interface TaskService extends TaskSchedular<String>, TaskExecutor<String> {}
并且您的class可以实现TaskService接口。我认为这种方法有利于组合而不是继承。
public class TaskServiceImpl implements TaskService { ... }
如何申请ISP没有统一的规则。这一切都是为了为客户制作有用的抽象并找到合适的平衡点。如果所有客户端都同时使用操作 A 和 B,并且这些操作具有内聚性,那么通过分离行为就没有真正的价值。
过于细化可能会迫使客户依赖于同一概念的多个“视图”来实现他们的目标,而另一方面,过于粗略会迫使客户依赖比他们需要的更多的东西.
但是,如果您最终只是将细粒度的接口捆绑在一个更大的外观中并且仅仅依赖于外观,那么您的粒度接口在任何方面都没有帮助。
除非你正在构建一个必须从一开始就具有灵活性的库,否则我建议让抽象根据实际使用情况自行出现,而不是默认使用一个方法接口并在你进行时进行重构.
找到合适的域抽象通常是一个迭代过程,很少从一开始就完成。例如,Java 设计师未能及早意识到他们应该为列表提供一个只读界面。