Interface explosion problem
I am implementing a screen using MVP pattern, with more feature added to the screen, I am adding more and more methods to the IScree开发者_运维百科n/IPresenter interface, hence, the IScreen/IPresenter interface is becoming bigger and bigger, what should I do to cope with this situation?
There is no specific limit on the number of artifacts (methods, constants, enumerations, etc) - say N - in an interface such that we can say if interface X has more than N artifacts, it is bloated.
At least not without a context. In this case, the context is what is the interface supposed to provide?, or better yet, what are implementations of this interface supposed to do? What is the intended behavior or role of classes implementing the interface?
I would strongly suggest you get familiar with certain metrics like cohesion and coupling (both in general and in specifics to OO.) In particular, I'd suggest you take a look at LCOM. Once you understand it, it will help you eyeball situations like the one you are encountering now.
http://javaboutique.internet.com/tutorials/coupcoh/
One of the last things you want to do with an interface or class (or even package or module if you were doing procedural programming) is to turn them into bags of methods and functions where you throw everything but the kitchen sink. That leads to either poorly cohesion or tight coupling (or both.)
One of the problems with interfaces is that we cannot easily compute or estimate their LCOM as one would with actual classes, which could guide you in deciding when to r-efactor. So for that you have to use a bit of intuition.
Let's assume your interface is named A for the sake of argument. Then,
Step 1: Consider grouping the interface methods by arguments: is there a subset of methods that operate on the same type of arguments? If so, are they significantly different from other method groups?
interface A
{
void method1();
void method2(someArgType x);
someOtherType y method3();
...
void doSomethingOn( someType t );
boolean isUnderSomeCondition( someType t )
someType replaceAndGetPrev( someType t, someFields ... )
}
In such a case, consider splitting that group into its own interface, B.
Step 2:
Once you extract interface B, does it look like this?
interface B
{
void doSomethingOn( someType t );
...
boolean isUnderSomeCondition( someType t )
...
someType replaceAndGetPrev( someType t, someFields ... )
}
That is, it represents methods that do things on some type?
If so, your interface is mimicking a procedural module operation on an ADT (in this case, someType) - nothing wrong with if you are using a procedural or multi-paradigm language.
Within reason and while being pragmatic, in OO, you minimize procedures that do things on other objects. You call methods in those objects to do things to themselves on your behalf. Or more precisely, you signal them to do something internally.
In such a case, consider turning B into a class encapsulating the type (and, have it extend an interface with the same signature, but only if it makes sense, if you expect different implementations of artifacts encapsulating/managing elements of that type.)
class Bclass
{
someType t;
Bclass(){ t=new someType();}
...
void doSomethingOn();
...
boolean isUnderSomeCondition()
...
someType replaceAndGetPrev( someFields ... )
}
Step 3: Determine the relationships between the interfaces and classes re-factored out from A.
If B represent things that can only exist when A does (A is a context for B, for example a servlet request exists in a servlet context in Java EE lingo), then have B define a method that returns A (for example A B.getContext() or something like that.)
If B represent things that are managed by A (A being a composite of things, including B), then have A define a method that returns B (B A.getBThingie())
If there is no such relationship between A and B, and they have nothing in common other than they were grouped together, then chances are that the original interface was poorly cohesive.
If you cannot disentangle one from the other without breaking a significant amount of your design, then that's a sign that pieces of your system had poor boundaries and are tightly coupled.
Hope it helps.
ps. Also, I would also avoid trying to fit your interfaces and classes into traditional patterns UNLESS doing so serves an application/business specific purpose. I gotta throw that in there just in case. Too many people run amok with the GoF book trying to fit their classes into patterns rather than asking 'what problem am I solving with this?'
In my opinion, a "perfect program world" contains public interfaces and internal implementations.
Each interface is strictly "in charge" of one thing only.
I try to view these entities is "little" human beings which interact with one another in order to complete a certain task.
(sorry if this is a bit of philosophizing)
What flavor of Model-View-Presenter are you using? I've found that Passive View rarely involves overlap between the view and presenter interfaces - normally they change at different times.
Typically the view's interface is essentially a view model, perhaps something like this (C#-style):
public interface IEditCustomerView {
string FirstName { get; set; }
string LastName { get; set; }
string Country { get; set; }
List<Country> AvailableCountries { get; set; }
// etc.
}
The view implementation usually has handlers for user gestures that are usually thin wrappers that call into the presenter:
public class EditCustomerView {
// The save button's 'click' observer
protected void SaveCustomer() {
this.presenter.SaveCustomer();
}
}
The presenter generally has a method for each user gesture, but none of the data, since it gets that directly from the view (which is generally passed to the presenter in the constructor, though you can pass it on each method call if it's more suitable):
public interface IEditCustomerPresenter {
void Load();
void SaveCustomer();
}
Can you break your interface into sub-interfaces representing sections of the screen? For example, if your screen is divided into groups such as a navigation section, or a form section, or a toolbar section, then your IPresenter
/IScreen
could have getters for interfaces for those sections, and those sections could contain relevant methods for each section. Your main IPresenter
/IScreen
would still have methods that are relevant to the whole interface.
If sections of the screen don't work as a logical category for your application, think of other things that might provide a logical breakdown. Workflow would be one.
EDIT For example:
For example, for a large UI which I did, I actually broke up not just my presenter but also my model and view code. The entire screen neatly broke up into a tree (in this case), with the main presenter delegating work to the children presenters and down the chain. When I had to later go back and add to this UI, I found fitting into the hierarchy fairly simple and maintainable.
In an example that works like this, the MainPresenter
implementation of IMainPresenter
knows about both it's model, it's view, and it's sub-presenters. Each SubPresenter
controls its own view and model. Any operations on what logically belongs in that sub-section should be in the SubPresenter
. If your screen is laid out in such a way that there are logical units like this, such a set-up should work well. Each SubPresenter
should be able to return its SubView
for the MainPresenter
to plug into the MainView
as appropriate.
精彩评论