16

我有一个类结构,我希望基类中的某些方法可以从直接从基类派生的类访问,而不是从派生类派生的类。根据 Java 语言规范,可以覆盖继承方法的访问规范以使它们更公开,但不能更私有。例如,这是我需要做的事情的要点,但这是非法的:

// Defines myMethod
public class Base {
    protected void myMethod() {}
}

// Uses myMethod and then hides it.
public class DerivedOne extends Base {
    @Override
    private void myMethod();
}

// can't access myMethod.
public class DerivedTwo extends DerivedOne {

}

有没有办法做到这一点?

编辑解释我为什么要这样做:

在这种情况下,类结构是数据处理和导入结构。它读入并解析充满表格数据的文本文件,然后将它们存储在数据库中。

基类是管理其数据库处理部分的基表类。其中包含相当数量的功能,这些功能对所有表类型都是通用的——因为一旦它们在数据库中,它们就会变得统一。

中间类特定于被解析文件中的表类型,具有表解析和导入逻辑。它需要访问一些基类的数据库访问功能。

顶级类是特定于表的,只是以父类可以理解的方式初始化表的布局。此外,基类的用户不需要查看或访问中间类所做的数据库特定功能。本质上,我只想将这些函数展示给基类之上的一层,而不是其他任何一层。

我问是因为,虽然我作为示例发布的代码是非法的,但可能还有其他方法可以达到同样的目的。我问有没有

也许隐藏是错误的表达方式——我真正需要做的是将一些应该对基类私有的功能暴露给层次结构中上一层的类。隐藏可以做到这一点 - 但我可以看到隐藏将是一个问题。还有另一种方法可以做到这一点吗?

4

8 回答 8

17

我认为您提出的问题的本质暴露了您的对象模型的概念问题。您试图将各种单独的职责描述为“是”关系,而实际上您应该做的是描述“具有”或“使用”关系。您想对子类隐藏基类功能这一事实告诉我,这个问题实际上并没有映射到三层继承树上。

听起来您在描述一个经典的 ORM 问题。让我们再看一遍,看看我们是否可以将它重新映射到严格的“是”继承之外的其他概念,因为我真的认为您的问题不是技术问题,而是概念问题:

你说:

基类是管理其数据库处理部分的基表类。其中包含相当数量的功能,这些功能对所有表类型都是通用的——因为一旦它们在数据库中,它们就会变得统一。

这可能更清楚,但听起来我们有一个类需要管理数据库连接和常见的数据库操作。在Single Responsibility之后,我想我们已经完成了。你不需要扩展这个类,你需要把交给一个需要使用它的功能的类。

中间类特定于被解析文件中的表类型,具有表解析和导入逻辑。它需要访问一些基类的数据库访问功能。

这里的“中产阶级”听起来有点像Data Mapper。这个类不需要扩展前一个类,它需要拥有对它的引用,也许注入构造函数或作为接口的设置器。

顶级类是特定于表的,只是以父类可以理解的方式初始化表的布局。此外,基类的用户不需要查看或访问中间类所做的数据库特定功能。本质上,我只想将这些函数展示给基类之上的一层,而不是其他任何一层。

我不清楚为什么高级类似乎了解 db 模式(至少这就是“初始化表的布局”这句话对我的建议),但同样,如果前两个类之间的关系是封装("has a"/"uses a") 而不是继承 ("is a"),我认为这不是问题。

于 2009-12-14T23:42:58.323 回答
10

不,我不确定您为什么要引用规范,然后询问是否有任何方法可以与规范所说的相反...

也许如果你解释你为什么要这样做,你可以得到一些关于如何做的建议。

于 2009-12-14T17:13:17.240 回答
7

当覆盖一个方法时,你只能让它更公开,而不是更私有。我不知道你为什么用“一般”这个词

请记住,从最严格到最严格的顺序:

public<protected<default<private

是的,“ protected”是一个比default(当没有使用修饰符时)限制较少的访问修饰符,因此您可以覆盖将覆盖方法标记为的默认方法protected,但不能反其道而行之。

