- Working with Architectural Patterns
- An Overview of Architectural Patterns
- Differences Among MVP, MVC, and MVVM
- Handling UI Events
- How Do the Patterns Work?
- Which Pattern Should You Choose?
- Summary
Differences Among MVP, MVC, and MVVM
From a high-level perspective, these three patterns are similar. They strive to separate the user interface (UI) logic from the business logic and data tier. The differences among the patterns are relatively small, but they are significant.
The main driver for many of the differences described in this section is testability. A goal of any application architecture is to make the code as testable as possible. In any application, the UI (Winforms, Windows Presentation Foundation [WPF] or Silverlight, or ASP) is notoriously difficult to test. Testing the UI classes means the UI must be instantiated, which causes many problems with automated test runners. Therefore, we try to move as much of our logic out of the view and into other classes.
Model Access
One of the differences among these three patterns is access to the data model. In MVC, both the view and the controller have a reference to the model, as illustrated in Figure 2.1 This structure makes it easier to build views because the view can bind to the data model properties directly. However, this means the views are more difficult to reuse outside of the current application because they are bound to a specific data model that might not be used in another application. It also means the binding logic cannot be tested because it resides in the view.
Figure 2.1. Model-View-Controller
MVP does not allow this reference from the view to the model, so the presenter must take on the responsibilities of binding the data model to the input controls, as illustrated in Figure 2.2. This enhances testability because more code is in the presenter rather than the view; however, implementing binding logic without having direct access to the UI controls can be challenging.
Figure 2.2. Model-View-Presenter (Passive view)
MVVM has a concept of a view model that is a class that holds data and logic specifically for that view. It is analogous to the controller or presenter in that it has logic to control the view and that logic is testable. However the question of whether the view should have access to the model is sidestepped with this pattern due to the binding features in WPF. MVVM is used with XAML-based applications, which provides a new way of binding data that does not need a direct reference to the object containing the data. With this new binding method, we can easily refrain from adding any model references to the view. Figure 2.3 shows the relationships in the MVVM pattern.
Figure 2.3. Model-View-ViewModel
View Models
Only the MVVM pattern mentions the existence of a view model, so it would appear that is the only pattern that uses one, but this isn’t true. An emerging standard is to also use a view model with MVC and MVP patterns, but using a different interpretation of the term view model.
Before we discuss the differences in the interpretations of view model, let’s discuss a common thread that runs through all of them. Whether being used in MVC, MVP, or MVVM, a view model is always used as a container for data necessary to display the view. To illustrate this concept, assume you have a view that manages a user and his security permissions. For the view to display the appropriate data, it needs access to the user entity, a list of security attributes granted to the user, a list of all security attributes that exist, and a system configuration setting of the minimum number of characters required in a password. Listing 2.1 illustrates the view, the controller, and the logic necessary to create the view and display it on the screen.
Listing 2.1. A Sample View Interface
using
System.Collections.Generic;namespace
CodeSamples.Ch02_ApplicationArchitecture.Listing01 {public class
SampleView
{private
User
User {get
;set
; }private
ICollection
<UserRole
> AvailableRoles {get
;set
; }private int
RequiredPasswordChars {get
;set
; }public
SampleView (User
usr,ICollection
<UserRole
> roles,int
passwordLen) {this
.User = usr;this
.AvailableRoles = roles;this
.RequiredPasswordChars = passwordLen; }public void
Show() {//
show the view
} }public class
SampleViewController
{public
SampleViewController() {var
repo =new
Repository
();var
usr = repo.GetUserInfo();var
roles = repo.GetUserRoles();var
passwordLen = repo.GetPasswordChars();var
view =new
SampleView
(usr, roles, passwordLen); view.Show(); } }public class
Repository
{public
User
GetUserInfo() {//
get user info from DB
return new
User
(); }public
List
<UserRole
> GetUserRoles() {//
get user roles from DB
return new
List
<UserRole
>(); }public int
GetPasswordChars() {//
get required password length from dB
return
6; } }public class
User
{//
user info goes here
}public class
UserRole
{//
security role info goes here
} }
This sample uses the traditional controller approach by creating the view and setting each property appropriately before showing the view. A presenter pattern looks similar. The advantage of this approach is how obvious the information needed is for this view to function. Simply examining the constructor line shows the necessary inputs. The disadvantage of this approach is the amount of refactoring necessary should the list of required inputs change. If we decided to add a new parameter to the constructor line, we would have to search for every occurrence of this class and add the parameter, forcing a rebuild (and possibly redistribution to the end user) of every affected DLL.
Applying the common strategy of the view model that each presentation pattern supports, we would create a new class that would contain the data the view needs to use. This structure looks like Listing 2.2.
Listing 2.2. A Sample View Interface with View Model
using
System.Collections.Generic;namespace
CodeSamples.Ch02_ApplicationArchitecture.Listing02 {public class
SampleView
{private
SampleViewModel
_viewModel;public
SampleView(SampleViewModel
viewModel) { _viewModel = viewModel; }public void
Show() {//
show the view
} }public class
SampleViewController
{public
SampleViewController() {var
repo =new
Repository
();var
vm =new
SampleViewModel
(); vm.User = repo.GetUserInfo(); vm.AvailableRoles = repo.GetUserRoles(); vm.RequiredPasswordChars = repo.GetPasswordChars();var
view =new
SampleView
(vm); view.Show(); } }public class
SampleViewModel
{public
User
User {get
;set
; }public
ICollection
<UserRole
> AvailableRoles {get
;set
; }public int
RequiredPasswordChars {get
;set
; } }public class
Repository
{public
User
GetUserInfo() {//
get user info from DB
return new
User
(); }public
List
<UserRole
> GetUserRoles() {//
get user roles from DB
return new
List
<UserRole
>(); }public int
GetPasswordChars() {//
get required password length from dB
return 6
; } }public class
User
{//
user info goes here
}public class
UserRole
{//
security role info goes here
} }
Listing 2.1 shows an additional class called SampleViewModel. This class has a property for every piece of information that the view needs to interact with the user as desired. To create and use the view, we create this view model class and fill it with the appropriate data. Though it is more difficult to see the data the view requires, refactoring to add additional information is simplified.
If we decide that the view requires more information, this pattern avoids the need to edit the constructor line of each view creation. However, we still have to visit the places where the view is used to fill the view model with the appropriate data, so this structure doesn’t buy us much.
A common implementation that solves this problem is to push view model population into the view model itself, so the caller needs to create only the view model without needing to know what goes inside of it. Consider the implementation shown in Listing 2.3.
Listing 2.3. A View Model Using Repository
using
System.Collections.Generic;namespace
CodeSamples.Ch02_ApplicationArchitecture.Listing03 {public class
SampleView
{private
SampleViewModel
_viewModel;public
SampleView(SampleViewModel
viewModel) { _viewModel = viewModel; }public void
Show() {//
show the view
} }public class
SampleViewController
{public
SampleViewController() {var
repo =new
Repository
();var
vm =new
SampleViewModel
(repo);var
view =new
SampleView
(vm); view.Show(); } }public class
SampleViewModel
{public
User
User {get
;set
; }public
ICollection
<UserRole
> AvailableRoles {get
;set
; }public int
RequiredPasswordChars {get
;set
; }public
SampleViewModel(Repository
repo) {this
.User = repo.GetUserInfo();this
.AvailableRoles = repo.GetUserRoles();this
.RequiredPasswordChars = repo.GetPasswordChars(); } }public class
Repository
{public
User
GetUserInfo() {//
get user info from DB
return new
User
(); }public
List
<UserRole
> GetUserRoles() {//
get user roles from DB
return new
List
<UserRole
>(); }public int
GetPasswordChars() {//
get required password length from dB
return
6; } }public class
User
{//
user info goes here
}public class
UserRole
{//
security role info goes here
} }
In this code sample, we pass the Repository to the view model when creating it. This enables us to centralize the code necessary to populate the view model appropriately, and it insulates the controller from the need to know what parameters are necessary for the view model. However, notice that the view model properties are all public, so if there are special circumstances where the controller wishes to replace the default values in the view model, that is possible. When using a view model with the MVC or MVP pattern, this is the view model to use.
In this implementation, the only thing that is needed from the caller is the Repository to use. Even this requirement can be removed if we create our Repository inside the view model, but this is not a good idea for various reasons we discuss in Chapter 10, “Establishing the Foundation.”
Adding the logic for the view model to populate itself introduces the difference among all the interpretations of the responsibilities of a view model. The three presentation patterns disagree on the amount of business logic that should be placed in the view model. For strict MVC or MVP implementations, use the view model just like it is—as a convenient container for view data. The other end of the spectrum is the MVVM pattern, which places all of the business logic necessary for the view into the view model. Most MVC or MVP implementations fall somewhere in the middle.
In summary, though the use of a view model is not in the official description of the MVC or MVP patterns; using a view model with the presentation patterns can be useful and is becoming more commonplace. In this book, we stick with the strict definitions of MVP and MVC, which exclude a view model. However, if you like the pattern and want to use it, your preference does not affect the processes recommended here.