Role Specific Interfaces: DIP And ISP In Action
I do most of my UI development – in ASP.NET WebForms and in WinForms – with a Model-View-Presenter setup. It helps me keep my application logic separate from my view implementations, makes it possible to unit test the presenters, etc. I also like to use custom controls – often with their own presenter – to help encapsulate UI related process and keep my UI implementations clean. The challenge with custom controls is getting them to converse to each other and getting the parent form to converse with the controls. My favorite way of solving this challenge is through simple messaging patterns. This gives you a lot of control and ensures your system is nice and decoupled. Of course, there is a cost/benefit tradeoff that needs to be considered. There may not need the indirection and potential complexities that come along with those solutions. The system in question may not need a messaging system, event aggregator, command pattern or whatever else. There are times when its easier and makes more sense to forego these patterns and have the presenters talk directly to each other.
Role Specific Interfaces
When the cost of the messaging pattern architecture out-weighs the benefits, stick to simple abstractions that still keep the presenters decoupled by one layer. This can easily be done with an interface or abstract base class in static languages like C#, Java and C++. However, don’t take the easy way out in this abstraction and creating a one-to-one mapping between the abstraction and the implementation. Doing so will create a semantic coupling between the two presenters.
For example, the IProductCodeSelectionPresenter may have the following definition:
1: public interface IProductCodeSelectionPresenter
3: void Initialize();
4: void ProductCodeSelected(ProductCode code);
5: ProductCode GetSelectedProductCode();
6: void SelectionCancelled();
7: void SelectionConfirmed();
Which of these methods should another presenter call in order to retrieve the ProductCode? Should GetSelectedProductCode be called? Does this method guarantee the view to select a product code was run and that the product code has been specified by the user? Or maybe the ProductCodeSelected method should be called instead, or Initialize or … This easy-to-create interface may cause semantic coupling by forcing another developer to look at the implementation in order to know which methods should be called, when.
It would be better to define a role that the presenter is playing in the communication and create an interface that is specific to that role. In this situation, the name of the presenter provides some insight to what role the presenter is playing – product code selection. A simple role specific interface for this presenter may look like this:
1: public interface IProductCodeSelector
3: ProductCode GetProductCode();
With an interface defined like this, a developer calling this code will not have any confusion on what needs to be called. There is no need to look at the implementation of the interface, and semantic coupling has been avoided. Making a call to this interface is easy.
The Interface Segregation Principle (ISP)
The driving principle in making the decision to create the role specific interface is often the Interface Segregation Principle (ISP). This principles says that we should not force a client – the code that is calling out to our interface – to know about methods and properties that it does not need.
In this case, the client code does not need to know that the interface sits on top of a presenter. Therefore, take the name “presenter” out of the interface that the client calls. This gives the interface more flexibility for the future and prevents the client code from knowing that a view and user input is likely to be the implementation. The client code also doesn’t need to know about the Initialize, ProductCodeSelected and other methods that the presenter has. These methods are specific to the interactions between the View implementation and the Presenter – a different role that the presenter is playing. By removing these methods from the interface, the client code is no longer bound to the knowledge of which methods should and should not be called, when.
The Dependency Inversion Principle (DIP)
The Dependency Inversion Principle (DIP) may also be at play in this scenario. DIP is not just about creating an abstraction and passing it into a constructor. That would only be dependency abstraction and dependency injection. Rather, DIP talks about abstraction ownership. In the case of a role specific interface, the owner of that interface is the code that depends on it – the client code that calls out to it.
If another presenter, such as a ProductDefinitionPresenter, is the driving force behind the need to create the IProduceCodeSelector interface, then this presenter should own that abstraction. This means that the ProductDefinitionPresenter determines what that interface looks like. What methods and properties are available, and the name of the interface are all driven by the needs of the ProductDefinitionPresenter.
1: public class ProductDefinitionPresenter
3: private IProductCodeSelector ProductCodeSelector;
5: public ProductDefinitionPresenter(IProductCodeSelector productCodeSelector)
7: ProductCodeSelector = productCodeSelector;
10: public void SelectProductCode()
12: var productCode = ProductCodeSelector.GetProductCode();
13: //do something with the productCode, here
There is not syntax or markup that declares ProductDefinitionPresenter as the owner of this interface, in this example. That responsibility is left to the standards, conventions and organizational means of the system in question and the team that maintains it.
Model-View-Presenter scenarios are not the only place that roles need to be considered. Any time two or more objects interact and there is a need for them to be decoupled, the roles that the objects are playing need to be considered. There are likely other principles and patterns that come into play when considering a role specific interface, as well. Each scenario’s needs must be considered for their own reasons, and ISP and DIP may not always be at play when defining an interface for an object. And role specific interfaces are not always needed. There are other benefits to creating interfaces or other abstractions that can be referenced in place of concrete implementations such as dependency injection, general decoupling, creating service layer or other context specific barriers, etc.