【问题标题】:Use of interface with no methods使用没有方法的接口
【发布时间】:2012-05-01 18:58:15
【问题描述】:

我已将实际代码简化为说明这一点的最小示例。请原谅缺少 setter/getter 等。

假设我们有几个网页,客户按顺序浏览。这里的用例是:-

  • 用户选择他们想要的书
  • 用户选择是否希望通过邮寄或电子邮件发送信息以及相关详细信息
  • 系统完成订单

这个问题的重点是两种交付方式。这建模如下:

interface DeliveryDetails
{
    // Implementations of this have nothing in common other than that they
    // fulfil the same logical role.
}

class EmailDeliveryDetails implements DeliveryDetails
{
    String emailAddress;  // It really has a constructor and getter, I promise.
}

class PostalDeliveryDetails implements DeliveryDetails
{
    String streetAddress;
    String Country;
}

现在,为了表示用户在浏览页面时输入的信息,我们有这个类:

class PurchaseData
{
    String title;
    DeliveryDetails deliveryDetails;
}

当用户浏览网页时,信息存储在PurchaseData 的实例中。如果用户返回一个页面,我们可以向他们展示他们之前输入的内容。在用户确认并交付图书后,deliveryDetails 引用了 PostalDeliveryDetailsEmailDeliveryDetails 的实例。

总之,当用户确认他们的信息时:

    // Some code in a factory
    if ( purchaseData.deliveryDetails instanceof EmailDelivery )
    {
        // construct a EmailDeliveryService( purchaseData, SMTP details, etc ... )
    }
    if ( purchaseData.deliveryDetails instanceof PostalDelivery )
    {
        // construct a PostalDeliveryService( purchaseData, etc ... )
    }
}

困扰我的是Delivery接口没有方法。

这是由于电子邮件和邮政递送之间的差异所造成的。

我不认为DeliveryDetails.deliver() 是一个好方法,因为这会强制实现静态获取诸如 SMTP 服务器地址之类的东西。这会混淆关注点(管道与用户输入的信息)。

如果您必须存储任意类型的内容,泛型可能会很有用。无法使用泛型 (PurchaseData<T extends Delivery>),因为在创建 PurchaseData 实例时不知道交付类型。无论如何,这对工厂没有帮助。

这个空界面可以吗?有没有更好的方法来设计这段代码?

【问题讨论】:

  • 我没有看到空接口有任何问题。这样的界面我见过很多次了,用你的方法看起来还不错。

标签: oop marker-interfaces


【解决方案1】:

对我而言,EmailDeliveryDetailsPostalDeliveryDetails 之间的(数据)差异归结为地址。所以我的第一直觉是将这些数据提取到一个单独的地址类中。然后,您可能会决定使用单个地址,其中包含 emailAddress 和 streetAddress 的可选字段,或者使用具有不同子类的电子邮件和邮政地址的类层次结构。

我更喜欢带有可选字段的单个类,因为它使用起来更简洁,而且对我来说,实用性胜过概念上的“纯度”。

更新

基于下面的评论链:

当特定类的属性(因此可能的状态)之间没有重叠时,尝试多态地处理它们是非常尴尬的。而且,如果一个人也不打算在它们中添加太多功能,那么在继承某些通用接口的不同类中处理它们就更加困难(正如您也指出的那样)。 OTOH 从概念上讲,所有这些都是某种地址数据,因此可以在一个类中处理。

请注意,这大部分都是推测 - 如果没有更详细的信息,很难对您的设计进行推理。

您说得对,我不打算在EmailDeliveryDetailsPostalDeliveryDetails 中添加太多行为。在实际应用中,这些细节将被持久化在数据库中,并通过发送到外部系统。

嗯,好的,所以你实际上并不会多态地对待它们。您只需要一个通用的“句柄”来访问要持久化的不同数据位。无论如何,持久性通常不关心多态性和接口。在这种情况下,让您的类从空接口继承就可以了。

