【问题标题】:In Java: Code reuse possible for a chain of method calls up an inheritance hierarchy?在 Java 中:方法链调用继承层次结构的代码重用可能吗?
【发布时间】:2011-08-04 10:11:03
【问题描述】:

我有一些类继承 SubClass

我目前的解决方案是非常样板:

class SuperClass {
  protected void m1() {
    //TASK (calls m2())
  }

  private void m2() {
    //...
  }
}

class MidClass extends SuperClass {
  protected void m1() {
    //same TASK (calls m2())
    super.m1();
  }

  private void m2() {
    //...
  }
}

class SubClass extends MidClass {
  protected void m1() {
    //same TASK (calls m2())
    super.m1();
  }

  private void m2() {
    //...
  }
}

我可以利用一些代码重用机制而不是复制 TASK 吗?

类似下面的东西,m1() 只在 SuperClass 中,不起作用:

class SuperClass {
  protected final void m1() {
    //TASK (calls m2())
    if (!(this.getClass().equals(SuperClass.class))) {
      super.m1();
  }
}

因为 super.m1() 不是指在超类的上下文中执行相同的继承方法,而是指被覆盖的方法实现。由于 Object 中不存在 m1(),所以我另外得到一个编译器错误...

将 TASK 放在 SuperClass 中受保护的最终 helper() 方法中并调用 helper() 而不是复制 TASK 将不起作用,因为这样总是会调用 SuperClass.m2()。

我能想到的唯一选择是缓慢、复杂和不安全:使用类型标记作为参数,即 SuperClass 中的protected final void m1(Class<? extends SuperClass> clazz),并通过反射完成 TASK(需要将 m2() 设为公共静态或使用 setAccessible(true ) 在 m2()) 上。

你知道更好的解决方案吗?奥普?也许是一些可以将方法注入类的框架(如在 C# 中)?还是我错过了什么???

【问题讨论】:

  • 您能否详细说明一下任务的不同之处?我怀疑设计问题比代码问题更多。
  • 任务的不同之处在于比较的值:由该子类添加的那些字段。详细说明:我已经实现了具有默认值约束的混合类型 equal(),而不是忽略子类值字段。详情请见angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/…。在 Angelika 的术语中,m1() 是 _navigateClassHierarchy(),m2() 是 _compareFields()。
  • 如果我理解正确,_compareFields() 也会比较子类中的附加字段。因此,我提出的super.m2() 方法应该适用于此(类似于if( super._compareFields() ) { /*compare additional fields*/})。
  • 是的,Thomas,我可以做到,谢谢。它将大大简化代码:对于 o1.equals(o2),我可以简单地调用 this._compareFields(o2) 和 o2._compareFields(this),而不需要 _navigateClassHierarchy()。请参阅我自己的回答,了解如何避免某些检查被执行两次,这就是首先实施 _navigateClassHierarchy() 的原因。获得一个关于如何用 Java 解决我的问题的一般答案,而不是重新设计,仍然很有趣:对于以后的类似情况,用 Java 解决这个问题也可以让我们对语言有更多的了解......

标签: java inheritance code-reuse super dynamic-dispatch


【解决方案1】:

这个怎么样?

class SuperClass {
  protected void m1() {
    //TASK (calls m2())
  }

  protected void m2() {
    //...
  }
}

class MidClass extends SuperClass {

  protected void m2() {
    //...
  }
}

class SubClass extends MidClass {
  protected void m2() {
    //...
  }
}

m1 方法是继承的,并且会一直调用m2 方法。由于m2 受到保护,因此它被称为多态。因此,如果在 SubClass 实例上调用,SubClass.m2() 将被调用。

【讨论】:

  • 感谢您的快速回复:) 由于我不想执行一次任务,而是向上链接继承层次结构,我需要类似if (!(this.getClass().equals(SuperClass.class))) { super.m1(); } 的东西,这导致我尝试的第一个替代方案(请参阅我的第二个较小的代码示例)。
  • @user750378 为什么不直接在protected void m2() 中调用super.m2()?这将处理链接,您可以自己定义顺序(首先、中间或最后调用超级实现)
  • @Thomas:我想链接 m1(),将 TASK 的细微差别换成方法 m2()。如果我使 m2() 受保护,当我在 m1() 中向上创建链时,动态调度将始终选择 this.m2() - 因此是私有 m2()。
【解决方案2】:

扩展我对 JB 回答的评论:

class SuperClass {
 protected void m1() {
   m2();
 }

