Skip to content
This repository has been archived by the owner on Dec 6, 2017. It is now read-only.

named binding #195

Open
jonaskello opened this issue Oct 30, 2014 · 12 comments
Open

named binding #195

jonaskello opened this issue Oct 30, 2014 · 12 comments

Comments

@jonaskello
Copy link

Maybe this is already possible but I cannot find any way to do it. Most di frameworks I've worked with before support binding to a name. So I guess something like this:

abstract class MyInterface {}
class MyClassA implements MyInterface {}
class MyClassB implements MyInterface {}
bind(MyClassA, toName: "ImplA")
bind(MyClassB, toName: "ImplB")
MyInterface implToUse = injector.getByName("ImplB")

The name does not have to be a string, it would be better if any object could be used as a key/name.

@pavelgj
Copy link
Contributor

pavelgj commented Oct 30, 2014

What's the use case?

@jonaskello
Copy link
Author

Well basically every case where you have alternative implementations of an interface and want to pick one at run-time. One example may be that you have a logger interface that have several implementations and you want to be able to select one from config. Say you have one implementation that writes to a file and another that writes to a database. Something like this:

abstract class Logger { void LogMessage(); }
class WriteToFileLogger implements Logger { ... }
class WriteToDatabaseLogger implements Logger { }
bind(WriteToFileLogger, toName: "FileLogger");
bind(WriteTODatabaseLogger, toName: "DBLogger");
var configuredLogger = GetConfiguredLoggerFromConfigFile();
var logger = injector.getByName(configuredLogger);
logger.LogMessage("Hello World!"); // Will be logged to file or database depending on config file

Another example that is my current case is when you have a messaging framework and want to get a named handler for particular message while all handlers implement the same interface.

@pavelgj
Copy link
Contributor

pavelgj commented Oct 31, 2014

Use annotations for this.

bind(Logger, toImplementation: DbLogger, withAnnonation: const UseDb());
bind(Logger, toImplementation: ConsoleLogger, withAnnonation: const UseConsole());

class MyService {
  MyService(@UseDb() Logger logger) {
    ...
  }
}

or

var loggerType = logToDb ? const UseDb() : const UseConsole();
injector.getByKey(new Key(Logger, loggerType));

@jonaskello
Copy link
Author

Ok, but I was hoping to achieve this without annotations. In general I think annotations is a bad idea since they spread your framework specific DI concerns across the whole codebase. I usually want to keep the DI concerns contained in the module classes leaving all other classes ignorant of the dependency injection framework.

Is there a reason to not support keyed bindings that don't require annotations (instead keyed by string or other objects)?

@jonaskello
Copy link
Author

Just to clarify. Of course you need some code to be aware of the key but I much rather encapsulate this in something like a factory than maintain annotations across all classes in my codebase.

@jonaskello
Copy link
Author

From using DI in C# over the last years think this is the pattern that has emerged:

1st generation:
Using XML files to specify registrations and injections (eg. early StructureMap for .NET)
2nd generation:
Using annotation to specify registrations and injections (eg.Microsoft Unity for .NET)
3rd generation:
Using module files while keeping the codebase ignorant of all DI concerns (eg. AutoFac for .NET)

If possible, I would like dart to have capabilities in the 3rd generation :-)

@jonaskello
Copy link
Author

To be more specific about the problem with the suggested annotation solution. If you have 100 classes using the logger and you want to switch implementation you will have to edit the annotation from @usedb to @UseFile in all 100 classes. This is if I understood the usage of correctly?

@pavelgj
Copy link
Contributor

pavelgj commented Oct 31, 2014

@jonaskello

A couple of points:

  • annotations are not DI specific concerns, they are your application specific metadata which you can use to also configure/interact with DI.
  • annotations (as shown in the second example from my previous comment) cover your usecase. You can build a String-based abstraction on top of annotations if you want, but it's up to you. I personally believe that anything String-based in DI is evil.

@jonaskello
Copy link
Author

Well, I would not need the annotations unless the DI framework required it, which in IMHO makes it a DI framework specific concern. Compare with persistance ignorance for persistance frameworks. I like to keep things as POCO/POJO as possible or in Dart's case PODO :-).

I do like that the di package does not require you to use built-in framework specific attributes which is the case in many other annotation-based DI frameworks. But I prefer a solution that don't rely on annotations at all.

Could you elaborate on the string-based abstraction? I'm not sure how I would do that?

@jonaskello
Copy link
Author

Actually if it was possible to do a non-annotation abstraction that would allow any object to be used as key that would be even better. This could help avoid magic-strings in some situations.

@jonaskello
Copy link
Author

I tried your sample and did some experiments. It seems what I want to do is already possible. This code works now but not sure if you will support it in the future?

import 'package:di/di.dart';

class Logger { LogMessage(String message); }
class DbLogger implements Logger { LogMessage(String message) { print('DbLogger says: $message'); } }
class ConsoleLogger implements Logger { LogMessage(String message) { print('ConsoleLogger says: $message'); } }

void main() {
  var module = new Module()
    ..bind(Logger, toImplementation: DbLogger, withAnnotation: "UseDb")
    ..bind(Logger, toImplementation: ConsoleLogger, withAnnotation: "UseConsole");

  var injector = new ModuleInjector([module]);
  bool logToDb = true;
  var loggerType = logToDb ? "UseDb" : "UseConsole";

  var theLogger = injector.getByKey(new Key(Logger, loggerType));
  theLogger.LogMessage('Hello!');
}

@jonaskello
Copy link
Author

Here is another example that uses a Message type as key to pick a handler. It also works but outputs DEPRECATED messages:

import 'package:di/di.dart';

abstract class MessageHandler<TMessage> { Handle(TMessage message); }
abstract class Message {  }

class SaveMessage implements Message {  }
class DeleteMessage implements Message {  }

class SaveMessageHandler implements MessageHandler<SaveMessage> { Handle(SaveMessage message) { print('Save!'); } }
class DeleteMessageHandler implements MessageHandler<DeleteMessage> { Handle(DeleteMessage message) { print('Delete!'); } }

void main() {
  var module = new Module()
    ..bind(MessageHandler, toImplementation: SaveMessageHandler, withAnnotation: SaveMessage)
    ..bind(MessageHandler, toImplementation: DeleteMessageHandler, withAnnotation: DeleteMessage);
  var injector = new ModuleInjector([module]);

  var message = new SaveMessage();

  MessageHandler handler = injector.getByKey(new Key(MessageHandler, SaveMessage));
  handler.Handle(message);
}

Output

DEPRECATED: Use `withAnnotation: const SaveMessage()` instead of `withAnnotation: SaveMessage`.
DEPRECATED: Use `withAnnotation: const DeleteMessage()` instead of `withAnnotation: DeleteMessage`.
Save!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

No branches or pull requests

2 participants