Как использовать дротик для реализации делегата/прокси?

У меня есть два класса Parser и Proxy, и когда я вызываю несуществующий метод из Parser, он делегирует его классу Proxy.

Мой код:

class Parser {
    noSuchMethod(Invocation invocation) {
        // how to pass the `invocation` to `Proxy`???
    }
}

class Proxy {
    static String hello() { return "hello"; }
    static String world() { return "world"; }
}

Что когда я пишу:

var parser = new Parser();
print(parser.hello());

Он будет печатать:

hello

person Freewind    schedule 03.07.2013    source источник


Ответы (4)


Вы должны использовать dart:mirrors. Вот как это сделать:

import 'dart:mirrors';

class Parser {
  noSuchMethod(Invocation invocation) {
    ClassMirror cm = reflectClass(Proxy);
    return cm.invoke(invocation.memberName
        , invocation.positionalArguments
        /*, invocation.namedArguments*/ // not implemented yet
        ).reflectee;
  }
}

class Proxy {
  static String hello() { return "hello"; }
  static String world() { return "world"; }
}

main(){
  var parser = new Parser();
  print(parser.hello());
  print(parser.world());
}
person Alexandre Ardhuin    schedule 03.07.2013
comment
Могу ли я использовать cm.delegate(invocation) вместо cm.invoke(...)? Я пробовал, они оба работают, но какая разница? - person Freewind; 03.07.2013

Ответ Александра правильный, но я хотел бы кое-что добавить.

Я предполагаю, что делегирование Proxy является деталью реализации, и мы не хотим, чтобы пользователь подвергался этому воздействию. В этом случае у нас должна быть некоторая обработка случаев, когда методы вызываются для parser, которые не поддерживаются Proxy. Прямо сейчас, если вы сделаете это:

void main() {
  var parser = new Parser();
  print(parser.foo());
}

Вы получаете эту ошибку:

Unhandled exception:
Compile-time error during mirrored execution: <Dart_Invoke: did not find static method 'Proxy.foo'.>

Я бы написал код в noSuchMethod немного по другому. Прежде чем делегировать Proxy, я бы проверил, поддерживает ли Proxy метод, который я собираюсь вызвать. Если Proxy поддерживает это, я бы вызвал метод на Proxy, как описывает Александр в своем ответе. Если Proxy не поддерживает метод, я бы выкинул NoSuchMethodError.

Вот исправленная версия ответа:

import 'dart:mirrors';

class Parser {
  noSuchMethod(Invocation invocation) {
    ClassMirror cm = reflectClass(Proxy);
    if (cm.methods.keys.contains(invocation.memberName)) {
      return cm.invoke(invocation.memberName
          , invocation.positionalArguments
          /*, invocation.namedArguments*/ // not implemented yet
          ).reflectee;
    }
    throw new NoSuchMethodError(this,
        _symbolToString(invocation.memberName),
        invocation.positionalArguments,
        _symbolMapToStringMap(invocation.namedArguments));
  }
}


String _symbolToString(Symbol symbol) => MirrorSystem.getName(symbol);

Map<String, dynamic> _symbolMapToStringMap(Map<Symbol, dynamic> map) {
  if (map == null) return null;
  var result = new Map<String, dynamic>();
  map.forEach((Symbol key, value) {
    result[_symbolToString(key)] = value;
  });
  return result;
}

class Proxy {
  static String hello() { return "hello"; }
  static String world() { return "world"; }
}

main(){
  var parser = new Parser();
  print(parser.hello());
  print(parser.world());
  print(parser.foo());
}

И вот результат выполнения этого кода:

hello
world
Unhandled exception:
NoSuchMethodError : method not found: 'foo'
Receiver: Instance of 'Parser'
Arguments: []
person Shailen Tuli    schedule 03.07.2013

Я также добавлю, что вы можете избежать использования зеркал, если набор вещей, которым вы хотите делегировать, фиксирован, и вы можете разумно жестко запрограммировать его. Это особенно просто, если вы используете статические методы, но я не понимаю, почему вы делаете это здесь. Я думаю, что следующее будет работать как для методов экземпляра, так и для статических методов, но я набираю этот код, фактически не пробуя его...

Function lookupMethod(Proxy p, Symbol name) {
  if (name == const Symbol("hello")) return p.hello;
  if (name == const Symbol("world")) return p.world;
  throw "Aaaaaagh";
}

noSuchMethod(invocation) => 
    Function.apply(lookupMethod(Proxy, invocation.memberName),
        invocation.positionalArguments);

Это ненадежно, если набор пересылаемых методов изменяется, но может помочь избежать увеличения размера кода, если вы используете зеркала (которые на данный момент отключают почти все встряхивания деревьев).

person Alan Knight    schedule 03.07.2013

Этот пример также может помочь вам понять:

void main() {
  var car = new CarProxy(new ProxyObjectImpl('Car'));
  testCar(car);

  var person = new PersonProxy(new ProxyObjectImpl('Person'));
  testPerson(person);
}

void testCar(Car car) {
  print(car.motor);
}

void testPerson(Person person) {
  print(person.age);
  print(person.name);
}

abstract class Car {
  String get motor;
}

abstract class Person {
  int get age;
  String get name;
}

class CarProxy implements Car {
  final ProxyObject _proxy;

  CarProxy(this._proxy);

  noSuchMethod(Invocation invocation) {
    return _proxy.handle(invocation);
  }
}

class PersonProxy implements Person {
  final ProxyObject _proxy;

  PersonProxy(this._proxy);

  noSuchMethod(Invocation invocation) {
    return _proxy.handle(invocation);
  }
}

abstract class ProxyObject {
  dynamic handle(Invocation invocation);
}

class ProxyObjectImpl implements ProxyObject {
  String type;
  int id;
  Map<Symbol, dynamic> properties;

  ProxyObjectImpl(this.type, [this.id]) {
    properties = ProxyManager.getProperties(type);
  }

  dynamic handle(Invocation invocation) {
    var memberName = invocation.memberName;

    if(invocation.isGetter) {
      if(properties.containsKey(memberName)) {
        return properties[memberName];
      }
    }

    throw "Runtime Error: $type has no $memberName member";
  }
}

class ProxyManager {
  static Map<Symbol, dynamic> getProperties(String name) {
    Map<Symbol, dynamic> properties = new Map<Symbol, dynamic>();
    switch(name) {
      case 'Car':
        properties[new Symbol('motor')] = 'xPowerDrive2013';
        break;
      case 'Person':
        properties[new Symbol('age')] = 42;
        properties[new Symbol('name')] = 'Bobby';
        break;
      default:
        throw new StateError('Entity not found: $name');
    }

    return properties;
  }
}
person mezoni    schedule 04.07.2013
comment
Есть ли способ избежать создания одного прокси для каждого класса. - person Luis Vargas; 08.08.2014