【发布时间】:2011-10-06 10:15:13
【问题描述】:
我一直在网上寻找 .NET 中 MVC 设置的示例实现。我发现了很多例子,但它们在某些方面似乎都不同。我有一本关于设计模式的书,其中描述了 MVC 起源于 Smalltalk,所以我阅读了几个人讨论它在该语言中的实现。下面是我编写的一个示例项目,利用我收集到的内容是一个正确的实现,但我对一些细节感到困惑。
我遇到的一个问题是对象构造的正确顺序。这是我的 Program.cs 中的 impl
Model mdl = new Model();
Controller ctrl = new Controller(mdl);
Application.Run(new Form1(ctrl, mdl));
观点: 我立即有几个我不确定的问题。首先,如果视图应该只从模型中读取数据进行更新,但包含对它的引用,那么是什么阻止我从视图中调用控制器对模型的调用?程序员是否应该忽略他们暴露于模型成员函数的事实?我的另一个想法是,通知视图模型已更新的事件可能会发送某种状态对象以供视图自行更新。
public interface IView
{
double TopSpeed { get; }
double ZeroTo60 { get; }
int VehicleID { get; }
string VehicleName { get; }
}
/// <summary>
/// Assume the form has the following controls
/// A button with a click event OnSaveClicked
/// A combobox with a selected index changed event OnSelectedIndexChanged
/// A textbox that displays the vehicles top speed named mTextTopSpeed
/// A textbox that displays the vehicles zero to 60 time named mTextZeroTo60
/// </summary>
public partial class Form1 : Form, IView
{
private IController mController;
private IModel mModel;
public Form1(IController controller, IModel model)
{
InitializeComponent();
mController = controller;
mController.SetListener(this);
mModel = model;
mModel.ModelChanged += new ModelUpdated(mModel_ModelChanged);
}
void mModel_ModelChanged(object sender, EventArgs e)
{
mTextTopSpeed.Text = mModel.TopSpeed.ToString();
mTextZeroTo60.Text = mModel.ZeroTo60.ToString();
}
public double TopSpeed { get { return Double.Parse(mTextTopSpeed.Text); } }
public double ZeroTo60 { get { return Double.Parse(mTextZeroTo60.Text); } }
public int VehicleID { get { return (int)mComboVehicles.SelectedValue; } }
public string VehicleName { get { return mComboVehicles.SelectedText; } }
#region Form Events
private void OnFormLoad(object sender, EventArgs e)
{
mComboVehicles.ValueMember = "Key";
mComboVehicles.DisplayMember = "Value";
mComboVehicles.DataSource = new BindingSource(mModel.VehicleList, null);
}
private void OnSelectedIndexChanged(object sender, EventArgs e)
{
mController.OnSelectedVehicleChanged();
}
private void OnSaveClicked(object sender, EventArgs e)
{
mController.OnUpdateVehicle();
}
#endregion
}
控制器: 我实现控制器的方式唯一真正的问题是,在没有明确分配视图的情况下构建控制器对我来说似乎有点奇怪。我可以完全忽略视图,但这意味着我会将参数传递给控制器的函数以更新模型,这似乎完全没有抓住重点。
public interface IController
{
void OnUpdateVehicle();
void OnSelectedVehicleChanged();
void SetListener(IView view);
}
class Controller : IController
{
private IModel mModel;
private IView mView = null;
public Controller(IModel model)
{
mModel = model;
}
public void OnUpdateVehicle()
{
if(mView == null)
return;
mModel.UpdateVehicle(mView.VehicleID, mView.TopSpeed, mView.ZeroTo60);
}
public void SetListener(IView view)
{
mView = view;
}
public void OnSelectedVehicleChanged()
{
if (mView == null)
return;
mModel.SelectVehicle(mView.VehicleID);
}
}
型号: 在我的表单中,我有一个组合框,它是我的伪数据库中给出的车辆列表。因此,我觉得我的表单实际上应该实现多个视图/模型。一个视图专门用于列出具有相应控制器/模型的可能车辆,以及一个用于显示所选车辆及其自己的控制器/模型的信息的视图。
public delegate void ModelUpdated(object sender, EventArgs e);
public interface IModel
{
event ModelUpdated ModelChanged;
void UpdateVehicle(int id, double topSpeed, double zeroTo60);
void SelectVehicle(int id);
double TopSpeed { get; }
double ZeroTo60 { get; }
IDictionary<int, string> VehicleList { get; }
}
// class for the sake of a pseudo database object
class Vehicle
{
public int ID { get; set; }
public string Name { get; set; }
public double TopSpeed { get; set; }
public double ZeroTo60 { get; set; }
public Vehicle(int id, string name, double topSpeed, double zeroTo60)
{
ID = id;
Name = name;
TopSpeed = topSpeed;
ZeroTo60 = zeroTo60;
}
}
class Model : IModel
{
private List<Vehicle> mVehicles = new List<Vehicle>()
{
new Vehicle(1, "Civic", 120.0, 5.0),
new Vehicle(2, "Batmobile", 9000.0, 1.0),
new Vehicle(3, "Tricycle", 5.0, 0.0)
};
private Vehicle mCurrentVehicle;
public Model()
{
mCurrentVehicle = mVehicles[0];
}
public event ModelUpdated ModelChanged;
public void OnModelChanged()
{
if (ModelChanged != null)
{
ModelChanged(this, new EventArgs());
}
}
public double TopSpeed { get { return mCurrentVehicle.TopSpeed; } }
public double ZeroTo60 { get { return mCurrentVehicle.ZeroTo60; } }
public IDictionary<int, string> VehicleList
{
get
{
Dictionary<int, string> vDict = new Dictionary<int, string>();
foreach (Vehicle v in mVehicles)
{
vDict.Add(v.ID, v.Name);
}
return vDict as IDictionary<int, string>;
}
}
#region Pseudo Database Calls
public void SelectVehicle(int id)
{
foreach (Vehicle v in mVehicles)
{
if (v.ID == id)
{
mCurrentVehicle = v;
OnModelChanged(); // send notification to registered views
break;
}
}
}
public void UpdateVehicle(int id, double topSpeed, double zeroTo60)
{
foreach (Vehicle v in mVehicles)
{
if (v.ID == id)
{
mCurrentVehicle.TopSpeed = topSpeed;
mCurrentVehicle.ZeroTo60 = zeroTo60;
OnModelChanged(); // send notification to registered views
break;
}
}
}
#endregion
}
在这个 tl;dr 的结论中,我想我正在寻找的是关于我在这里所做的是否代表真正的 MVC 实现的一些指导,也许有人可以对上述问题有所了解.任何建议将不胜感激。
【问题讨论】:
-
我不是 MVC 概念的大师,但我认为您遇到视图无法在模型中调用函数的问题的原因是您没有将模型从数据库。您的模型应该是普通对象,而其他东西(数据库上下文)应该处理保存更改。然后你将普通模型对象传递给你的视图,并且没有可以调用它们的方法。
-
最初,我实现了 MVP,其中我的模型只是一个代表我的数据库表的相应列的对象。然后,我有一个与 Presenter 相关联的服务,该服务进行数据库交互并将它们分配给该模型。但是,问题在于我仍然有一种将数据分配给该模型的方法,例如,只是一个构造函数调用。如果我希望我的模型是可更新的,它必须能够以某种方式接受分配。除非,当我从视图将数据保存到表中时,我构造了一个新对象以传递给数据库更新函数。
-
我不认为这真的很不正常。只要您不能将更改提交回(到数据库),为什么可以在模型上设置属性会出现问题?
-
你说得对,我想我实际上可能更喜欢我最初的 MVP 设置的变体。在某个地方,我的视图将被一个模型更新,该模型只是我的表的一个对象表示,它将有一个视图绑定到的事件。演示者或视图将有权访问该视图上的分配,但只有演示者将包含将模型分配给数据库表的逻辑。
标签: c# .net winforms model-view-controller