Академический Документы
Профессиональный Документы
Культура Документы
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.
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 }
//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).
//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 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?
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.
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).
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