Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use dart to implement a delegate/proxy?

I have two classes Parser and Proxy, and when I invoke a method from Parser, which does not exist, it will delegate it to Proxy class.

My code:

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

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

That when I write:

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

It will print:

hello
like image 766
Freewind Avatar asked Jul 03 '13 07:07

Freewind


3 Answers

You have to use dart:mirrors. Here's how to do :

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());
}
like image 78
Alexandre Ardhuin Avatar answered Nov 13 '22 21:11

Alexandre Ardhuin


Alexandre's answer is correct, but I'd like to add something.

I assume that the delegation to Proxy is an implementation detail, and we don't want the user to be exposed to it. In that case, we should have some handling of cases where methods are called on parser that are not supported by Proxy. Right now, if you do this:

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

You get this error:

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

I would write the code in noSuchMethod a little differently. Before delegating to Proxy, I would check that Proxy supports the method I'm about to invoke. If Proxy supports it, I would invoke the method on Proxy as Alexandre describes in his answer. If Proxy does not support the method, I would throw a NoSuchMethodError.

Here is a revised version of the answer:

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

And here is the output from running this code:

hello
world
Unhandled exception:
NoSuchMethodError : method not found: 'foo'
Receiver: Instance of 'Parser'
Arguments: []
like image 7
Shailen Tuli Avatar answered Nov 13 '22 23:11

Shailen Tuli


I'll also add that you could avoid the use of mirrors if the set of things you want to delegate to is fixed and you can reasonably hard-code it. That's particularly easy if you're using static methods, but I'm not clear why you're doing that here. I think the following would work for both instance methods and static methods, but I'm typing this code in without actually trying it...

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

This is fragile if the set of forwarded methods changes, but may help avoid the code size increase if you use mirrors (which at the moment disables almost all tree-shaking).

like image 3
Alan Knight Avatar answered Nov 13 '22 21:11

Alan Knight