可以: 您可以用一个方法覆盖一个protected方法public

不能: 你不能public用一个方法覆盖一个方法protected

于 2009-12-14T17:15:07.797 回答
6

如果你这样做了,那么从 DerivedTwo 的角度来看,DerivedOne 将不是 Base。相反,你想要的是一个包装类

//Uses myMethod but keeps it hidden
public class HiddenBase {
    private final Base base = new Base();
    private void myMethod();
    public void otherMethod() {base.otherMethod();}
}

您无法通过这种方式访问​​基础的受保护方法......

于 2009-12-14T17:18:52.873 回答
4

您描述的内容接近protected访问类的用途,派生类可以访问,所有其他类都不能。

如果您从基类继承,您无法控制这可能会造成问题,您可以通过抛出异常使该方法无法被其他人访问,同时通过直接调用 super 使继承的代码可用于您的类,例如:

// Uses myMethod and then hides it.
public class DerivedOne extends Base {
    @Override
    public void myMethod() {
        throw new IllegalStateException("Illegal access to myMethod");
    }

    private void myPrivateMethod() {
        super.myMethod();
    }

}

编辑:回答您的阐述,如果我理解正确,您需要在中产阶级中定义的基类的上下文中指定行为。抽象的受保护方法对于从中间类派生的类来说是不可见的。

一种可能的方法是使用您需要在基类中抽象的方法定义一个接口,在基类中保留一个私有最终引用,并在构造中间类对象时提供对实现的引用。

该接口将在嵌套在中间类中的(静态?)中实现。我的意思看起来像:

public interface Specific {
    public void doSomething();
}

public class Base {
    private final Specific specificImpl;

    protected Base(Specific s) {
        specificImpl = s;
    }

    public void doAlot() {

         // ...

         specificImpl.doSomething();

         // ...
    }
}

public class Middle extends Base {

    public Middle() {
        super(new Impl());
    }

    private Impl implements Specific {

        public void doSomething() {

            System.out.println("something done");
        }
    }
}

public class Derived extends Middle {

    // Access to doAlot()
    // No access to doSomething()
}
于 2009-12-14T17:27:44.660 回答
3

继承有效,因为您可以在任何地方使用基类,也可以使用它的子类之一。行为可能不同,但 API 不同。这个概念被称为Liskov 替换原则

如果您能够限制对方法的访问,则生成的类将不会具有相同的 API,并且您将无法使用基类的实例替代派生类之一,从而否定继承的优势。

你真正想要完成的事情可以通过接口来完成:

interface IBase1 {
}

class Derived1 implements IBase1 {
  public void myMethod() {
  }
}

class Derived2 implements IBase1 {
}

class UseMe {
  public void foo(IBase1 something) {
     // Can take both Derived1 and Derived2
     // Can not call something.myMethod()
  }
  public void foo(Derived1 something) {
     something.myMethod();
  }
  public void foo(Derived2 something) {
    // not something.myMethod()
  }
}
于 2009-12-14T17:30:20.527 回答
3

这是可能的,但需要对包进行一些操作,并且可能会导致结构比您希望长期使用的结构更复杂一些。

考虑以下:


package a;

public class Base {
    void myMethod() {
        System.out.println("a");
    }
}

package a;

public class DerivedOne extends Base {
    @Override
    void myMethod() {
        System.out.println("b");
    }
}

package b;

public class DerivedTwo extends a.DerivedOne {
    public static void main(String... args) {
        myMethod(); // this does not compile...
    }
}

我建议对你自己、你的同事和任何其他最终不得不维护你的代码的人好一点;重新考虑您的类和接口以避免这种情况。

于 2009-12-14T17:40:54.000 回答
2

你必须在覆盖它时使方法最终

public class Base {
protected void myMethod() {}
}

// Uses myMethod and then hides it.
public class DerivedOne extends Base {
@Override
final protected void myMethod(); //make the method final
}


public class DerivedTwo extends DerivedOne {
   // can't access myMethod here.
}
于 2015-12-31T05:09:22.167 回答