0

我已将真实代码简化为说明这一点的最小示例。请原谅缺少 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. 如果用户返回一个页面,我们可以向他们展示他们之前输入的内容。在用户确认并交付图书后,引用ordeliveryDetails的一个实例。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 实例时不知道交付类型。无论如何,这对工厂没有帮助。

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

4

4 回答 4

1

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

于 2012-04-20T07:58:13.287 回答
1

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

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 方法可以返回布尔值以指示是否处理详细信息。

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

于 2012-04-20T07:58:18.557 回答
1

To me the (data) differences between EmailDeliveryDetails and PostalDeliveryDetails boil down to addresses. So my first instinct would be to extract that data into a separate Address class. Then you may decide to have a single Address with optional fields for emailAddress and streetAddress, or a class hierarchy with distinct subclasses for email and postal addresses.

I would prefer the single class with optional fields, as it is cleaner to use, and to me practicality trumps conceptual "purity".

Update

Based on the comment chain below:

When there is no overlap between the properties (thus the possible states) of specific classes, it is very awkward to try to handle them polymorphically. And if one doesn't intend to put much functionality into them either, it is even more difficult to handle them in distinct classes inheriting some common interface (as you too noted). OTOH conceptually all of this is some sort of address data, so it can be handled in one class.

Note that most of this is speculation though - it is difficult to reason about your design without more detailed information.

You are right in that I don't intend to put much behaviour in EmailDeliveryDetails and PostalDeliveryDetails. In the real application, these details would be persisted in the database and details sent through to outside systems.

Ah OK, so you aren't actually going to treat them polymorphically. You only need a common "handle" to access different bits of data to be persisted. And persistence typically doesn't care about polymorphism and interfaces anyway. In this case having your classes inherit from an empty interface is fine.

于 2012-04-20T07:35:59.807 回答
1

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

就个人而言,我会从PurchaseData.

于 2012-04-21T22:35:37.580 回答