Adelin
Adelin

Reputation: 18961

Why injecting classes instead of interfaces is considered bad practice?

Injecting classes into other classes is considered bad, some of the arguments is that it is hard to mock a class and it couples the objects together.

But I see many developers do this everyday and most of the mocking frameworks are very good at mocking classes and provide test mocks so what is the problem ?

Upvotes: 11

Views: 2575

Answers (3)

J. B. Rainsberger
J. B. Rainsberger

Reputation: 1203

Abstract types (like interfaces) tend to be more stable (change less over time) than concrete classes. It is generally less risky to depend on more-stable things. Put these together, and it's generally less risky to depend on interfaces compared to concrete classes.

I also find it easier to detect significant changes in behavior when I depend more on interfaces. When an interface has to change, something significant is happening; and when something insignificant is happening that (surprisingly) causes an interface to change, then I interpret that as a signal to raise the level of abstraction in the design. With concrete classes, this signal doesn't "sound" quite as crisp, clean, and obvious to me.

Injecting a concrete class creates far fewer risks than instantiating them directly inside the client module. If you're not worried about the risk of injecting concrete classes, then do it for a while and wait for the risk to become a problem. You can decide then how much you mind. Fortunately, extracting interfaces from concrete classes is pretty safe, as refactorings go. Just remember, when it comes time, to extract the smallest interface that the client needs, and don't mindlessly put every concrete class method onto the interface.

Upvotes: 12

GhostCat
GhostCat

Reputation: 140457

Certain (influential) people, like Robert Martin claim that using the names of concrete classes in your source code is basically something that you should avoid in order to prevent "too tight coupling".

Let me quote from his book on "Agile principles":

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend upon details. Details should depend upon abstractions

and further:

Consider the implications of high-level modules that depend on low-level modules. It is the high-level modules that contain the important policy decisions and business models of an application. These modules contain the identity of the application. Yet when these modules depend on the lower-level modules, changes to the lower-level modules can have direct effects on the higher-level modules and can force them to change in turn. This predicament is absurd! It is the high-level, policy-setting modules that ought to be influencing the low-level detailed modules. The modules that contain the high-level business rules should take precedence over, and be independent of, the modules that contain the implementation details. High-level modules simply should not depend on low-level modules in any way.

But of course, in reality, there is always balancing. When you are very much convinced that

  1. your class is very likely to not change over time
  2. you class isn't final, and the methods you might want to mock aren't final either

then there is not much sense in preferring an interface over a class.

But - if in doubt, interfaces are the better way to go.

I have to explain people far too often that their idea of using PowerMock in order to allow mocking some final classes/methods is the wrong approach; and the much better answer is to change the type of a parameter to some interface instead.

Upvotes: 6

stackh34p
stackh34p

Reputation: 9009

The most obvious reason I have seen in practice is that you can lose capabilities of the DI framework that are sometimes essential for the proper functioning of your application. Furthermore, the issues may not become obvious until later on, when the code is happily executing on production.

For example, spring framework (undoubtedly among the top DI frameworks in the Java world) relies on using the decorator pattern to dynamically implement interfaces that are a subject to AOP. It dynamically creates a class that implements from your interface, the class has your original bean instance as a private member and decorates the relevant members with the AOP logic.

When injecting the class directly, you disable the possibility for the DI framework to perform the AOP magic, as the resulting type of the dynaimic implementation will not be assignable to the concrete class you have requested (I am not sure how spring will do this - it will either create the decorator and complain it is unassignable to the injected member type, or worse - inject the non-decorated member that is stripped of AOP capabilities). Losing the AOP capabilities of an injected bean may result in an incorrect behaviour of the injected class.

Notable examples never to use classes for direct injection are persistence services that rely on the transaction management trough the @Transactional annotations. If somehow such a service gets injected, and has no AOP, then you could end up corrupting your database as the transaction manager will not be around to clean up your mess when an exception is thrown. Of course, you will realize the consequences of this later on, when the application users complain, or when the application itself misbehaves.


Some people could object and recommend to use cjlib along spring's AOP instead of the default AOP behavior. This has the effect of making Spring extend your AOP-enabled class instead of using the decorator pattern. While this allows you to inject a class, and its AOP to still work, this approach is considered a bad practice and is mostly rejected by now in favor of the decoration approach. Among the various reasons for this are:

  • Extending is limited to classes that permit it. If your AOP-enabled class, or AOP-enabled method of that class is final, cjlib will fail. Decoration can handle this better.
  • The usage of the decorator pattern enforces the adoption of the programming-to-interfaces paradigm, which is the correct way of designing your software when working with DI containers.
  • It is possible that the DI framework itself, or other third party libraries, to rely on AOP and to expect it to happen via decoration. If you enforce the inheritance approach you may get into deeper trouble.

Upvotes: 2

Related Questions