Dependency Injection vs. Virtual Classes
Wednesday, December 30th, 2009
Every time you create an instance of a class by using the
newkeyword, you tightly couple your code to that class, and you will not be able to substitute that particularly implementation with a different one (at least not without recompiling the code).
Taken from an answer on stackoverflow that motivates dependency injection. Dependency injection however is not the real solution, it is rather a workaround for missing virtual classes. In this post I will show you why.
For example in
public class MyClass {
public string sendMessage(String contents) {
return new MailService().sendMessage(contents);
}
}
you cannot use a different MessageService. This might not seem limiting at first sight, but think for example of testing. Your tests might be better off using a stub service that does not send actual emails.
This can and is being solved with dependency injection, yes. But the actual problem is rather a language problem: Classes are not virtual. While methods are virtual and thus looked up at runtime, classes are not.
In the following example, we can change the mail service by subclassing and overriding a method. This works because methods are virtual and thus late bound.
public class MyClass {
public string sendMessage(String contents) {
return newMailService().sendMessage(contents);
}
public MailService newMailService() {
return new MailService();
}
}
What a difference a single whitespace can make!
When newMailService is called, that method name is looked up in the context of the calling method. The context of the calling method is its class. Therefore, by subclassing the containing class we can provide a different mail service.
Imagine the same would happen with class names. Whenever we call new MailService() then the class name would be looked up in the containing context. Imagine further the lookup would not stop with the class but bubble up to the containing package. Then, by “subclassing” that package we might provide a different mail service.
Subclassing a package is nonsense terminology, let’s rather say that we instantiate a package … and let’s further assume that packages and classes are the same, and that our system is built of nested classes.
public class MyApplication {
public class MyClass {
public string sendMessage(String contents) {
return new "MailService"().sendMessage(contents);
}
}
}
I have put the class name of MailService in quotes to remind you that it is the name of a virtual class name and thus looked up in the containing context.
Imagine further that we could pass the binding of classes to a class when creating it, then we could instantiate two versions of our application: one for real, and one for testing.
Application forTesting = new Application(MailService = TestMailer);
Application forReal = new Application(MailService = InterwebMailer);
Testing and production are not the only possible contexts. Imagine for example an XML parser. Typically it creates DOM nodes, but maybe you would prefer you own implementation of nodes. Maybe even the DAO objects of your application? There we go
XMLParser custom = new XMLParser(Node = Application.DAO);
XMLDocument doc = custom.new XMLDocument("example.xml");
NB, the second line is actually valid Java syntax: this is how you create an instance of an inner class when you are not in the same file as its outer class.
— So this is how dependency injection should really work!
And it is even not that far from reality. Both Beta and Newspeak (the new language by Gilad Bracha) do have virtual classes and both do have nested classes, and in both classes what I just described is how you decouple your packages in these languages.
And, hup hup hup, another design pattern has been disguised as a missing language feature :)