【问题标题】:interface segregation SOLID principle in Spring servicesSpring服务中的接口隔离SOLID原理
【发布时间】:2023-04-04 18:17:01
【问题描述】:

我尽可能精简我的服务接口,通常它们是@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 实现这两个接口是一个好习惯吗?还是我应该为每个接口设置一个单独的类?

【问题讨论】:

  • 接口隔离原则对一个类实现多个接口没有问题。单一职责原则可能有问题,但这是一个不同的问题。
  • 我根据他们的用户需求(其他依赖服务)设计接口,如果他们通常需要methodA()和methodB(),我会将这两个方法放在同一个接口中。关于它们的实现,如果接口实现需要相同的依赖项,我在实现这些服务接口时所做的就是在同一个类中实现它们。这是一个好的经验法则吗?现在我认为我没有像这样完成“单一责任原则”,我应该为每个接口创建一个类吗?
  • 这不是一个简单的话题。我在 SOLID 上找到的样本非常简单。我希望在 SOLID 之后看到好的春季服务样本。关于这个主题有什么好的材料参考吗?

标签: java spring solid-principles


【解决方案1】:

如果您创建的每个接口都只有一个方法,那么您做错了。

假设某物想要查找一个时期内的任务数量,如果数量高于或低于某个任意阈值,则添加一个新任务。用你的设计,这是不可能的。您可以与 finder 或 executor 交谈,但不能与 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...

现在您需要在 size > 100 时清除。您使用什么类型?

public static void clearIfTooBig(??? collection) {
    if (collection.size() > 100) collection.clear();
}

你可以用类型交集做一些事情,但大多数人不知道,你的方法签名会很笨重。

【讨论】:

  • 我添加了代码注释。也许我的示例代码不是最好的。另一个示例可能是:我希望有 findTask、createTask、executeTask、scheduleTask、printTask 等操作,目前我将为每个操作都有一个独立的接口,所以如果另一个服务需要打印一个任务,它只有使用单一方法对 TaskPrinter 的依赖。实现所有这些以某种方式相关的接口的最佳实践是什么?一个 \\@Service 类实现所有?还是多个 \\@Services 类?
  • @zeugor 彻底检查了我的答案
【解决方案2】:

在如何应用 ISP 方面没有通用规则。这一切都是为了为客户制作有用的抽象并找到适当的平衡。如果所有客户端都一起使用操作 A 和 B,并且这些操作具有凝聚力,那么通过隔离行为就不会获得真正的价值。

过于细化可能会迫使客户依赖同一概念的多个“视图”来实现他们的目标,而另一方面,过于粗糙会迫使客户依赖比他们需要的更多的东西。

但是,如果您最终只是将细粒度接口捆绑在一个较大的外观中,并且只是依赖外观,那么您的粒度接口就没有任何帮助。

除非您要构建一个必须从一开始就具有灵活性的库,否则我建议让抽象根据实际使用情况自行出现,而不是默认使用一个方法接口并随时重构。

找到合适的域抽象通常是一个迭代过程,很少从一开始就完成。例如,Java 设计人员未能及早认识到他们应该有一个只读的列表接口。

【讨论】:

    【解决方案3】:

    我认为,您的方式是跟上 SOLID 的好方法。

    我建议您在当前示例中在必要时将接口捆绑在一起。我建议您执行以下操作,将 TaskExecutor 和 TaskSchedular 组合到一个名为 TaskService 的新接口中。

    interface TaskService extends TaskSchedular<String>, TaskExecutor<String> {}
    

    并且你的类可以实现TaskService接口。我认为这种方法有利于组合而不是继承。

    public class TaskServiceImpl implements TaskService { ... }
    

    【讨论】:

    • “将接口捆绑在一起”有什么好处?与您的 TaskService 示例一样。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-19
    • 1970-01-01
    • 1970-01-01
    • 2022-11-09
    相关资源
    最近更新 更多