使用类委派时,有没有办法覆盖委托对象的方法使用的成员?

时间:2018-06-03 14:03:23

标签: kotlin

在Kotlin中使用类授权时,您可以覆盖成员。但是,reference page on delegation说:

  

但是,请注意,以这种方式覆盖的成员不会从委托对象的成员调用,该委托对象只能访问其自己的接口成员实现。

我想覆盖委托对象的方法使用的属性,以便委托对象的方法调用此重写属性。正如文档所述,使用override关键字覆盖该属性并不能实现此目的。有没有办法可以实现这种行为?如果没有,这是否表明我应该使用继承呢?

这是一个代码示例:

interface Base {
    val message: String
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override val message = "BaseImpl: x = $x"
    override fun print() { println(message) }
}

class Derived(b: Base) : Base by b {
    // This property is not accessed from b's implementation of `print`
    override val message = "Message of Derived"
}

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    val derived = Derived(b)
    derived.print()
}

此代码打印“BaseImpl:x = 10”,但我希望它打印“Derived of Message”。

2 个答案:

答案 0 :(得分:2)

问题出在生成的代码上:

public final class BaseImpl implements Base {
   @NotNull
   private final String message;
   private final int x;

   @NotNull
   public String getMessage() {
      return this.message;
   }

   public void print() {
      String var1 = this.getMessage();
      System.out.println(var1);
   }

   public final int getX() {
      return this.x;
   }

   public BaseImpl(int x) {
      this.x = x;
      this.message = "BaseImpl: x = " + this.x;
   }
}
public interface Base {
   @NotNull
   String getMessage();

   void print();
}
public final class Derived implements Base {
   @NotNull
   private final String message;
   // $FF: synthetic field
   private final Base $$delegate_0;

   @NotNull
   public String getMessage() {
      return this.message;
   }

   public Derived(@NotNull Base b) {
      Intrinsics.checkParameterIsNotNull(b, "b");
      super();
      this.$$delegate_0 = b;
      this.message = "Message of Derived";
   }

   public void print() {
      this.$$delegate_0.print();
   }
}

我花了一段时间才发现它,但这是要点:

委派并不意味着扩展特定类型。这意味着重写Base(在这种情况下)接口,并将未重写的事件发送到委托类。这里有两件事要特别注意:

public void print() {
   this.$$delegate_0.print();
}

还有

public final class Derived implements Base {

这是什么意思?这意味着Derived绝不会覆盖BaseImpl。为了显示我的意思,请使用以下代码:

interface Base {
    val message: String
    fun print()
}

open class BaseImpl(val x: Int) : Base { // This needs to be `open` to override
    override val message = "BaseImpl: x = $x"
    override fun print() { println(message) }
}

class Derived(x: Int) : BaseImpl(x) {
    // This property is not accessed from b's implementation of `print`
    override val message = "Message of Derived"
}

fun main(args: Array<String>) {
    val derived = Derived(10)
    derived.print()
}

它将打印Message of Derived。为什么?因为现在您实际上覆盖了BaseImpl。反编译代码,并记住我之前告诉您要密切注意的两件事:

public final class Derived extends BaseImpl {
   @NotNull
   private final String message = "Message of Derived";

   @NotNull
   public String getMessage() {
      return this.message;
   }

   public Derived(int x) {
      super(x);
   }
}

您可能会看到打印方法不见了;这是因为您没有覆盖它。另外,它现在是extends BaseImpl而不是implements Base。对getMessage方法的任何调用(可以在print方法中看到的调用)都将“重定向”到重写的方法,而不是基类中的那个。

如果您使用Java进行此操作,而不是直接使用getter调用字段,那么它将无法正常工作,因为you can't override variables in Java。它在Kotlin中可以立即使用的原因是,正如您在反编译代码中看到的那样,它使用方法。

如何解决此问题取决于您。如果仍要使用委托的属性,则需要覆盖Derived类中的print方法。因为,正如我之前提到的,DerivedBaseImpl之间没有实际的关系,除了有一个共享的父对象外。但是Derived不是BaseImpl的子级。

因此将getMessage()覆盖为EpicPandaForce无效。另外,通过执行override val message = ...,您会生成一个覆盖的吸气剂,这是我的第二个示例所示。

TL; DR: Base by b实现Base,并将对print的调用发送到委托的BaseImplDerived不是BaseImpl的子代,因此BaseImpl无法调用getMessage()中的Derived getter,因此打印自己的消息

答案 1 :(得分:0)

await page.click(<button css selector here>)

doSomething in Derived

BaseImpl中的doSomethingElse