The benefit of visual use of the MVC (Model-view-control) pattern in our development projects is that it can completely reduce the interaction between the business layer and the application presentation layer. In addition, we will have a completely separate object to manipulate the presentation layer. The independence between the objects and layers that MVC provides in our project will make our maintenance simpler and easier for our code reuse (as you'll see below).
As a general rule, we know that we want to keep the minimum dependencies between objects so that changes can be easily met, and we can reuse the code we've worked so hard to write. To achieve this, we will follow the general principle of using the MVC pattern for interfaces, rather than classes.
Our mission, if we choose to accept it ...
We were appointed to build an Acme Sports car project, our task is to make a simple Windows screen to show the direction and speed of the vehicle, so that end users can change direction, accelerate or slow down. Of course there will be a range of extensions.
There has been a rumor at Acme that if our project succeeds, we will eventually have to develop a similar interface for ACME 2 Pickup Truck and Acme 1 tricycle. As a developer, we also know that the Acme management team will ultimately ask, "is this so great that we can see it on our intranet?" "All of this comes to mind and we want to deliver a product that can be easily upgraded so that we can have food in the future."
So, at the same time, we decided, "This is a perfect scenario for using MVC."
Overview of our architecture
Now that we know we're going to use MVC, we need to point out its nature. The three parts of MVC are derived from our experiments: Model,control and view. In our system, model is our car, view is our picture, control will link these two parts.
To change model (our ACME sports car), we need to use control. Our control will produce a request to model (our ACME sports car), and the update View,view is our screen (UI).
This looks simple, but here's the first problem to solve: what happens when end users want to make a change to Acme sports car, such as acceleration or steering? They will use control to present a change application through view (our Windows Form).
Now we're left with one unsolved problem. What if the view doesn't have the necessary information to show the status of model? We need to add an arrow to our diagram: View will be able to apply the state of model to get the relevant state information it wants to display.
Finally, our end user (driver) will interact with our Acme Vehicle control system through the view. If they want to make an application that changes the system, such as increasing the acceleration, the application will be issued from the view to be handled by the control.
Control will apply to model to change and reflect the necessary changes to the view. For example, if an overbearing driver makes a "floor it" application to Acme Sports car, and now travels too fast to turn, then control will reject the application and notify in view, thus preventing a tragic serial collision in traffic jams.
Model (the ACME Sports car) will notify the view that its speed has been improved, and view will do the appropriate updates.
In conclusion, this is the summary we will build:
Start
As a developer who always wants to be a little bit more, we want our system to have a long and good lifecycle. This means being able to get ready to meet many of the changes in Acme. To do this, we know that we have to follow two principles ... "Ensure your class is low coupling", to achieve this goal, but also "to interface programming."
So we're going to do three interfaces (as you can guess, a model interface, a view interface, a control interface).
After a lot of research and laborious consultation with ACME, we get a lot of information about the detailed design. We want to make sure that we can set the maximum speed in advance, back and turn. We also need to be able to accelerate, slow down, turn left and turn right. Our dashboard must display the current speed and direction.
Achieving all of these needs is very demanding, but we are sure we can do it ...
First, let's consider the basic project. We need something to show the direction and turn the request. We did two enumeration types: Absolutedirection and relativedirection.
public enum Absolutedirection
{
North=0, East, South, West
}
public enum Relativedirection
{
Right and left and back
}
The control interface is resolved below. We know that control needs to pass the request to model, which includes: Accelerate, decelerate, and Turn. We build a Ivehiclecontrol interface and add the appropriate method.
public interface Ivehiclecontrol
{
void accelerate (int paramamount);
void decelerate (int paramamount);
void Turn (Relativedirection paramdirection);
}
Now let's clean up the model interface. We need to know the name of the car, speed, maximum speed, maximum backward speed, maximum turning speed and direction. We also need to accelerate, slow down, turn the function.
public interface Ivehiclemodel
{
String name{get; set;}
int speed{get; set;}
int maxspeed{get;}
int maxturnspeed{get;}
int maxreversespeed {get;}
Absolutedirection direction{get; set;}
void Turn (Relativedirection paramdirection);
void accelerate (int paramamount);
void decelerate (int paramamount);
}
Finally, we'll organize the View interface. We know that view needs to expose some of the functions of control, such as permitting or prohibiting acceleration, deceleration and turn applications.
public interface Ivehicleview
{
void Disableacceleration ();
void Enableacceleration ();
void Disabledeceleration ();
void Enabledeceleration ();
void Disableturning ();
void Enableturning ();
}
Now we need to do some fine-tuning so that our interfaces can interact with each other. First, any control needs to know its view and model, so add two functions to our Ivehiclecontrol interface: "Setmodel" and "Setview":
public interface Ivehiclecontrol
{
void requestaccelerate (int paramamount);
void requestdecelerate (int paramamount);
void Requestturn (Relativedirection paramdirection);
void Setmodel (Ivehiclemodel paramauto);
void Setview (Ivehicleview paramview);
}
The next part is more ingenious. We want the view to know the changes in model. To achieve this, we use the Observer pattern.
To implement the observer pattern, we need to add the following function to model (viewed by view): Addobserver, Removeobserver, and Notifyobservers.
public interface Ivehiclemodel
{
String name{get; set;}
int speed{get; set;}
int maxspeed{get;}
int maxturnspeed{get;}
int maxreversespeed {get;}
Absolutedirection direction{get; set;}
void Turn (Relativedirection paramdirection);
void accelerate (int paramamount);
void decelerate (int paramamount);
void Addobserver (Ivehicleview paramview);
void Removeobserver (Ivehicleview paramview);
void Notifyobservers ();
}
... and add the following function to the view (by model observation). The purpose of this is that model will have a reference to the view. When model changes, the Notifyobservers () method is called, passing in a reference to itself and calling Update () to notify view of the change.
public class Ivehicleview
{
void Disableacceleration ();
void Enableacceleration ();
void Disabledeceleration ();
void Enabledeceleration ();
void Disableturning ();
void Enableturning ();
void Update (Ivehiclemodel parammodel);
}
So we can connect our interfaces. In the following code we only need to refer to our interfaces, which guarantees the low coupling of our code. Any user interface that shows the state of the car needs to be implemented Ivehicleview, all of our Acme needs to implement Ivehiclemodel, and we need to make controls for our Acme car, These control implements the Ivehiclecontrol interface.
Next ... What is needed in common
We know that all cars do the same thing, so we do a common code based on "skeleton" to handle these operations. This is an abstract class, because we don't want anyone to drive on the skeleton (an abstract class cannot be instantiated). We call it a automobile. We will use a ArrayList (from System.Collections) to keep track of all views of interest (remember the Observer pattern?). )。 We can also use an old-fashioned array to record references to Ivehicleview, but now we're tired of trying to end this article. If you are interested, look at the Addobserver, Removeobserver, and notifyobservers in the observer pattern, and how these functions interact with Ivehicleview. Any time when there is a change in speed or direction, automobile notifies all ivehicleviews.
Public abstract class Automobile:ivehiclemodel
{
"Declarations" #region "Declarations"
Private ArrayList alist = new ArrayList ();
private int mintspeed = 0;
private int mintmaxspeed = 0;
private int mintmaxturnspeed = 0;
private int mintmaxreversespeed = 0;
Private Absolutedirection mdirection = Absolutedirection.north;
private string mstrname = "";
#endregion
"Constructor" #region "constructor"
public automobile (int parammaxspeed, int parammaxturnspeed, int parammaxreversespeed, string paramname)
{
This.mintmaxspeed = Parammaxspeed;
This.mintmaxturnspeed = Parammaxturnspeed;
This.mintmaxreversespeed = Parammaxreversespeed;
This.mstrname = paramname;
}
#endregion
"Members of the Ivehiclemodel" #region "Ivehiclemodel members"
public void Addobserver (Ivehicleview paramview)
{
Alist.add (Paramview);
}
public void Removeobserver (Ivehicleview paramview)
{
Alist.remove (Paramview);
}
public void Notifyobservers ()
{
foreach (Ivehicleview view in alist)
{
View. Update (this);
}
}
public string Name
{
Get
{
return this.mstrname;
}
Set
{
This.mstrname = value;
}
}
public int Speed
{
Get
{
return this.mintspeed;
}
}
public int Maxspeed
{
Get
{
return this.mintmaxspeed;
}
}
public int Maxturnspeed
{
Get
{
return this.mintmaxturnspeed;
}
}
public int Maxreversespeed
{
Get
{
return this.mintmaxreversespeed;
}
}
Public Absolutedirection Direction
{
Get
{
return this.mdirection;
}
}
public void Turn (Relativedirection paramdirection)
{
Absolutedirection newdirection;
Switch (paramdirection)
{
Case Relativedirection.right:
Newdirection = (absolutedirection) ((int) (this.mdirection + 1)%4);
Break
Case Relativedirection.left:
Newdirection = (absolutedirection) ((int) (this.mdirection + 3)%4);
Break
Case Relativedirection.back:
Newdirection = (absolutedirection) ((int) (this.mdirection + 2)%4);
Break
Default
Newdirection = Absolutedirection.north;
Break
}
This.mdirection = newdirection;
This. Notifyobservers ();
}
public void accelerate (int paramamount)
{
This.mintspeed + = Paramamount;
if (mintspeed >= this.mintmaxspeed) mintspeed = mintmaxspeed;
This. Notifyobservers ();
}
public void decelerate (int paramamount)
{
This.mintspeed-= Paramamount;
if (mintspeed <= this.mintmaxreversespeed) mintspeed = mintmaxreversespeed;
This. Notifyobservers ();
}
#endregion
}
Now that our "ACME Framework" is ready, we just need to set up tangible classes and interfaces. First let's look at the last two classes: Control and Model ...
Here our tangible Automobilecontrol implements the Ivehiclecontrol interface. Our Automobilecontrol will also set the view to depend on the state of the model (Setview method is detected when a request to model is made).
Note that we only have references to Ivehiclemodel (not abstract classes automobile) and references to Ivehicleview (rather than specific view), thus guaranteeing a low coupling between objects.
public class Automobilecontrol:ivehiclecontrol
{
Private Ivehiclemodel Model;
Private Ivehicleview View;
Public Automobilecontrol (Ivehiclemodel Parammodel, Ivehicleview Paramview)
{
This. Model = Parammodel;
This. View = Paramview;
}
Public Automobilecontrol ()
{}
Ivehiclecontrol members#region Ivehiclecontrol Members
public void Setmodel (Ivehiclemodel parammodel)
{
This. Model = Parammodel;
}
public void Setview (Ivehicleview paramview)
{
This. View = Paramview;
}
public void requestaccelerate (int paramamount)
{
if (Model!= null)
{
Model.accelerate (Paramamount);
if (View!= null) Setview ();
}
}
public void requestdecelerate (int paramamount)
{
if (Model!= null)
{
Model.decelerate (Paramamount);
if (View!= null) Setview ();
}
}
public void Requestturn (Relativedirection paramdirection)
{
if (Model!= null)
{
Model.turn (paramdirection);
if (View!= null) Setview ();
}
}
#endregion
public void Setview ()
{
if (model.speed >= model.maxspeed)
{
View.disableacceleration ();
View.enabledeceleration ();
}
else if (model.speed <= model.maxreversespeed)
{
View.disabledeceleration ();
View.enableacceleration ();
}
Else
{
View.enableacceleration ();
View.enabledeceleration ();
}
if (model.speed >= model.maxturnspeed)
{
View.disableturning ();
}
Else
{
View.enableturning ();
}
}
}
Here is our Acme200sportscar class (inherited from the abstract class automobile, implementing the Ivehiclemodel Interface):
public class Acme2000sportscar:automobile
{
Public Acme2000sportscar (String paramname): Base ( -20, paramname) {}
Public Acme2000sportscar (string paramname, int parammaxspeed, int parammaxturnspeed, int parammaxreversespeed):
Base (Parammaxspeed, Parammaxturnspeed, Parammaxreversespeed, paramname) {}
}
Now it's our turn to view ...
Now we're finally starting to build our last part of MVC ... view!
We're going to build a autoview to implement the Ivehicleview interface. This autoview will have a reference to the control and model interfaces.
public class AutoView:System.Windows.Forms.UserControl, Ivehicleview
{
Private Ivehiclecontrol control = new ACME. Automobilecontrol ();
Private Ivehiclemodel Model = new ACME. Acme2000sportscar ("Speedy");
}
We also need to wrap everything in the UserControl constructor.
Public AutoView ()
{
This are required by the windows.forms Form Designer.
InitializeComponent ();
Wireup (control, Model);
}
public void Wireup (Ivehiclecontrol paramcontrol, Ivehiclemodel Parammodel)
{
If we ' re switching Models, don ' t keep watching
The old one!
if (Model!= null)
{
Model.removeobserver (this);
}
Model = Parammodel;
control = Paramcontrol;
Control.setmodel (Model);
Control.setview (this);
Model.addobserver (this);
}
Next, add our button and a label to show the status of the ACME2000 Sports car and the status bar to display the encoding for all buttons.
private void Btnaccelerate_click (object sender, System.EventArgs e)
{
Control.requestaccelerate (int. Parse (This.txtAmount.Text));
}
private void Btndecelerate_click (object sender, System.EventArgs e)
{
Control.requestdecelerate (int. Parse (This.txtAmount.Text));
}
private void Btnleft_click (object sender, System.EventArgs e)
{
Control.requestturn (Relativedirection.left);
}
private void Btnright_click (object sender, System.EventArgs e)
{
Control.requestturn (Relativedirection.right);
}
Add a method to update the interface ...
public void Updateinterface (Ivehiclemodel auto)
{
This.label1.Text = Auto. Name + "heading" + Auto. Direction.tostring () + "at speed:" + Auto. Speed.tostring ();
This.pBar.Value = (auto. speed>0)? Auto. Speed*100/auto. Maxspeed:auto. Speed*100/auto. Maxreversespeed;
}
Finally, we implement the Ivehicleview interface method.
public void Disableacceleration ()
{
this.btnAccelerate.Enabled = false;
}
public void Enableacceleration ()
{
This.btnAccelerate.Enabled = true;
}
public void Disabledeceleration ()
{
this.btnDecelerate.Enabled = false;
}
public void Enabledeceleration ()
{
This.btnDecelerate.Enabled = true;
}
public void disableturning ()
{
this.btnRight.Enabled = this.btnLeft.Enabled = false;
}
public void enableturning ()
{
this.btnRight.Enabled = This.btnLeft.Enabled = true;
}
public void Update (Ivehiclemodel parammodel)
{
This. Updateinterface (Parammodel);
}
We're finally done,!!!.
Now we can test the ACME2000 Sports car. Everything goes according to plan, and then we find Acme's supervisor, but he wants to open a truck instead of a sports car.
Luckily, we're using mvc!. All we need to do is build a new Acmetruck class, wrap it up and finish it!
public class Acme2000truck:automobile
{
Public Acme2000truck (String paramname): Base ( -12, paramname) {}
Public Acme2000truck (string paramname, int parammaxspeed, int parammaxturnspeed, int parammaxreversespeed):
Base (Parammaxspeed, Parammaxturnspeed, Parammaxreversespeed, paramname) {}
}
In Autoview, we just need to build a truck pack!
private void Btnbuildnew_click (object sender, System.EventArgs e)
{
This.autoView1.WireUp (New ACME.) Automobilecontrol (), New ACME. Acme2000truck (This.txtName.Text));
}
If we want a new control just allow us to come every time to accelerate or decelerate the maximum 5mph, a trifle! Make a slowpokecontrol (same as our autocontrol, but limit the application acceleration).
public void requestaccelerate (int paramamount)
{
if (Model!= null)
{
int amount = Paramamount;
if (Amount > 5) amount = 5;
Model.accelerate (amount);
if (View!= null) Setview ();
}
}
public void requestdecelerate (int paramamount)
{
if (Model!= null)
{
int amount = Paramamount;
if (Amount > 5) amount = 5;
Model.accelerate (amount);
Model.decelerate (amount);
if (View!= null) Setview ();
}
}
If we want our ACME2000 truck to become dull, just wrap it in Autoview.
private void Btnbuildnew_click (object sender, System.EventArgs e)
{
This.autoView1.WireUp (New ACME.) Slowpokecontrol (), New ACME. Acme2000truck (This.txtName.Text));
}
Finally, if we need an interface on the Web, all we have to do is build a Web project to implement the Ivehicleview interface in UserControl.
Conclusion
As you can see, using MVC to build code control interfaces is very low coupling and is easy to adapt to changes in requirements. It can also reduce the impact of change, and you can reuse your virtual functions and interfaces anywhere. There are many times when we can achieve scalability in our projects, especially when the requirements change, but this needs to be said again.
At the same time, do the next project to remember MVC ... You won't feel sorry!