 protected void m2() {
   System.out.println("start super task");
   System.out.println("end super task");
 }
}

class MidClass extends SuperClass {
  protected void m2() {
   super.m2();
   System.out.println("start mid task");
   System.out.println("end mid task");
  }
}

class SubClass extends MidClass {
 protected void m2() {
  System.out.println("start sub task");
  super.m2();
  System.out.println("end sub task");
 }
}

new SubClass().m1() 产生这个结果:

开始子任务
启动超级任务
结束超级任务
开始中间任务
结束中间任务
结束子任务

请注意,m2() 的所有 3 个版本都按定义的顺序执行:sub 启动,然后以 super 和 mid 继续执行,并再次以 sub 结束。

【讨论】:

  • 谢谢。这种链接正是我正在做的(在我较长的代码示例中使用 m1() )。如果您现在不仅要执行一些 println(),而且要执行一个相当复杂的 TASK,只对 3 个类进行微小的更改,那么您将如何最有效地做到这一点?
  • @user750378 您可以将每个复杂的任务分解成更小的方法,这些方法可以选择性地被覆盖(例如,对于可能单独更改的每个部分)。
  • 你能给出一个代码示例吗?选择性地被覆盖是什么意思?有选择地为每个类,以便在链接类层次结构时执行不同的版本?我不知道该怎么做! (有关在这些情况下动态调度的说明,请参阅例如 stackoverflow.com/questions/4595512/…)。
  • @user750378 好吧,您可以将 m2() 拆分为几个可以单独覆盖的受保护方法。但是,如果MidClass 会覆盖这样的方法,但如果SubClass 需要SuperClass 提供的原始版本,那么您可能会遇到设计问题。在这种情况下,您可能会使用复合模式,即为每个部分创建“函数”-对象(函子),并从这些函子组成每个任务表示。
  • 感谢您的努力,托马斯。不幸的是,我仍然看不到如何进行代码重用并避免动态调度:如果向上链接类层次结构的方法调用另一个受保护的方法或调用函子,动态调度将始终选择同一个,即 this 指向的那个将被调用 3 次...
【解决方案3】:

我的混合类型 equals() 的具体示例的解决方案,具有默认值约束,而不是忽略子类值字段。代替使用私有方法 _compareFields() 和必须复制到每个子类中的受保护方法 _navigateClassHierarchy() 的 Angelika Langer 解决方案(参见 http://www.angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/Equals-2.html),只使用受保护方法 compareOwnFields(),它必须在每个子类。

class SuperClass {
    // ...

    @Override
    public final boolean equals(final Object other) {
        if (other == this) { return true; }
        if (!(other instanceof SuperClass)) {
            return false;
        }
        final SuperClass otherSuperClass = (SuperClass) other;

        return compareOwnFields(otherSuperClass, false)  
        && otherSuperClass.compareOwnFields(this, true);
    }

    protected boolean compareOwnFields(final SuperClass other, 
        final boolean firstTraversal) {
        if (!firstTraversal) {
            return true;
        }
        if (field1 != other.getField1()) {
           return false;
        } 
        // compare other fields similarly ...
        return true;
    }

}    

class SubClass {
    // ...

    @Override
    protected boolean compareOwnFields(final SuperClass other, 
        final boolean firstTraversal) {
        if (other instanceof SubClass && !firstTraversal) {
            return true;
        if (other instanceof SubClass) {
            if (field1 != ((SubClass) other).getField1()) {
                return false;
            }
            // compare other fields similarly ...
            return super.compareOwnFields(other, firstTraversal);
        } else {
            if (field1 != DEFAULT_FIELD1) {
                return false;
            }
            // check other fields for default values similarly ..
            return super.compareOwnFields(other, firstTraversal);
        }
    }
}

但这一般不能回答我的问题,而是重新设计以避免问题。因此,非常欢迎您提供有关如何解决 Java 语言功能问题的进一步答案!

【讨论】:

  • 只是一个问题:你为什么要两次比较这些字段 - 我的意思是它就像 'this.equals(other) && other.equals(this)? Note that the contract for equals()` 表明如果 @987654324 @对象不相等。因此你可以做类似if( super.compareOwnFields((SuperClass)other) ) { compareOwnFields((SubClass)other); }
  • 如果你对equals()感兴趣,我强烈推荐阅读Angelikas关于它的文章。简而言之: this.getClass() != other.getClass() 不是 equal 合同的一部分。许多人这样做,但它不符合 Liskov 的替换原则(参见例如 Effective Java item 8)。要允许混合类型相等,您没有该约束。我正在调用 o1.compareOwnFields(o2) 和 o2.compareOwnFields(o1) 来涵盖完整的类层次结构:例如超类>SubA1>SubA2,超类>SubB1>SubB2,o1 SubA2 实例,o2 SubB2 实例。通过两次调用,我比较了所有相关字段。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多