【问题标题】:Share code (which uses an abstract operation) between two classes在两个类之间共享代码(使用抽象操作)
【发布时间】:2011-03-06 03:24:32
【问题描述】:

我正在用 Java 开发一个服务器应用程序。服务器需要两种类型的服务器类。这些类有一些共同的方法,这些方法中的代码完全相同。所以我创建了一个包含所有共享代码的抽象超类,两个类都继承了它。但是,代码的某些部分需要子类进行精确化。我的意思是超类“依赖”子类方法。

这是我的意思的纯化示例:

public abstract class AbstractServer
{
    public void loadConfig(String configPath)
    {
        //Load the configuration file.

        //This code is exactly the same for subclasses.
    }

    public void startRMI(int port)
    {
        //Create an empty RMI registry.
        //This part also need to be identical.

        //Here' where the superclass "rely" on subclasses.
        fillRegistry(); //Call the method overwritten by subclasses.
    }

    /**
    Bind remote objects in the RMI registry
    */
    protected abstract void fillRegistry(); //This method will be overriten by subclasses.
}

我觉得这样做真的很糟糕,但我找不到另一种更清洁的方法。

所以,我想要的是一些关于如何让它变得更好的建议。

谢谢,抱歉我的英语不好。

【问题讨论】:

  • 对于初学者来说,我会让 fillRegistry() 受保护,除非它需要在外部调用并且你应该在 SuperClass 的类声明中添加抽象(我觉得最好将其命名为 ServerBase 或 AbstractServer 或其他东西像那样)
  • 并且超类必须是抽象的,但除此之外并使方法受到保护,我认为设计没有任何问题。
  • 好的,因为我找到了一篇关于 Swing API 的文章。而且作者说JComponent依赖子类的“paintComponent”方法是一件坏事。所以我想知道这样做是否有那么糟糕。
  • 总体上没问题,但你应该将 startRMI 方法设为 final 以防止行为在子类中被覆盖

标签: java oop inheritance software-design


【解决方案1】:

你的方法很好。坚持下去,伙计。

我觉得你理解它的“哲学需要”。只要基类是抽象的,基类“依赖”子类就可以了。它知道此时必须注册一些东西,但它对于究竟要注册什么却没有丝毫线索。因此,高级过程在基类中编码,并带有可以由派生类插入的“漏洞”。高级过程和“洞”本身的位置很有价值,这证明了基类的实现是合理的。派生类只是遵循“差异编码”的基本面向对象原则并填补“漏洞”。

【讨论】:

    【解决方案2】:

    在您编辑后看起来对我来说是正确的(假设您为了可读性而忽略了抛出异常的部分):)

    所有三种方法都需要在真实案例中引发异常。

    【讨论】:

    • 是的,我让这个例子没有无关紧要的东西,便于理解。
    【解决方案3】:

    超类被子类继承。您可以在超类中编写您想要通用的方法并保持不变。对于您希望它被子类覆盖的代码的另一部分,请在超类中定义其他方法集。也在子类中编写方法。当你从子类调用方法时,你可以调用超类方法的

    简而言之,你必须在子类中编写方法来覆盖超类的方法。

    【讨论】:

      【解决方案4】:

      我还会确保您的超类实际上是抽象的。在这个 sn-p 中它不是。总的来说,看起来不错。

      还可以考虑在超类中声明扩展它的类也需要的任何实例变量。

      【讨论】:

        【解决方案5】:

        首先,要求在抽象(基)类中实现子类并没有错。 IMO,这只是不应该被滥用的东西。但是,如果我不得不避免它,我会让 ServerClass not 抽象,并定义它的每个方法。相反,我会创建 RegistryFactory 类并将它们传递给 ServerClass :

        class ServerClass {
           public void startRMI(int port, RegistryFactory rf) {
              // ...
              rf.fillRegistry(this);
           }
        }
        
        
        interface RegistryFactory {
           /**
            * Implement this method
            */
           public void fillRegistry(ServerClass server);
        }
        
        public class RMIRegistryFactory implements RegistryFactory {
           public void fillRegistry(ServerClass server) { /* ... */ }
        }
        

        或者类似的东西。

        【讨论】:

        • 但这不是 Poltergeist 反模式吗?我实例化一个“RMIRegistryFactory”只是为了填充服务器,然后它被垃圾收集了吗?因为我喜欢你的想法,它真的会减轻我的子类。
        【解决方案6】:

        您的方法很好,但需要一个简单的改进才能使其完美 - 使 startRMI() 方法 final:

        public final void startRMI(int port) {
            fillRegistry();
        }
        

        这样您将防止有人覆盖它(可能是因为不知道应该重用 startRMI() 中的所有内容,并且只需要自定义 fillRegistry())。

        您的解决方案通常与template method design pattern 匹配:

        模板方法是超类中的方法,通常是抽象的 超类,并根据 a 定义操作的骨架 高级步骤的数量。这些步骤本身由 与模板方法在同一类中的其他辅助方法。

        辅助方法可以是抽象方法,在这种情况下 子类需要提供具体的实现,或者钩子 方法,在超类中有空的主体。 子类可以 (但不是必须)通过覆盖 钩子方法s。模板方法的目的是定义 操作的整体结构,同时允许子类 改进或重新定义某些步骤。 (维基百科)

        鉴于上述情况,方法startRMI() 是一种模板方法,它通过使用许多高级步骤来定义操作的框架(在您的情况下,它只是一个步骤,但这没有什么区别)。您示例中的方法 fillRegistry() 是一个高级步骤 - 它在超类中定义为抽象方法,并在超类中具有具体实现。

        另一方面,如果您要在子类中覆盖方法startRMI(),这将不再可行。这就是为什么你应该将它设为final 以避免混淆 - 这样创建子类的人会知道他必须实现fillRegistry()(因为它是抽象的)但不应该更改startRMI 的实现(因为它是最终的)。

        既然是常用的设计模式,这个方案好不好我一点都不担心,很多人都是这样做的,懂设计模式的人都会认出来,我觉得很自然即使对于不了解设计模式的开发人员也是如此。

        【讨论】:

          猜你喜欢
          • 2019-07-03
          • 1970-01-01
          • 2014-07-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多