Вы находитесь на странице: 1из 8

Model View Controller Explained

Model view controller (MVC) is a very useful and popular design pattern. If youre writing software, you should know it. Unfortunately its also one of the hardest to truly understand. In this article I will provide what I think is the simplest explanation of MVC, and why you should use it.

What is Model View Controller (MVC)?


In a typical application you will find these three fundamental parts:

Data (Model) An interface to view and modify the data (View) Operations that can be performed on the data (Controller)

The MVC pattern, in a nutshell, is this: 1. The model represents the data, and does nothing else. The model does NOT depend on the controller or the view. 2. The view displays the model data, and sends user actions (e.g. button clicks) to the controller. The view can: 1. be independent of both the model and the controller; or 2. actually be the controller, and therefor depend on the model. 3. The controller provides model data to the view, and interprets user actions such as button clicks. The controller depends on the view and the model. In some cases, the controller and the view are the same object. Rule 1 is the golden rule of MVC so Ill repeat it: The model represents the data, and does nothing else. The model does NOT depend on the controller or the view. Lets take an address book application as an example. The model is a list of Person objects, the view is a GUI window that displays the list of people, and the controller handles actions such as Delete person, Add person, Email person, etc. The following example does not use MVC because the model depends on the view.
//Example 1: void Person::setPicture(Picture pict){ m_picture = pict; //set the member variable m_listView->reloadData(); //update the view }

The following example uses MVC:

//Example 2: void Person::setPicture(Picture pict){ m_picture = pict; //set the member variable } void PersonListController::changePictureAtIndex(Picture newPict, int personIndex){ m_personList[personIndex].setPicture(newPict); //modify the model m_listView->reloadData(); //update the view }

In the above example, the Person class knows nothing about the view. The PersonListController handles both changing the model, and updating the view. The view window tells the controller about user actions (in this case, it tells the controller that the user changed the picture of a person).

What is the advantage of MVC?


Unnecessary complexity is the devil of software development. Complexity leads to software that is buggy, and expensive to maintain. The easiest way to make code overly complex is to put dependencies everywhere. Conversely, removing unnecessary dependencies makes delightful code that is less buggy and easier to maintain because it is reusable without modification. You can happily reuse old, stable code without introducing new bugs into it. The primary advantage of the MVC design pattern is this: MVC makes model classes reusable without modification. The purpose of the controller is to remove the view dependency from the model. By removing the view dependency from the model, the model code becomes delightful. Why is the model code so delightful? Lets continue with the address book application example. The project manager approaches the developer and says We love the contact list window, but we need a second window that displays all the contacts by their photos only. The photos should be in a table layout, with five photos per row. If the application uses MVC, this task is pretty straight forward. Currently there are three classes: Person, PersonListController, and PersonListView. Two classes need to be created: PersonPhotoGridView and PersonPhotoGridController. The Person class remains the same, and is easily plugged into the two different views. How delightful. If the application is structured badly like in Example 1, then things get more complicated. Currently there are two classes Person, and PersonListView. The Person class can not be plugged into another view, because it contains code specific to PersonListView. The developer must modify the Person class to accommodate the new PersonPhotoGridView, and ends up complicating the model like so:

//Example 3: void Person::setPicture(Picture pict){ m_picture = pict; //set the member variable if(m_listView){ //check if it's in a list view m_listView->reloadData(); //update the list view } if(m_gridView){ //check if it's in a grid view m_gridView->reloadData(); //update the grid view } }

As you can see, the model code is starting to turn nasty. If the project manager then says were porting the app to a platform with a different GUI toolkit the delightfulness is even more prominent. With MVC, the Person class can be displayed by different GUI toolkits without any modification. Just make a controller and a view with the new toolkit, just as you would with the old toolkit. Without MVC, it is a nightmare to support multiple GUI toolkits. The code may end up looking like this:
//Example 4: void Person::setPicture(Picture pict){ m_picture = pict; # ifdef ORIGINAL_GUI_TOOLKIT if(m_listView){ //check if it's in a list view m_listView->reloadData(); //update the list view } if(m_gridView){ //check if it's in a grid view m_gridView->reloadData(); //update the grid view } # endif # ifdef NEW_GUI_TOOLKIT if(m_listView){ //check if it's in a list view m_listView->redisplayData(); //update the list view } if(m_gridView){ //check if it's in a grid view m_gridView->redisplayData(); //update the grid view } # endif }

The setPicture function is basically spaghetti code at this point.

Why not put the controller code in the view?


One solution to the spaghetti code problem in Example 4 is to move the controller code from the model to the view like so:
//Example 5: PersonListView::newPictureClicked(Picture clickedPicture){ m_selectedPerson.setPicture(clickedPicture); this->reloadData();

The above example also makes the model reusable, which is the main advantage of MVC. When the view will only ever display one type of model object, then combining the view and the controller is fine. For example, a SinglePersonView will only ever display a Person object, so the SinglePersonView can double as the controller. However, if the controller is separate from the view then MVC has a second advantage: MVC can also make the view reusable without modification. Not only does MVC make the model delightful, it can also make the view delightful. Ideally, a list view should be able to display lists of anything, not just People objects. The code in Example 5 can not be a generic list view, because it is tied to the model (the Person class). In the situation where the view should be reusable (e.g. a list view, or a table view) and the model should be reusable, MVC is the only thing that will work. The controller removes the dependencies from both the model and the view, which allows them to be reused elsewhere.

Conclusion
The MVC design pattern inserts a controller class between the view and the model to remove the model-view dependencies. With the dependencies removed, the model, and possibly the view, can be made reusable without modification. This makes implementing new features and maintenance a breeze. The users get stable software quickly, the company saves money, and the developers dont go insane. How good is that?

