我已经将实际代码简化为说明这一点的最小例子。请原谅缺乏制定者/吸气者等。
想象一下,我们有几个客户按顺序浏览的网页。这里的用例是: -
这个问题集中在两种交付方式上。建模如下:
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
会引用PostalDeliveryDetails
或EmailDeliveryDetails
的实例。
总之,当用户确认他们的信息时:
// 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实例时不知道传递类型。无论如何,这对工厂没有帮助。
这个空接口好吗?有没有更好的方法来设计此代码?
答案 0 :(得分:1)
对我来说,EmailDeliveryDetails
和PostalDeliveryDetails
之间的(数据)差异归结为地址。所以我的第一直觉就是将这些数据提取到一个单独的Address类中。然后,您可以决定使用带有emailAddress和streetAddress的可选字段的单个地址,或者具有电子邮件和邮政地址的不同子类的类层次结构。
我更喜欢带有可选字段的单一类,因为它使用起来更干净,而且对我来说实用性胜过概念“纯度”。
基于以下评论链:
当特定类的属性(因此可能的状态)之间没有重叠时,尝试以多态方式处理它们是非常尴尬的。如果一个人不打算在其中加入很多功能,那么在继承一些通用接口的不同类中处理它们就更加困难了(正如你所指出的那样)。 OTOH在概念上所有这些都是某种地址数据,所以它可以在一个类中处理。
请注意,大部分内容都是猜测 - 如果没有更详细的信息,很难对您的设计进行推理。
你是对的,我不打算在
EmailDeliveryDetails
和PostalDeliveryDetails
中放置太多行为。在实际应用程序中,这些详细信息将保留在数据库中,并将详细信息发送到外部系统。
好的,所以你实际上并没有多态地对待它们。您只需要一个通用的“句柄”来访问要保留的不同数据位。并且持久性通常不关心多态性和接口。在这种情况下,您的类继承自空接口是可以的。
答案 1 :(得分:1)
你的两个实现类都是某种描述符,它们实际上并没有做任何事情。对我来说,在这种情况下使用类层次结构感觉更清晰(带有两个子类的抽象基类:EmailDeliveryDetails和PostalDeliveryDetails),即使基类是空的。 EmailDeliveryDetails 是 DeliveryDetail而不是Delivery实现,除非它实现了交付方法。
答案 2 :(得分: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执行某些操作。
可选地,进程和processWith方法可以返回布尔值以指示是否处理了细节。
同样,在这种情况下,这种方法可能不必要地复杂,但它解决了处理最后提出的未知交付类型的问题。
答案 3 :(得分:1)
EmailDeliveryDetails
和PostalDeliveryDetails
只有“交付”的概念,但他们的行为和知识都不相同。在这种情况下,共同的祖先DeliveryDetails
不适合。
就个人而言,我会从PurchaseData
创建一个继承树。