public interface Foo{ public int bar(); public int bar(int someArg); }
The interface knows nothing of the implementation details of the concrete class that will implement the interface. In the example below, the Foo interface does not even know of the existence of a FooImpl class which allows FooImpl to change implementation details without impacting the interface at all.
public class FooImpl implements Foo{ private int value; public FooImpl() { this(0); } public FooImpl(int start) { this.value = start; } public int bar() { return this.value++; } public int bar(int someArg) { this.value += someArg; return this.bar(); } }
In some situations, an abstract class can accomplish the same goal as an interface, but it is important to note that in Java, a class is only allowed to extend a single base class, but it can implement any number of interfaces. To understand why using interfaces may be a better solution, I would highly recommend reading Effective Java by Joshua Bloch in which he makes the point over and over again why composition is typically better than inheritance.
As described by Robert C. Martin (Uncle Bob) in his book Clean Architecture, the purpose of an interface is to invert the direction of a dependency. Using the UserAuthenticationService from my previous post about Dependency Injection as an example, if UserAuthenticationService depended directly on a DatabaseUserRepository then every time the DatabaseUserRepository class changes then the UserAuthenticationService will need to be recompiled and redeployed.
By changing UserAuthenticationService to point to a UserRepository interface implemented by DatabaseUserRepository we effectively flip the direction of the dependency.
UserAuthenticationService depends on the UserRepository interface and DatabaseUserRepository depends on the UserRepository interface, but changes to DatabaseUserRepository no longer force changes to UserAuthenticationService.
Interfaces are crucial to the success of Spring. Throughout the Spring Framework, interfaces are defined and often implemented by Spring with "opinionated defaults", but Spring allows developers to replace the provided implementation with their own via different configuration mechanisms. By using interfaces, the Spring Framework does not need to be recompiled to accept one of these newly provided implementations and everything still works (assuming the interface was implemented according to the specs by the newly provided implementation).
The use of interfaces especially comes in handy when it comes time to test our software. Operationally, our UserAuthenticationService may be configured to use a DatabaseUserRepository instance, but that may not work well for a unit test environment where we do not want to rely on the state of a shared database that may not be available. In that case, UserAuthenticationService could be provided with a MockUserRepository that implements the UserRepository interface and stores configured values in memory. This allows our unit tests to run in isolation, but the implementation of UserAuthenticationService does not need to change (or even know) that it's interacting with a different object.
No comments:
Post a Comment