The Model-View-Controller Pattern


By Christoffer Skjoldborg

The intent of the Model-View-Controller pattern (MVC) is to break UI behavior up into separate pieces in order to increase reuse possibilities and testability. The three pieces are the View, the Model, and the Controller.

The Model is some representation of entity state. It can range from simple data structures (XML documents, DataSets, and Data Transfer Objects) to a full-blown Domain Model if you have one.

The View should consist only of logic directly related to "painting the pixels on the screen." Keeping the View as "dumb as possible" is often the major design objective when applying MVC. The rationale is that Views/Screens in practice require a human eye to be tested. This is an error prone and costly process compared to automated unit tests. If we minimize the logic we put in the Views in the first place, we minimize the risk of introducing bugs that can only be detected by manual testing, or, in other words, we minimize the manual testing efforts we have to apply.

Finally, the Controller, which is really the heart of the MVC, is the glue that ties the Model and the View together. The Controller receives messages when the user interacts with the View, translates those messages into actions that are performed on the underlying Model, and then updates the View accordingly.

The MVC Pattern has its roots in the Smalltalk community back in the 70s and 80s [MVC History].

As mentioned, one of the main reasons for introducing MVC is to increase testability. Other benefits are as follows:

  • The very process of explicitly extracting the code from the Views does tend to force more structure upon the UI code than would otherwise have been the case. Many modern tools make it very easy to just click your way around and quickly add code behind various events without thinking too hard. By deploying the MVC pattern, one must exercise a bit more effort and be very explicit about what goes on in the UI.

  • It makes it easier to change the look and feel or even support entire new UIs or even Platforms. The reuse value in MVC is sometimes exaggerated a bit, especially when it comes to cross platform (rich client/Web/handheld device) portability. Often the Controller will have to be modified significantly in order to make full use of the target platform's capabilities.

  • Different team members can work simultaneously on the View and the controller, even if you use a file-locking source control system, because they can now easily be separated.

Example: Joe's Shoe Shop

The MVC pattern is a very loosely formulated design pattern, which means that you are faced with numerous implementation detail decisions to be made when using it. This can indeed be a staggering experience, so before we dig into the various variations of MVC I'll show you a sample implementation that will get you started. As with most other book examples, I have to keep it fairly simplistic in order to preserve the overview. However, it is not very far from what I would do in a real-world application of the same complexity, and when I'm done with the example I will elaborate on some of the key areas where you might want to improve on the foundation laid in the sample and offer a few suggestions on how to do that.

I'm using C# and Windows Forms here, but the code should be simple enough to be understood even if you are not familiar with C# and Windows Forms.

Consider a product catalog for a shoe shop, Joe's Shoe Shop. Joe only sells two types of shoes: dancing shoes and safety shoes. Dancing shoes have a hardness property and safety shoes a maximum pressure. All shoes have an ID, name, and price. The screen that Joe uses to maintain his catalog could look something like this:

Figure 11-1. Joe's Shoe ShopCatalog screen


Shoes can be viewed, added, removed, and updated. When a new shoe has been saved, the ID cannot change anymore. When Joe selects a dancing shoe, he expects to see the hardness of the shoe, and when he selects a safety shoe, he expects to see the maximum pressure that the shoe can withstand. While this View is probably not the most complex you have ever seen, it does have a few important and common concepts such as a grid and parts of the screen that dynamically need to change based upon actions in other parts in the screen.

In order to detach logic from the View, we first determine roughly which events/user interactions we think we need to handle. In this case, it would be events such as the following:

  • LoadView to load data and instantiate the entire View when first brought up.

  • SelectedShoeChanged would occur when the user changes the active shoe.

  • AddNewShoe when the user clicks the Add New button.

  • RemoveShoe when the user clicks the Remove button.

  • ChangeType needs to change the description of the variant label (Hardness or Max Pressure) as well as clear the value.

  • Save saves the changes made by the user.

All these events would be prime candidates for methods on the Controller class. Even though the MVC pattern formally states that the Controller should receive the events and act upon the View, it is often more practical to have the View subscribe to the events and then delegate the handling onto the Controller. (This slight variation on the basic MVC is called Model-View-Presenter by Martin Fowler [Fowler PoEAA2].)

Now we have a fairly good idea of what kind of logic we would like to put in the Controller, but we still need to enable the Controller to talk to the View. Remember, we strive to make the View as dumb as possible, so we'd prefer to make the Controller do all the hard work and just hand the View some simple instructions that do not require any further processing. We do this by defining an interface, ICatalogView, which the View must implement.

public interface ICatalogView {     void SetController(CatalogController controller);     void ClearGrid();     void AddShoeToGrid(Shoe shoe);     void UpdateGridWithChangedShoe(Shoe shoe);     void RemoveShoeFromGrid(Shoe shoe);     string GetIdOfSelectedShoeInGrid();     void SetSelectedShoeInGrid(Shoe shoe);     string Id{get;set;}     bool CanModifyId{set;}     string Name{get;set;}     ShoeType Type{get;set;}     string Price{get;set;}     string VariantName{get;set;}     string VariantValue{get;set;} }


