MVC (Model-View-Controller) 將業務邏輯、使用者介面、控制流程分離,降低耦合、提升可測試性與重用性。本文透過一個「ACME 車輛控制系統」完整案例,示範如何用介面定義契約、觀察者模式同步 Model/View,並輕鬆擴充新車種與新控制器。
public enum AbsoluteDirection { North, East, South, West }
public enum RelativeDirection { Right, Left, Back }
// Model:車輛狀態與行為
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 Accelerate(int amount);
void Decelerate(int amount);
void Turn(RelativeDirection dir);
}
// View:介面操作與顯示
public interface IVehicleView
{
void EnableAcceleration(); void DisableAcceleration();
void EnableDeceleration(); void DisableDeceleration();
void EnableTurning(); void DisableTurning();
void Update(IVehicleModel model); // 觀察者更新介面
}
// Controller:協調 Model 與 View
public interface IVehicleControl
{
void RequestAccelerate(int amount);
void RequestDecelerate(int amount);
void RequestTurn(RelativeDirection dir);
void SetModel(IVehicleModel model);
void SetView(IVehicleView view);
}
public class AutomobileControl : IVehicleControl
{
private IVehicleModel Model;
private IVehicleView View;
public AutomobileControl() { }
public AutomobileControl(IVehicleModel m, IVehicleView v) { Model = m; View = v; }
public void SetModel(IVehicleModel m) => Model = m;
public void SetView(IVehicleView v) => View = v;
public void RequestAccelerate(int amount)
{
if (Model != null) { Model.Accelerate(amount); UpdateView(); }
}
public void RequestDecelerate(int amount)
{
if (Model != null) { Model.Decelerate(amount); UpdateView(); }
}
public void RequestTurn(RelativeDirection dir)
{
if (Model != null) { Model.Turn(dir); UpdateView(); }
}
private void UpdateView()
{
if (View == null) return;
// 依速度啟用/停用按鈕
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();
View.Update(Model); // 觸發 View 重繪
}
}
public abstract class Automobile : IVehicleModel
{
public string Name { get; set; }
public int Speed { get; set; }
public int MaxSpeed { get; }
public int MaxTurnSpeed { get; }
public int MaxReverseSpeed { get; }
public AbsoluteDirection Direction { get; set; }
protected Automobile(int maxSpeed, int maxTurn, int maxReverse, string name)
{
MaxSpeed = maxSpeed; MaxTurnSpeed = maxTurn; MaxReverseSpeed = maxReverse; Name = name;
}
public virtual void Accelerate(int amount) => Speed += amount;
public virtual void Decelerate(int amount) => Speed -= amount;
public virtual void Turn(RelativeDirection dir)
{
// 簡化:右轉+1、左轉-1、掉頭+2
int turn = (dir == RelativeDirection.Right) ? 1 : (dir == RelativeDirection.Left) ? -1 : 2;
Direction = (AbsoluteDirection)(((int)Direction + turn + 4) % 4);
}
}
// 具體車種只需指定參數
public class ACME2000SportsCar : Automobile
{
public ACME2000SportsCar(string name) : base(250, 40, -20, name) { }
public ACME2000SportsCar(string name, int maxS, int maxT, int maxR) : base(maxS, maxT, maxR, name) { }
}
public class ACME2000Truck : Automobile
{
public ACME2000Truck(string name) : base(80, 25, -12, name) { }
}
public partial class AutoView : UserControl, IVehicleView
{
private IVehicleControl Control = new AutomobileControl();
private IVehicleModel Model = new ACME2000SportsCar("Speedy");
public AutoView() { InitializeComponent(); WireUp(Control, Model); }
public void WireUp(IVehicleControl ctrl, IVehicleModel mdl)
{
if (Model != null) Model.RemoveObserver(this); // 假設 Model 實作觀察者
Model = mdl; Control = ctrl;
Control.SetModel(Model); Control.SetView(this);
Model.AddObserver(this);
}
// 按鈕事件 → Controller
private void btnAccelerate_Click(object sender, EventArgs e) => Control.RequestAccelerate(int.Parse(txtAmount.Text));
private void btnDecelerate_Click(object sender, EventArgs e) => Control.RequestDecelerate(int.Parse(txtAmount.Text));
private void btnLeft_Click(object sender, EventArgs e) => Control.RequestTurn(RelativeDirection.Left);
private void btnRight_Click(object sender, EventArgs e) => Control.RequestTurn(RelativeDirection.Right);
// 介面更新
public void UpdateInterface(IVehicleModel auto)
{
lblStatus.Text = $"{auto.Name} heading {auto.Direction} at speed: {auto.Speed}";
pBar.Value = (auto.Speed > 0) ? auto.Speed * 100 / auto.MaxSpeed : auto.Speed * 100 / auto.MaxReverseSpeed;
}
// IVehicleView 介面實作
public void EnableAcceleration() => btnAccelerate.Enabled = true;
public void DisableAcceleration() => btnAccelerate.Enabled = false;
public void EnableDeceleration() => btnDecelerate.Enabled = true;
public void DisableDeceleration() => btnDecelerate.Enabled = false;
public void EnableTurning() => btnLeft.Enabled = btnRight.Enabled = true;
public void DisableTurning() => btnLeft.Enabled = btnRight.Enabled = false;
public void Update(IVehicleModel m) => UpdateInterface(m);
}
ACME2000Truck,WireUp(new AutomobileControl(), new ACME2000Truck("Heavy")) 即可。SlowPokeControl 繼承 AutomobileControl,覆寫 RequestAccelerate/Decelerate 限制最大加減速 5,WireUp(new SlowPokeControl(), new ACME2000Truck(...))。IVehicleView,同樣可與既有 Model/Controller 搭配。此案例展示 MVC 核心價值:介面導向程式設計 定義契約,觀察者模式 解耦 Model/View 同步,依賴注入 (WireUp) 組合具體實作。擴充新車種、新控制邏輯、新介面技術均無需修改既有程式碼,完美符合開放封閉原則 (OCP)。