首页 > 解决方案 > Dart - 实现一个带有参数的类方法作为实现的类

问题描述

我正在开发外部库。假设我有不同的记录器类实现,我创建ILogger了这些类可以实现的抽象类。

我也有各种日志对象的实现,我想遵守ILog抽象类,所以我也公开了它。

问题是当我ILogger使用它ILog作为其中一种方法的参数时。我会假设我实现的任何记录器类(实现ILogger)都将接受任何作为日志类(实现ILog)的参数。

请参阅下面的受限示例:

abstract class ILog {
  const LogImpl({required this.id});
  final String id;
}

class Log implements ILog {
  const Log({required this.id});
  
  final String id;
}

abstract class ILogger {
  void writeLog({
    required LogImpl log,
    required bool persist,
  });
}

class Logger implements ILogger {
  void writeLog({
    required Log log,
    required bool persist,
  }) {
    print('writing $log with persistence? $persist');
  }
}

void main() {
  final logger = Logger();
  final log = Log(id: 'abcd-1234');
  
  logger.writeLog(log: log, persist: true)
}

为此,我得到错误:

Error: The parameter 'log' of the method 'Logger.writeLog' has type 'Log', which does not match the corresponding type, 'ILog', in the overridden method, 'ILogger.writeLog'.

有没有办法在不求助于泛型的情况下解决这个问题?我的日志对象ILog需要是抽象类而不是扩展的常规类的原因是技术性的。我的序列化库之一使用源代码注释,这意味着该注释不能成为库的一部分(因为不同应用程序的注释可能不同)。

标签: dartinterface-implementation

解决方案


该程序无法编译,因为它不健全。Dart 和一般的面向对象编程基于子类型可替换性。如果您的代码适用于某个类型的实例,那么它也适用于子类型的实例——子类型可以替代超类型。

Logger'writeLog方法不是ILogger'方法的有效覆盖writeLog。后者接受一个ILog作为参数,并且为了能够替代它的子类型,它也需要接受一个ILog。但是,它只接受 a ,它是一个Log子类型,而不接受.ILog

一种替代方法是承认您正在编写的内容不合理,并告诉编译器无论如何都要接受它:

class Logger implements ILogger {
  void writeLog({
    required covariant Log log,
    required bool persist,
  }) {
    print('writing $log with persistence? $persist');
  }
}

这里covariant告诉编译器你知道Log不满足的超类参数类型ILog,但是没关系。你知道你在做什么,没有人会用不合适的东西来调用这个函数Log。(如果他们仍然这样做,它会抛出一个错误)。

我可能会推荐的另一种选择是根据Log它使用的参数化您的类:

abstract class ILogger<L extends ILog> {
  void writeLog({
    required L log,
    required bool persist,
  });
}

class Logger implements ILogger<Log> {
  void writeLog({
    required Log log,
    required bool persist,
  }) {
    print('writing $log with persistence? $persist');
  }
}

这样, theLogger就不必替换所有 ILogger的 s,只替换ILogger<Log>,它可以很好地做到这一点。(好吧,就像固有的不健全的协变泛型所允许的那样健全,但是如果你传递了不是实例的东西,程序将编译并抛出。)Log

在这两种情况下,编译器都会知道 a 的参数Logger必须是 a LogLogger在这两种情况下,您都可以通过将 the 强制转换为超类型ILogger/来欺骗程序ILogger<ILog>,然后传入一个ILogto writeLog,但要绕过类型系统至少需要一点努力。


推荐阅读