At first glance, some of the method names in the ICatalogView interface may look like they expose some fairly complex functionality, but in reality the functionality is strictly related to painting pixels on the screen, interacting with various UI widgets, enabling/disabling buttons, and so on. A subset of the implementation is shown in the following code listing.

Note

This section will focus on the scenario of loading the View with the list of shoes, and that goes for all the shown code regarding the View, the Controller, and the test.

If you'd like to see the complete codebase, you can download that from this book's Web site at www.jnsk.se/adddp.


public void SetController(CatalogController controller) {     _controller=controller; } public void ClearGrid() {     // Define columns in grid     this.grdShoes.Columns.Clear();     this.grdShoes.Columns.Add("Id", 50         , HorizontalAlignment.Left);     this.grdShoes.Columns.Add("Name", 300         , HorizontalAlignment.Left);     this.grdShoes.Columns.Add("Type", 100         , HorizontalAlignment.Left);     this.grdShoes.Columns.Add("Price", 80         , HorizontalAlignment.Right);       this.grdShoes.Items.Clear(); } public void AddShoeToGrid(Shoe shoe) {     ListViewItem parent;     parent=this.grdShoes.Items.Add(shoe.Id);     parent.SubItems.Add(shoe.Name);       parent.SubItems.Add(Enum.GetName(typeof(Shoe.ShoeType),     shoe.Type));       parent.SubItems.Add(shoe.Price.ToString()); } public void SetSelectedShoeInGrid(Shoe shoe) {     foreach(ListViewItem row in this.grdShoes.Items)     {         if(row.Text==shoe.Id)         {             row.Selected=true;             break;         }     } }


The SetController() enables us to tell the View which Controller instance it must forward events to, and all event handlers simply call the corresponding "event" method on the Controller.

You might notice that several methods on the ICatalogView interface make use of a Shoe object. This Shoe class is a Model class. It is thus perfectly fine to have the View know about the Model. In this example, the Shoe is an extremely simple domain class with no behavior, whereas in a real-world Domain Model you would probably have much more functionality in the domain classes.

public class Shoe {     public enum ShoeType     {         Dancing=1,         Safety=2     }     public string Id;     public string Name;     public ShoeType Type;     public decimal Price;     public string VariantValue;       public Shoe(string id, string name, ShoeType type,         decimal price, string variantValue)     {         Id=id;         Name=name;         Type=type;         Price=price;         VariantValue=variantValue;     } }


Note

You might wonder why I use the singular "Shoe" and not "Pair of Shoes" or something similar as a class name. I'm simply staying with the lingo used by shoe professionals here. I've noticed that they always say "this shoe" and "that shoe" where others would say "this pair of shoes" or "these shoes." This, plus the word Shoe is more straightforward as a class name than PairOfShoes.


At this point we have two pieces of the puzzle: the View and the Model. Both are mainly concerned with state; the Model holds the in-memory state in a structured format, and the View holds the visual representation as well as means to modify the state. Let's hook up the two through the Controller. We already have a rough idea of the types of events we need to handle, but in order to get the specifics pinned down, I'll start out by writing tests for each of the events. In the tests I will be using NMock [NMock].

You'll find the code in the next listing, but let me first comment on the pieces. The getTestData() is a private helper method that builds up a list of shoes in memory. This is used as the Model in the remainder of the test suite.

There is one test for each identified method on the Controller, as I said, we will focus here on the scenario of loading the View with data, and therefore the test that is shown is the LoadView_test().

First I create the viewMock that represents the View in the test, and then I create the shoes list (the Model), and finally the Controller (what I want to test). Notice how the Controller is fed the mock implementation of the View in its constructor. It will operate on this mock just as it would a real View because all the Controller knows about the View is the ICatalogView interface.

Next I tell the viewMock which calls to expect and with what parameters. These represent the "orders" I expect our dumb View to receive from the Controller. I then call the event handler I want to test in the Controller, and finally I ask the viewMock to verify that the expectations were met. Behind the scenes, the viewMock will assert that each of the expected calls were received and issued with the right parameters. The following listing shows the code.

private IList getTestData() {     IList shoes=new ArrayList();     shoes.Add(new Shoe("1001","Dancing     Queen",Shoe.ShoeType.Dancing,199m,"Soft"));     shoes.Add(new Shoe("1002",     "Heavy Duty Master",Shoe.ShoeType.Safety,299m,"500 lbs"));        shoes.Add(new Shoe("1003",     "Tango Beginner",Shoe.ShoeType.Dancing,149m,"Medium"));     return shoes; } [Test] public void LoadView_test () {     DynamicMock viewMock=new DynamicMock(typeof(ICatalogView));     IList shoes=getTestData();     CatalogController ctrl = new CatalogController         ((ICatalogView)viewMock.MockInstance, shoes);     // Set expectations on view mock     viewMock.Expect("ClearGrid");     viewMock.Expect("AddShoeToGrid",new object[]{shoes[0]});     viewMock.Expect("AddShoeToGrid",new object[]{shoes[1]});     viewMock.Expect("AddShoeToGrid",new object[]{shoes[2]});     viewMock.Expect("SetSelectedShoeInGrid"         , new object[]{shoes[0]});        ctrl.LoadView();     viewMock.Verify(); }


With the tests all in place (although only one was shown in the previous listing), all that is left is to show the actual implementation of the CatalogController (see the following code listing).

public class CatalogController {     ICatalogView _view;     IList _shoes;     Shoe _selectedShoe;     public CatalogController(ICatalogView view, IList shoes)     {         _view=view;         _shoes=shoes;         view.SetController(this);     }     public void LoadView()     {         _view.ClearGrid();         foreach(Shoe s in _shoes)             _view.AddShoeToGrid(s);         _view.SetSelectedShoeInGrid((Shoe)_shoes[0]);     } }


Simplifying the View Interfaces Through Adapters

In Joe's Shoe Shop, the ICatalogView exposes a property for every possible detail that the Controller needs to access. It is perfectly OK to do so, but you might end up with very large interfaces if you have 10 input boxes and you need to be able to control the visibility, enabled, font, back color, fore color, border style, and so on of each of them. To prevent this problem you can create an Adapter/Wrapper [GoF Design Patterns] for each type of UI control you use. The Adapter for a TextBox could expose the mentioned properties, and the View interface would then only have to expose one property of the TextBoxAdapter data type for each textbox. Behind the scenes, the View would then provide references to the real UI controls to each Adapter during instantiation and the Adapter would simply delegate the call it receives to the actual implementation.

Decouple the Controller from the View

It can sometimes be advantageous to decouple the Controller from the View. This can be done in several ways. A simple solution is to create an interface for the Controller and have the View know about that only and not the actual implementation. A more complex way is to have Views raise events to anonymous subscribers (Observer pattern [GoF Design Patterns]). I'm not a big fan of this approach as I cannot imagine why one View would need to communicate user interactions to multiple subscriberslet alone subscribers of unknown types! If different Controllers or even other Views need to know about a change to a particular View, the Controller for that View should notify them. This can be done through events if neededthe point is that the Controller should have 100% control of who gets to know about it.

Regardless of the implementation details, the advantage of decoupling the Controller from the View is that we can vary the Controller behind the View. This can be very useful when a View can show up in a lot of different contexts, and it might not make sense to always have to enforce a particular Controller to go along with it. An example of this could be special logic that has to be applied to the View for particular customers. In this case, we could have a factory method that serves up the appropriate Controller depending on which customer the system is dealing with.

Combining Views/Controllers

More complex Views are often made up of multiple smaller Views. Examples are wizards where the user navigates through a bunch of Views through next and previous buttons or maybe even jump back and forth several steps at a time. Information might be saved along the way, but it does not really get committed until the user clicks Save on the final screen.

Another example is Master/Details Views where changes made to the details View might cause changes in the Master View as well.

Both examples can technically be coded with one huge Controller, but it will most likely be a messy experience minimizing the reuse possibilities. We may want to use some of the steps in the wizard elsewhere in the application, or we may want to show the Master View without the Detail View, for example. The solution here is obviously to separate the View and Controllers into smaller pieces. ("If you want to eat an elephant you have to do it in small pieces.") In the case of the wizard, we could have a FlowController that controls the basic flow back and forth through the wizard, and each step could have its own View and Controller.

In the Master/Detail, we might have the Controller for the Detail View raise an event on changes. Any other Controller could then subscribe to this event, including the Controller for the Master View.

Is It Worth It?

The key benefit of applying the MVC pattern is that it makes your UI code testable. Secondarily, it forces a very structured approach onto the coding/design process of the UI, which may in itself cause cleaner code in the end. You're forced to think a little more, so to speak.

On the flip-side it should be noted that just because we can test every line of code does not mean we should. With the MVC in place, it is possible to write an extensive test suite for every View, and it sure feels good the first couple of times getting a bunch of green bars for the UI logic. However, the value of tests that test something very simple that is unlikely to get broken with future changes because of low interdependence with other parts of the system may not be that huge. Especially if those UI test suites impose huge maintenance overhead on your codebase. Every time you change the UI slightly, you have to make a lot of changes to the corresponding UI tests.

Thanks, Chris!

Whether we want to test the UI with rigorous unit tests or not, I think what Christoffer just showed us is a very valuable technique for making the view part of a Windows Forms application thinner, so it's just about painting the forms and for factoring out some of the presentation logic into a layer of its own.

That's pretty much the theme of the next section as well, but this time in the context of the Web. Ingemar Lundberg discusses how he applies the MVP pattern [Fowler PoEAA2] as the tool for testing a Web application.




Applying Domain-Driven Design and Patterns(c) With Examples in C# and  .NET
Applying Domain-Driven Design and Patterns: With Examples in C# and .NET
ISBN: 0321268202
EAN: 2147483647
Year: 2006
Pages: 179
Authors: Jimmy Nilsson

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net