抽象如何实现接口?

How can an abstract implement an interface?

我有一个通用接口,它描述了对输出流的访问,如下所示:

interface IOutput {
    function writeInteger(aValue:Int):Void;
}

我有一个基于标准的接口的抽象实现 haxe.io.BytesOutput class:

abstract COutput(BytesOutput) from BytesOutput {
    public inline function new(aData:BytesOutput) {
        this = aData;
    }
    public inline function writeInteger(aValue:Int):Void {
        this.writeInt32(aValue);
    }
}

虽然这个抽象真正实现了上述接口,但没有直接引用接口,当我尝试像这样使用它时:

class Main {
    public static function out(aOutput:IOutput) {
        aOutput.writeInteger(0);
    }
    public static function main() {
        var output:COutput = new BytesOutput();
        out(output); // type error
    }
}

编译器抛出错误:COutput should be IOutput。我只能通过使用包装 BytesOutput 并实现 IOutput.

的通用 class 来解决这个问题

我的问题是如何向 Haxe 编译器显示抽象实现了接口。

抽象 can't implement interfaces 因为它们是编译时特性,在运行时不存在。这与接口冲突,它们 do 存在于运行时并且像 Std.is(something, IOutput) 这样的动态运行时检查必须工作。

Haxe 还有一个名为 structural subtyping that can be used as an alternative to interfaces. With this approach, there's no need for an explicit implements declaration, it's good enough if something unifies 的机制,其结构为:

typedef IOutput = {
    function writeInteger(aValue:Int):Void;
}

不幸的是,抽象 aren't compatible with structural subtyping 要么是由于它们的实现方式。


您是否考虑过使用 static extensions instead? At least for your simple example, that seems like the perfect solution for making a writeInteger() method available for any haxe.io.Output:

import haxe.io.Output;
import haxe.io.BytesOutput;
using Main.OutputExtensions;

class Main {
    static function main() {
        var output = new BytesOutput();
        output.writeInteger(0);
    }
}

class OutputExtensions {
    public static function writeInteger(output:Output, value:Int):Void {
        output.writeInt32(value);
    }
}

您甚至可以将它与结构子类型结合起来,这样 writeInteger() 就可以在任何具有 writeInt32() 方法的东西上使用 (try.haxe link):

typedef Int32Writable = {
    function writeInt32(value:Int):Void;
}

, abstracts cannot implement interfaces。在 Haxe 中,对于实现接口的类型,它必须能够被编译成可以使用接口的方法调用的东西 class-like 而不会发生任何魔法。也就是说,要使用一个类型作为它的接口,需要有一个“真实的”class 实现该类型。 Haxe 中的抽象编译成它们的基本类型——编译发生后抽象本身是完全不可见的。因此,在运行时,没有 class 的实例具有实现接口的摘要中定义的方法。

但是,您可以通过 defining an implicit conversion 使您的抽象 出现 来实现一个接口,以实现您正在尝试实现的接口。对于您的示例,以下可能有效:

interface IOutput {
    function writeInteger(aValue:Int):Void;
}

abstract COutput(BytesOutput) from BytesOutput {
    public inline function new(aData:BytesOutput) {
        this = aData;
    }
    @:to()
    public inline function toIOutput():IOutput {
        return new COutputWrapper((cast this : COutput));
    }
    public inline function writeInteger(aValue:Int):Void {
        this.writeInt32(aValue);
    }
}

class COutputWrapper implements IOutput {
    var cOutput(default, null):COutput;
    public function new(cOutput) {
        this.cOutput = cOutput;
    }
    public function writeInteger(aValue:Int) {
        cOutput.writeInteger(aValue);
    }
}

class Main {
    public static function out(aOutput:IOutput) {
        aOutput.writeInteger(0);
    }
    public static function main() {
        var output:COutput = new BytesOutput();
        out(output);
        out(output);
    }
}

Run on try.haxe.org

请注意,每次发生隐式转换时,都会构造一个新的包装器实例。这可能会影响性能。如果您仅通过其接口访问您的值,请考虑将变量的类型设置为接口而不是抽象。

这类似于 C# 中 primitive/value 类型的“装箱”。在 C# 中,允许使用 struct 关键字定义的值类型实现接口。就像 Haxe 中的抽象一样,C# 中的值类型被(由 JITter)编译成无类型代码,这些代码只是直接访问和操作某些操作的值。但是,C# 允许 structs 实现接口。 C# 编译器会将任何将 struct 隐式转换为已实现接口的尝试转换为包装器 class 的构造,它存储值的副本并实现接口——类似于我们手动编写的包装器 class(这个包装器 class 实际上是由运行时作为 JITing 的一部分生成的,并由 IL box 指令执行。请参阅 this example 中的 M())。可以想象,Haxe 可以添加一个功能来自动为您生成这样的包装器 class,就像 C# 为 struct 类型所做的那样,但这不是当前的功能。但是,您可以自己动手,如上所述。