【问题标题】:Working with subclasses that have different method names without violating SOLID principles在不违反 SOLID 原则的情况下使用具有不同方法名称的子类
【发布时间】:2018-06-30 00:26:10
【问题描述】:

//接口

public interface Singer{
    void sing();
}
public interface SongWriter{
    void writeSong();
}

//实现

public class PureSinger implements Singer{
    void sing(){}
}
public class SingerSongWriter implements Singer, SongWriter{
    void sing(){}
    void writeSong(){}
}

//客户端代码

void methodA(){
    Singer objPureSinger = new PureSinger();
    Singer objSingerSWer = new SingerSongWriter();

    doSomething(objPureSinger);
    doSomething(objSingerSWer);
}

public void doSomething(Singer obj){
    obj.sing();
    obj.writeSong();    //<--- this does not work.
}

为了实现这种类型的代码,我应该如何设计类结构?

【问题讨论】:

  • 目标是用一个单一的基接口/抽象类对象来控制 PureSinger 和 SingerSongWriter 对象。
  • 根据你的设计,你应该使用SingerSongWriter作为doSomething()的参数
  • 你希望doSomething(objPureSinger);做什么?
  • 那样,我无法将 objPureSinger 传递给 doSomething()
  • @FedericoPeraltaSchaffner 再一次,OP 不希望歌手写歌。相反,OP 希望歌手唱歌,歌曲作者写一首歌。他还想要一个能唱能写歌的唱作人。需要使用单个接口来支持此功能,而 doSomething 不会真正担心传递给它的具体类型。我同意 Singer 接口名称令人困惑,它可以重命名为 ArtistdoSomething 可以采用 Artist 引用。希望能澄清这个问题。 (我已经在我的回答中介绍了解决方案)

标签: java oop design-patterns polymorphism mixins


【解决方案1】:

为了实现这种类型的代码,我应该如何设计类结构?

首先定义一个名为Artistinterface,它有一个名为perform 的方法。接下来,定义两个子类,即SingerSongWriter,每个子类都实现perform,并分别包含唱歌和写歌的代码。

以下两种设计模式可以很好地支持您的用例:

  1. Decorator 设计模式:在您的情况下,Artist组件SingerSongWriter具体组件ArtistDecorator 是你的 Abstract Decorator 类。使用ArtistDecoratorSinger 包装成SongWriterdoSomething 方法接受一个 Artist 参数,并传递最终装饰的 Artist 对象,该对象将 Singer 包装到 SongWriter 中。
  2. Composite 设计模式:在您的情况下,Arist组件CompositeArtistCompositeSingerSongWriterLeafConcrete Component 类。 doSomething 方法采用 Artist 参数。将CompositeArtist 的实例传递给它(IS-A Artist)和doSomething 只是调用perform,它在内部迭代所有Artist 实例调用它们的perform 方法逐个。

【讨论】:

  • @FedericoPeraltaSchaffner 关于您对复合模式不适合此处的评论:在复合模式的情况下,CompositeAritst 将有一个 Singer 实例和一个 SongWriter 实例一个数组/列表。总之,这将形成一个歌手-歌曲-作者的复合体。您能否详细说明您的意思是没有复合材料,只有单叶对象?
  • 嗯...我的意思是复合是一种用于建模嵌套 has-a / has-many 关系的模式,例如屏幕有很多窗口,每个窗口都有很多小部件。在这里,您想使用它来模拟许多可能的行为,将每个可能的动作(唱歌和写歌)变成一个组件。这是对这种模式 IMO 的滥用,也是一个过于复杂的解决方案。尤其是因为SingerSongWriter IS-A 歌手也写歌,或者歌曲作者也唱歌。
  • 不过,我觉得装饰器的方法在这里很合适,所以+1
  • @FedericoPeraltaSchaffner 如果我们以 Singer 和 SongWriter 为例,那么是的,装饰器更合适。上面的答案旨在涵盖在面临单个界面需要支持多种行为的情况时可能的解决方案。它旨在回答问题标题(假设给出的示例是为了支持问题标题)。
【解决方案2】:

要快速回答问题doSomething 调用sing()writeSong() 方法。只有你的 SingerSongWritter 类定义了这两个,所以是唯一可以同时做这两个的类。 objPureSinger 没有定义 writeSong 是什么,因此不知道调用时要做什么。

如果你想保持这个结构,你可以做一件事来解决这个问题,即在两个类中实现一个名为do 的方法。在您的PureSinger 类中,do 只是简单地调用sing(),但在您的SingerSongWriter 类中它同时调用sing()writeSong()。最后在你的doSomething 方法中调用obj.do();

【讨论】:

  • 我认为它违反了 SOLID 的 LSP。纯歌手不会写歌。
  • 如果您想让 dosomething 方法对两者都有效,请尝试新建议,它只需在两个名称相同的类中添加一个方法。
【解决方案3】:

您可以根据自己的业务需要设计课程。而且,很明显会在

处得到编译时错误
public void doSomething(Singer obj){
    obj.sing();
    obj.writeSong();    //<--- compiler will complain here
}

因为,Singer 没有 writeSong 方法。根据您当前的设计:

  1. 所有 Singer 仅 CAN sing
  2. 所有 SongWriter 仅限 CAN writeSong

但是,以上都不能同时执行。所以,很明显doSomething 直到

doSomething (SingerSongWriter obj)

因为,只有SingerSongWriter 可以同时执行。

【讨论】:

    【解决方案4】:

    您不应该假设在 doSomething() 中使用 .writeSong(),因为您将参数作为 Singer 传递,而且我们知道 Singer 只有一个名为 .sing() 的方法。这里有一些选项。

    1. 您应该考虑 doSomething() 在业务逻辑意义上的实际作用。也许我们可以不用调用 .writeSong(),因为传入的参数并不总是可以转换为 SongWriter。
    2. 考虑使用基类(例如抽象类或具体类)来实现 SingerSongWriter 以将其作为参数数据类型传入。
    3. 如果您正在寻找快速修复以使您的代码运行,请参见下文。

    快速修复: 您需要首先查看 obj 是否为 instanceOf SongWriter,因为传入的参数是 Singer 类型,它没有名为 .writeSong() 的方法。

    public void doSomething(Singer obj){
        obj.sing();
        //obj.writeSong();    //<--- this does not work.
        if (obj instanceOf SongWriter) songWriter.writeSong();
    }
    

    这是一个快速修复。您可以从下面的帖子中看到,您应该最后使用 instanceOf。

    【讨论】:

    • 未来可能会添加新的接口或新的 Singer 的具体类,因此“if (obj instanceOf) ...”违反了 SOLID 的 OCP。我需要一个能够实现 OCP 的设计。
    • 所以你有两个选择。首先是创建一个基类来实现这两个方法并将其作为参数数据类型传递。另一种选择是不在 doSomething() 方法中调用 .writeSong() (重新审视您的业务需求)。
    猜你喜欢
    • 1970-01-01
    • 2013-01-01
    • 2012-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-25
    • 1970-01-01
    • 2011-08-20
    相关资源
    最近更新 更多