如何在抽象类中实现接口?

3

我有一个通用接口,描述了如何访问输出流:

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

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

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 应该是 IOutput。我只能通过使用封装了 BytesOutput 并实现了 IOutput 的通用类来解决这个问题。

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

2个回答

9

摘要 无法实现接口,因为它们是编译时功能,在运行时不存在。这与接口存在冲突,它们在运行时确实存在,动态运行时检查(例如Std.is(something, IOutput))必须正常工作。

Haxe还拥有一种称为结构子类型的机制,可用作接口的替代方案。使用此方法,无需显式声明implements,如果某些内容统一一个结构就足够了:

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

很不幸,由于实现方式的原因,摘要也与结构子类型不兼容。


您是否考虑使用静态扩展呢?至少对于您的简单示例来说,这似乎是将writeInteger()方法应用于任何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;
}

感谢详细的解释。我不想使用静态扩展,因为接口应该将内部协议实现隐藏在业务逻辑之后。因此,它不应直接应用于 BytesOutput,因为输出对象可以是 XML 或 JSON 格式。我宁愿使用一个包装类。 - meps
你不能使用结构子类型来完成这个任务吗(例如,将东西标记为Int32Writable而不是BytesOutput)?但也许最好的解决方案还是创建一个子类/包装类。 :) - Gama11
就我的情况而言,结构子类型和接口之间没有什么区别。因此我支持接口方案,因为它更加清晰和健壮。 - meps
你可以尝试使用隐式转换来使抽象类型符合IOutput类型,也许?https://haxe.org/manual/types-abstract-implicit-casts.html - Chii

0

正如@Gama11所述, 抽象类无法实现接口。在Haxe中,为了使类型实现一个接口,它必须能够被编译成类似于类的东西,可以使用接口的方法进行调用,而不需要发生任何魔法。也就是说,要将类型用作其接口,需要有一个“真正”的类来实现该类型。在Haxe中,抽象类编译成其基础类型——在编译完成后,抽象类本身完全不可见。因此,在运行时,没有一个类的实例具有在您的抽象类中定义的实现接口的方法。

但是,您可以通过定义隐式转换到您要实现的接口,使您的抽象类看起来实现一个接口。对于您的示例,以下内容可能有效:

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);
    }
}

在try.haxe.org上运行

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接