【讨论】:

  • EmailDeliveryDetails 和 PostalDeliveryDetails 之间的私有成员没有重叠,将它们放在同一个类中似乎很麻烦。随着更多选项的添加,情况会变得更糟。
  • @WW。正是因为没有重叠(如果我理解正确,您也不打算在它们中添加太多功能),很难在继承某些通用接口的不同类中处理它们(正如您也指出的那样)。 OTOH 从概念上讲,所有这些都是某种地址数据,因此可以在一个类中处理。
  • @WW。您没有向我们展示您如何在应用程序中使用 DeliveryDetails - 具体子类包含哪些逻辑或方法?谁在使用这些?用这么少的信息很难推断出你的设计。
  • 你说得对,我不打算在 EmailDeliveryDetails 和 PostalDeliveryDetails 中添加太多行为。在真正的应用程序中,这些细节将被持久化在数据库中,并将细节发送到外部系统。
  • @WW.,好的,在这种情况下,让它们从空接口继承就可以了。
【解决方案2】:

像下面概述的那样的双重调度方法可以工作,但在这种情况下可能会过度杀伤力。我会提到它只是为了给你一些选择......

class DeliveryService {
    // base class doesn't handle anything
    process(EmailDelivery details) {}
    process(PostalDelivery details) {}    
}

class EmailDeliveryService extends DeliveryService {
    process(EmailDelivery details) { /* handle */ }
}

class PostalDeliveryService extends DeliveryService {
    process(PostalDelivery details) { /* handle */ }
}

interface DeliveryDetails {
    processWith(DeliveryService service);
}

class EmailDeliveryDetails implements DeliveryDetails {
    processWith(DeliveryService service) { service.process(this); }
}

// try all services (of unknown type) on the given details (also of unknown type)
List<DeliveryService> services = configureServices();
DeliveryDetails details = getDetails();
for (DeliverySerivce service : services) details.processWith(service);

如果添加了新类型的 DeliveryDetails(可能不太可能......),您必须更新 DeliveryService(添加一个空的流程方法)并添加一种新类型的 DeliveryService,它实际上对新类型的 DeliveryDetails 执行某些操作。

或者,process 和 processWith 方法可以返回布尔值以指示是否处理详细信息。

同样,在这种情况下,这种方法可能过于复杂,但它解决了处理您最后提出的未知交付类型的问题。

【讨论】:

  • 感谢您的想法,但我认为我不会选择此选项。另外,您如何想象配置(SMTP 地址等)会到达 EmailDeliveryService?
  • 为简洁起见,我省略了 configureServices() 方法,但它当然会执行类似 services.add(new EmailDeliveryService(options));或者实际上,交付服务将在系统的其他部分设置一次,然后在需要的地方进行访问。
  • 谢谢 - 我明白了。我认为将 DeliveryDetails 传递给服务在概念上更好,而不是相反。这给我留下了空界面,但也许没关系。
【解决方案3】:

您的两个实现类都是某种描述符,它们实际上并没有做任何事情。对我来说,在这种情况下使用类层次结构(具有两个子类的抽象基类:EmailDeliveryDetails 和 PostalDeliveryDetails)感觉更干净,即使基类为空。 EmailDeliveryDetails 是一个 DeliveryDetail而不是Delivery实现,除非它实现了一个deliver方法。

【讨论】:

  • 有趣:我对抽象类与接口的讨论不多。为简洁起见,我省略了这些类实际上扩展 ReflectionToString 以获取某些行为并且不能在 java 中多重继承。 (通常我更喜欢作曲,但在那种情况下我猜有点懒惰。)
【解决方案4】:

EmailDeliveryDetailsPostalDeliveryDetails 的共同点只有“交付”的概念,但他们的行为和知识都不相同。共同祖先DeliveryDetails不适合这种情况。

就我个人而言,我会从PurchaseData 创建一个继承树。

【讨论】:

  • 那么,EmailPurchaseData 和 PostalPurchaseData?我不知道最初要创建哪个对象,因此这意味着稍后在通过向导的流程中重新创建一个对象。在实际系统中,这发生在几个不同的事情上,所以我最终会得到很多组合。
  • 多考虑一下这个问题,我认为你甚至不需要两个类。在任何情况下,如果用户希望以这种方式交付,您将捕获电子邮件地址和可选的邮政地址。多态性是一种矫枉过正。只需使用一个简单的条件来确定交付方式。
猜你喜欢
  • 2014-01-19
  • 2014-06-02
  • 2015-03-28
  • 2017-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-07
相关资源
最近更新 更多