Menu
Coddy logo textTech

noSuchMethod のオーバーライド

CoddyのDartジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 74/110。

オブジェクトに存在しないメソッドを呼び出すと、Dartは通常 NoSuchMethodError をスローします。しかし、特別な noSuchMethod() メソッドをオーバーライドすることで、これらの呼び出しをインターセプトし、適切に処理することができます。

noSuchMethod() を使用するには、クラスがインターフェースを実装するか、dynamic 型指定を使用する必要があります。

このメソッドは、試行された呼び出しに関する詳細を含む Invocation オブジェクトを受け取ります:

class Flexible {
  @override
  dynamic noSuchMethod(Invocation invocation) {
    var methodName = invocation.memberName.toString();
    print('Called: $methodName');
    return null;
  }
}

void main() {
  dynamic obj = Flexible();
  obj.anyMethod();      // 呼び出し: Symbol("anyMethod")
  obj.anotherOne(42);   // 呼び出し: Symbol("anotherOne")
}

Invocationオブジェクトは、呼び出しに関する有用な情報を提供します。memberNameはメソッド名をSymbolとして返し、positionalArgumentsには渡された引数が含まれ、isMethodはそれがメソッド呼び出しなのか、あるいはゲッターやセッターなのかを教えてくれます。

class Logger {
  @override
  dynamic noSuchMethod(Invocation invocation) {
    print('Method: ${invocation.memberName}');
    print('Args: ${invocation.positionalArguments}');
    return 'handled';
  }
}

void main() {
  dynamic log = Logger();
  var result = log.save('data', 123);
  print(result);  // 処理済み
}

この手法は、プロキシオブジェクトやテスト用のモッククラスの作成、あるいは任意のメソッド呼び出しに応答する柔軟なAPIの構築に役立ちます。ただし、コンパイル時の型チェックをバイパスするため、使用は控えめにしてください。

challenge icon

チャレンジ

簡単

オブジェクトに対して行われたメソッド呼び出しをインターセプトして記録するメソッドロガーを作成しましょう!noSuchMethod() を使用して、未定義のメソッド呼び出しに関する情報を取得し、メソッド名に基づいてインテリジェントに応答するクラスを作成します。

コードを2つのファイルに整理します:

  • method_logger.dart: 未定義のメソッドへの呼び出しをインターセプトする MethodLogger クラスを作成します。ロガーは noSuchMethod() をオーバーライドして、以下の処理を行う必要があります:
    • 呼び出し(invocation)からメソッド名を抽出します(これは Symbol("methodName") のような Symbol になります。文字列に変換して名前だけを抽出する必要があります)。
    • メソッド名と位置引数を次の形式で出力します:Called: [methodName] with args: [arguments]
    • メソッド名に基づいて特別なレスポンスを返します:
      • メソッド名に get が含まれている場合は、文字列 'data retrieved' を返します。
      • メソッド名に save が含まれている場合は、文字列 'data saved' を返します。
      • それ以外の場合は、文字列 'unknown action' を返します。
  • main.dart: ロガーをインポートし、さまざまな未定義のメソッド呼び出しをどのように処理するかを示します。Dart が存在しないメソッドの呼び出しを許可するように、dynamic 型を使用することを忘れないでください:
    • dynamic 変数を使用して MethodLogger インスタンスを作成します。
    • そのインスタンスで getUserData('Alice', 42) を呼び出し、返された結果を出力します。
    • そのインスタンスで saveRecord('important') を呼び出し、返された結果を出力します。
    • そのインスタンスで processItem()(引数なし)を呼び出し、返された結果を出力します。

Symbol からメソッド名を抽出するには、それを文字列に変換してから、引用符の間の名前を解析します。例えば、Symbol("myMethod").toString()'Symbol("myMethod")' を返します。

期待される出力:

Called: getUserData with args: [Alice, 42]
data retrieved
Called: saveRecord with args: [important]
data saved
Called: processItem with args: []
unknown action

チートシート

noSuchMethod() メソッドを使用すると、オブジェクトに存在しないメソッドへの呼び出しをインターセプトできます。これを使用するには、クラスで dynamic 型を使用するか、インターフェースを実装する必要があります。

未定義のメソッド呼び出しを処理するために noSuchMethod() をオーバーライドします:

class Flexible {
  @override
  dynamic noSuchMethod(Invocation invocation) {
    var methodName = invocation.memberName.toString();
    print('Called: $methodName');
    return null;
  }
}

void main() {
  dynamic obj = Flexible();
  obj.anyMethod();      // Called: Symbol("anyMethod")
  obj.anotherOne(42);   // Called: Symbol("anotherOne")
}

Invocation オブジェクトは、呼び出しに関する情報を提供します:

  • memberName - Symbol としてのメソッド名
  • positionalArguments - メソッドに渡された引数
  • isMethod - ゲッターやセッターではなく、メソッド呼び出しであったかどうかを示します
class Logger {
  @override
  dynamic noSuchMethod(Invocation invocation) {
    print('Method: ${invocation.memberName}');
    print('Args: ${invocation.positionalArguments}');
    return 'handled';
  }
}

void main() {
  dynamic log = Logger();
  var result = log.save('data', 123);
  print(result);  // handled
}

この手法は、プロキシオブジェクト、テスト用のモッククラス、または柔軟な API を作成するのに役立ちますが、コンパイル時の型チェックをバイパスするため、控えめに使用する必要があります。

自分で試してみよう

import 'method_logger.dart';

void main() {
  // TODO: 動的型付けを使用して MethodLogger インスタンスを作成する
  // dynamic を使用すると、コンパイル時に存在しないメソッドを呼び出すことができます
  dynamic logger;
  
  // TODO: getUserData('Alice', 42) を呼び出し、結果を出力する
  
  // TODO: saveRecord('important') を呼び出し、結果を出力する
  
  // TODO: 引数なしで processItem() を呼び出し、結果を出力する
  
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン