The Open/Closed Principal (OCP) is a fundamental object-oriented design principal that states:
“Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”
This means that we should be able to add new behavior to a software entity without altering its code.
Most UI code I have seen, including Model-View-Presenter (MVP) and Model-View-Controller (MVC) implementations, clearly violate the open/closed principal.
When developing UI code, we tend to create presentation classes in terms of views, or sections of the UI. To add new behavior to the UI, we need to modify these presentation classes. This increases the risk of breaking existing functionality.
UI development can be notoriously complex because of the many interactions and state changes that can occur. To manage this complexity we should ensure that our classes have only one reason to change.
Instead of grouping presentation classes into visible sections of the UI, maybe we should be creating a series of discrete presentation behaviors that react to events raised from the view.
These behaviors can be added and removed without affecting other behaviors on the page.
Behavior-Driven Development (BDD) advocates creating tests based on individual behaviors, so why not create our presentation classes in the same way? The tests then correspond directly to a single discrete unit, rather than a behavior that occurs within a larger structure, e.g. a presenter class.
Each behavior has a name that describes the behavior it represents. Using a descriptive name provides instant documentation of the UI behavior. It should be easy for someone to understand what the UI does simply by looking at the behavior names. If a problem occurs, it should be easy to identify and isolate the affected behavior.
Implementing Presentation Behaviors
I have created a simple music finder application that demonstrates an implementation of presentation behaviors.
The sample application contains a single page, represented by an IFindMusicView interface. The behaviors respond to events raised by this View and update the View accordingly.
A typical behavior can be defined as:
Given… a particular scenario
When… an event occurs
And… all conditions have been met
Then… do something
And… do something else
Each behavior is implemented as a class that derives from a base class with an IBehavior interface. This interface contains two methods: When() and Then().
The When() method contains code that registers the behavior with a certain event on the page. The Then() method contains the code that responds to the event if all conditions have been met. The “Given” aspect is implemented by the class constructor, which takes the view and any associated dependencies.
1: public class when_song_list_is_empty_disable_select_button : FindMusicBehavior
3: public when_song_list_is_empty_disable_select_button(IFindMusicView view)
4: : base(view)
8: public override void When()
10: View.AfterSongsLoaded += (sender, e) => Then();
13: public override void Then()
15: View.IsSelectButtonEnabled = View.SongsList.Count > 0;
The behaviors are instantiated by the View, but an Inversion Of Control container could be used to register the behaviors at run-time. The View then wouldn’t need to know anything about the behaviors that are implemented for it. We could then drop-in and drop-out behaviors without needing to change the existing code.
Although this is an interesting concept, I am yet to implement it on a large-scale application. There are several areas I need to investigate further, such as:
- Can one behavior extend the functionality of another behavior?
- Can parallel behaviors remain independant from one another?
- How does this work with MVC frameworks? Are the behaviors triggered by actions?
- How well does this scale?
Does this concept make sense? Does it sound practical? Could it potentially solve some of the issues we face with developing complex UI code? Any feedback would be greatly appreciated.