如何正确设计许多子类的组合?

5

我有一个父类 - 产品(Product)

public abstract class Product {}

还有3个子类继承了它:

  1. public class Vinyl extends Product {}

  2. public class Book extends Product {}

  3. public class Video extends Product {}

所有子类都使用特定的实现覆盖了 preview() 方法。 现在,我有一个新的设计需求: 我需要定义一个 组合 项,其中包括 vinylbook,并且还具有一个 preview() 方法 (这是 vinyl 和 book 的组合 )。指令中说我可以创建 Interface\ class field member 或任何我想要支持的实现来支持它,但我不确定具体如何做。

新设计是否应该使用 继承 实现,还是应该改变当前的设计?


1
使用组合而非继承。 - Polygnome
1
这是一个设计问题,因此有多个正确答案。只给我们4个类而没有任何细节(除了extends)会使我们更难给出好的答案。告诉我们在您的领域中定义产品的是什么,以及Vinyl / Book在具体情况下是什么。 - Pedro David
“组合优于继承”这句话很容易说,而且通常是正确的,但要应用它可能会有一点困难,仅凭这么多信息,我们无法确定最佳选项。 - Pedro David
一个 vinylBook 看起来像是一个 Book 对象的实例,除非你提供更多的细节。 - OneCricketeer
1
@Nimrod 现在您已经澄清了您的意图,我为您提供了一个答案。解决方案是使用“组合”模式。 - Chetan Kinger
显示剩余6条评论
2个回答

5
新设计可以纯粹地采用继承来实现。但是真正的问题在于您将拥有太多的子类。您可以轻松地创建一个名为 VinylBook 的 Product 子类并完成它。但是,如果您不得不引入另一个组合,比如 VinylVideo,那么您也必须创建一个新的子类,依此类推。解决方案是同时使用继承和组合。组合模式是这个问题的良好解决方案。根据维基百科的说法:
“组合模式描述了一组对象应该像单个对象实例一样被处理。实现组合模式让客户端统一地处理单个对象和组合。”
让我们从定义一个 CompositeProduct 开始。
public class CompositeProduct` extends Product {
     private List<Product> products;

     public CompositeProduct(List<Product> products) { this.products = products }

     public String preview() {
          String previewText = "";
          for(Product product : products) { previewText+=product.preview(); }
          return preview;

     }
}

您现在拥有一个复合产品,表现为一个单一的产品,使您可以创建组合产品,而无需为每个组合创建新的子类。
以下是如何即时创建产品的示例:
Book book = new Book();
Vinyl vinyl = new Vinyl();
List<Product> products = new List<>();
products.add(book);
products.add(vinyl);
CompositeProduct vinylBook = new CompositeProduct(products);

如果您想在产品运行时添加额外的行为,您也可以查看装饰器模式。


这是一个不错的替代方案,但如果没有更多信息,这似乎只是避免设计问题而不是改进设计的方法。 - Pedro David
@PedroDavid 我不完全明白你的意思。 - Chetan Kinger
将“Smashing”产品组合在一起很容易,但是要用相应的逻辑来破解它们比你刚才展示的要难得多。但说实话,这是根据提供的细节所能得出的最佳答案。 - Pedro David
@PedroDavid 在我看来,这类问题只有有限的解决方案。是的,一些细节可能会改变方法,但组合模式和装饰器模式是正确解决此问题的可靠选项。 - Chetan Kinger
我在我的答案中详细阐述了我的问题,并提供了一个替代方案。我希望能得到您的反馈。 - Pedro David

0

我想提供一个替代方案,因为我对其他解决方案不满意。(别误会,它很简单直接,而且组合概念是一个非常好的学习方式,用户还提到了装饰器模式非常有用)。

我对那个解决方案的问题在于你只是给出产品(无论是什么),然后你能做什么呢?

让我们想象一下

public abstract class Product {
    abstract void preview();
}

现在你的CompositeProduct非常通用,只能实现类似于这样的东西

void preview(){
    for(Product product : products) product.preview();
}

我敢打赌这不是你想要的。

我的替代方案是通过接口使用组合。由于领域的细节我无法提供更多详细信息,因此我将给出一个简单的例子。

什么是书?让我们想象一些可读的东西。

public interface Readable { void read(); }

public class Book extends Product implements Readable {}

什么是Vinyl?可展示的

public interface Showable { void show(); }

public class Vinyl extends Product implements Showable {}

现在BookVinyl就是这样

public class BookVinyl extends Product implements Readable, Showable { }

这会给你什么?现在,BookVinyl可以像一本书一样阅读,并像黑胶唱片一样展示。预览呢?它可以展示然后阅读或者其他操作。

类的数量确实可能令人望而生畏。但是,当有人创建一个 VinylAudio 时,它不应该只是 Vinyl + Audio,它应该有自己的逻辑,因此您无论如何都需要一个不同的类。你的回答很干净,真的很通用,但我认为太通用了。话虽如此,根据提问者提供的信息,唯一能得出更好选择结论的人就是提问者本人。 - Pedro David
1
这个解决方案的真正问题在于需要为每个新组合创建大量子类。仅有3种产品类型,就可以有如此多的不同组合(VinylBook、VinylVideo、Book、Video和Vinyl)。再引入另一种产品类型,你就会得到更多的类(Audio、VinylAudio、AudioBook、VinylAudioBook)。更不用说还要添加另一个名为Listenable的接口了。 - Chetan Kinger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接