Model View Controller Mechanisms


The previous article, Model View Controller Explained, explained what MVC is and why its such a good design pattern. The model view controller design pattern has a flow. Actions flow from the view to the model via certain pathways. Conversely, changes to the model flow through to the view. This article will dive into finer detail about these pathways, and discuss some of the specific mechanisms and design patterns used to implement the flows.

Observing The Model For Changes


The model will change for a number of reasons. It will change in response to user events. It can also change based on internal events such as timers, data coming in over the network, etc. When the model changes, the view needs to be notified so it can display the new information. Watching for changes in an object is a common task, and is popularly known as observation. There are numerous design patterns that deal with observation; all are slightly different, but have the same general purpose. One such pattern is the gang of four Observer Pattern. Here is an example of the pattern in Java:
// Example 1 interface Observer { void observeChange(Object changedObject); } interface Observable { void addObserver(Observer o); void removeObserver(Observer o); } class PersonModel implements Observable { // ... (code omitted) public void setName(String name){ m_name = name; for(Observer o : m_observers){ o.observeChange(this); } } } class PersonView implements Observer { // ... (code omitted) public void setModel(PersonModel pm){ m_model.removeObserver(self); m_model = pm; m_model.addObserver(self); }

public void observeChange(Object changedObject){ if(changedObject == m_model){ this.updateView(); } } }

In the above example, the view knows when the model has changed because it adds itself as an observer. J2SE actually provides an Observer interface and an Observable class that perform the same function (but differ to the example). For brevity, the above example doesnt contain a controller, but the controller could be the Observer object. One drawback is that it is difficult to tell what has actually changed in the model. Maybe only a tiny part of the view needs to be redrawn, but because view doesnt know that, it has to redraw everything which may be an expensive operation.

Listening For Change Notifications From The Model


Another method of observation is through notifications. Notifications are very similar to the observer pattern, but are more flexible. I really like the way notifications are handled in Cocoa, and the Cocoa documentation on notifications explains notifications well. Basically, the model object broadcasts different types of Notification objects, possibly to a NotificationCenter object. Conversely, views/controllers register to receive specific types of Notification objects. The model doesnt know or care about who is listening, so it doesnt have to manage a list of observers. Also, notifications can be specific enough to avoid the redraw everything whenever the tiniest change happens problem with the observer pattern. The NotificationCenter can also do cool things like coalescing. Coalescing can be used to stop floods of update messages. For example, the model might be changed 5000 times in a fraction of a second. Instead of redrawing the view 5000 times, the NotificationCenter can coalesce all the update the view notifications into a single notification, meaning that the view is redrawn just once at the end.

Catching User Actions With The Signal/Slot Design Pattern


Subclassing every button and overriding onClick() is a tedious and unnecessary way to intercept user actions. One better (but still nasty) alternative is to tag every button with a string or a number, and have a big handling function like so:
// Example 2 void SomeWindow::handleClick(int buttonId){ switch(buttonId){ case OK_BUTTON: okClicked(); break;

case CANCEL_BUTTON: cancelClicked(); break; //etc. } }

What were are aiming for is something that performs the logic when button X is clicked, call function Y, and that is what the signal/slot design pattern does. View classes emit certain signals (e.g. button was clicked, finished editing text). The controller class has certain slots (e.g. delete contact, set contact name, etc). Any signal can be hooked up to any compatible slot at run time. Signals can send arguments to slots, and a compatible slot is a slot that can handle the arguments. For example the finished editing text signal may send a single string argument, and the set contact name slot accepts a single string argument, so they are compatible. Th signal/slot pattern is easier to implement in languages with dynamic typing and firstclass functions, but can still be done in languages such as C++. Examples of signal/slot implementations can be seen in Cocoas target/action mechanism (Objective-C), Boost.Signals (C++), and Qt Signals and Slots (C++ and other languages).

Catching User Actions With Delegation


Delegation is a pattern where an object sets itself as a delegate of a second object. The second object calls functions on the delegate to inform it when events occur, or ask it for information. Here is an example of a possible table view delegate in Objective-C:
// Example 3 @implementation TDTableView // ... (code omitted) -(void)mouseDown:(NSPoint)point { int rowClicked = [self rowAtPoint:point]; if(m_delegate && [m_delegate respondsToSelector:@selector(tableView:rowWasClicked:)]){ [m_delegate tableView:self rowWasClicked:rowClicked]; } } // ... (code omitted)

In the above example, the table view keeps an object m_delegate. m_delegate can be any class of object, which reduces the dependency between the view and whatever the delegate class is (probably the controller). When the view receives a click, if the delegate is set and the delegate has a function called tableView:rowWasClicked:, then that function is called on the delegate object. Here is a way you may implement this in C++:
// Example 4 class TableViewDelegate {

virtual void tableViewRowWasClicked(TableView tableView, int rowClicked); virtual void tableViewColumnWasClicked(TableView tableView, int columnClicked); //... (more delegate functions here) }; class TableView { private: TableViewDelegate* m_delegate; public: void setDelegate(TableViewDelegate* delegate); // ... (code omitted) }; void TableView::mouseDown(Point p) { int rowClicked = rowAtPoint(p); if(m_delegate){ m_delegate->tableViewRowWasClicked(this, rowClicked); } }

public:

The above example uses a technique called dependency injection to decouple the TableView from its delegate. In the MVC pattern, the delegate would be the controller object, and would therefor inherit from TableViewDelegate.

Conclusion
This article has discussed four common mechanisms used in MVC: the observer pattern, notifications, the signal/slot design pattern, and the delegation design pattern. Observation and notifications are useful for propagating changes from the model to the view, and signals/slots and delegation are used by the view to trigger changes in the model through the controller

Вам также может понравиться