Open jonaskello opened 10 years ago
What's the use case?
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.
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));
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)?
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.
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 :-)
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?
@jonaskello
A couple of points:
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?
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.
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!');
}
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!
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:
The name does not have to be a string, it would be better if any object could be used as a key/name.