浅析MVC Pattern
阅读原文时间:2023年07月12日阅读:2

最近做CAD插件相关的工作,用到了一些模式,解决对应场景的问题。 比如插件的运行实例上使用Singleton、实例内部使用了MVC(Strategy and Observer )。

针对CAD插件,插件可以在CAD运行过程中多次打开关闭,数据状态需要保持一致,数据联动,及多种UI布局模式。

1、Singleton 维持一个全局唯一实例,使得插件运行变得有“状态” 、提升插件的打开速度。

2、MVC 对程序结构进行解耦,方便不同UI进行数据互通、复用使用多种UI布局模式。

3、在这里本文主要针对MVC进行回顾。

代码链接:https://github.com/Shawn-china/MVCDemo.git

模式很多,场景各不同通常一个模式都对应一个特定场景,所以也就没有什么万能模式解决所有问题。从"Gang of Four" 总结23种设计模式之后,又不断有新的模式被总结出来。

比如:创建型模式中Singleton 维护一个全局唯一实例、Factory负责创建实例;行为型模式解决一些运行时问题;结构型模式适用模板类问题。

使用面向对象开一个应用程序除了满足功能需求,其次还要达到OO目标。 代码层面可读、高内聚低耦合、复用易扩展维护等。

实现目标二可通过多种途径:遵循代码规范、使用模式、面向抽象、面向接口、使用组合等具体的方法。

一些常见的开发框架中也可以看到很多模式的影子, Vue.js中双向绑定使用观察者模式实现,Qt中Model/View 模式简化了UI和数据的“交互”,一些带发布订阅机制的第三方软件。

MVC是挪威计算机科学家:Trygve Mikkjel Heyerdahl Reenskaug,在1979年为GUI软件设计制定的模式。参考维基百科:https://en.wikipedia.org/wiki/Trygve_Reenskaug

MVC有三种类型的“对象”。

Model 应用程序数据,

View  应用程序UI,可以有多种体现形式如:winform、wpf、控制台、web页面等,

Controller定义了View对输入的处理方式。

当Model数据更改时,它将更新View。一个模型可以对应多个视图。

如下图示:

MVC有很多具体形式,可以view 先行也可以controller先行。在这里使用图一UML所描述的实现方式。注:本质上其实为 Observer 与 Strategy 复合模式。

                       图一

1、Model 实现一个数据监听 Observer,对数据数据对象进行监听,当数据变动可以通知订阅者。

public interface IModel  
{  
    ArrayList DataObservers { get; set; }

    void RegisterObserver(IDataObserver concreteObserver, string key);

    void UnregisterObserver(IDataObserver concreteObserver);

    /// <summary>  
    ///  
    /// </summary>  
    /// <param name="baseObject">Base class containing an Id field and a Name field</param>  
    /// <param name="key"></param>  
    void GetData(BaseObject baseObject, string key);  
}

IModel

public class ConcreteModel : IModel  
{  
    public ArrayList DataObservers { get; set; } = new ArrayList();

    public void RegisterObserver(IDataObserver concreteObserver, string key)  
    {  
        if (!this.DataObservers.Contains(concreteObserver))  
        {  
            this.DataObservers.Add(concreteObserver);  
        }  
    }

    public void UnregisterObserver(IDataObserver concreteObserver)  
    {  
        if (this.DataObservers.Contains(concreteObserver))  
        {  
            this.DataObservers.Remove(concreteObserver);  
        }  
    }

    public void GetData(BaseObject baseObject, string key)  
    {  
        this.Notity(baseObject, key);  
    }

    private void Notity(BaseObject baseObject, string key)  
    {  
        foreach (object item in this.DataObservers)  
        {  
            if (((IDataObserver)item).ObserverKeys.Contains(key))  
            {  
                ((IDataObserver)item).Update(baseObject);  
            }  
        }  
    }  
}

ConcreteModel

2、view 包含一个Model 一个Controller  ,实现IDataObserver接口 。示例包含三个view : 1、ComboBox 2、TreeView 3、DataGridView

IDataObserver 数据观察者

public interface IDataObserver  
{  
    List<string> ObserverKeys { get; set; }

    string SubscriptionKey { get; set; }

    void Update(object data);  
}

IDataObserver

BaseRequest 作为所有view类的基类,包含一个Model 一个Controller

public class BaseRequest  
{  
    public static IModel ConcreteModel;  
    public static IController ConcreteController;

    public BaseRequest()  
    {  
        ConcreteModel = ConcreteModel ?? new ConcreteModel();  
        ConcreteController = ConcreteController ?? new ConcreteController(ConcreteModel);  
    }

    public BaseRequest(IModel concreteModel, IController concreteController)  
    {  
        ConcreteModel = ConcreteModel ?? concreteModel;  
        ConcreteController = ConcreteController ?? concreteController;  
    }  
}

BaseRequest

ComboBox ,被TreeView 和 DataGridView 观察 。

public class ConcreteComboxRequest : BaseRequest, IDataObserver  
{  
    private readonly ComboBox \_comboBox;

    public ConcreteComboxRequest(ComboBox comboBox)  
    {  
        this.\_comboBox = comboBox;  
        this.IntializeView();  
    }

    public List<string> ObserverKeys { get; set; }

    public string SubscriptionKey { get; set; } = $"{nameof(ConcreteComboxRequest)}";

    public void Update(object data)  
    {  
        this.\_comboBox.DataSource = DataContainer.Schools;  
    }

    private void IntializeView()  
    {  
        this.\_comboBox.DisplayMember = "Name";  
        this.\_comboBox.ValueMember = "Id";  
        this.\_comboBox.SelectedIndexChanged += this.cmb\_SelectedValueChanged;  
    }

    private void cmb\_SelectedValueChanged(object sender, EventArgs e)  
    {  
        BaseObject baseObject = (BaseObject)this.\_comboBox.SelectedItem;  
        ConcreteController.GetDatas(baseObject, this.SubscriptionKey);  
    }  
}

ConcreteComboxRequest

TreeView,注册到Model监听Observer中,观察ComboBox。 同时被DataGridView 观察

public class ConcreteTreeviewRequest : BaseRequest, IDataObserver
{
private readonly TreeView _treeView;

    public ConcreteTreeviewRequest(TreeView treeView)  
    {  
        this.\_treeView = treeView;  
        this.IntializeView();  
    }

    public List<string> ObserverKeys { get; set; } = new List<string> { $"{nameof(ConcreteComboxRequest)}" };

    public string SubscriptionKey { get; set; } = $"{nameof(ConcreteTreeviewRequest)}";

    public void Update(object data)  
    {  
        BaseObject baseObject = (BaseObject)data;  
        this.InitializeTreeView(baseObject);  
    }

    private void IntializeView()  
    {  
        foreach (string observerKey in this.ObserverKeys)  
        {  
            ConcreteModel.RegisterObserver(this, observerKey);  
        }

        this.\_treeView.AfterSelect += this.treeView\_SelectedValue;  
    }

    private void InitializeTreeView(BaseObject baseObject)  
    {  
        this.\_treeView.Nodes.Clear();  
        List<Grade> grades = Grade.GetList(baseObject);

        foreach (Grade item in grades)  
        {  
            this.CreateTreeNode(null, item);  
        }  
    }

    private void treeView\_SelectedValue(object sender, TreeViewEventArgs e)  
    {  
        TreeNode currentNode = this.\_treeView.SelectedNode;

        BaseObject baseObject = (BaseObject)currentNode.Tag;

        ConcreteController.GetDatas(baseObject, this.SubscriptionKey);  
    }

    private TreeNode CreateTreeNode(TreeNode parentNode, BaseObject concreteData)  
    {  
        TreeNode treeNode = new TreeNode  
        {  
            Tag = concreteData,  
            Name = concreteData.Id,  
            Text = concreteData.Name  
        };

        if (parentNode == null)  
        {  
            this.\_treeView.Nodes.Add(treeNode);  
        }  
        else  
        {  
            parentNode.Nodes.Add(treeNode);  
        }

        return treeNode;  
    }  
}

ConcreteTreeviewRequest

DataGridView,,注册到Model监听Observer中,观察ComboBox、TreeView 。

public class ConcreteDataGridViewRequest : BaseRequest, IDataObserver
{
private readonly DataGridView _dataGridView;

    public ConcreteDataGridViewRequest(DataGridView dataGridView)  
    {  
        this.\_dataGridView = dataGridView;  
        this.IntializeView();  
    }

    public List<string> ObserverKeys { get; set; } = new List<string>  
    {  
        $"{nameof(ConcreteTreeviewRequest)}",  
        $"{nameof(ConcreteComboxRequest)}"  
    };

    public string SubscriptionKey { get; set; } = $"{nameof(ConcreteDataGridViewRequest)}";

    public void Update(object data)  
    {  
        BaseObject baseObject = (BaseObject)data;  
        this.InitializeDataGridView(baseObject);  
    }

    private void InitializeDataGridView(BaseObject baseObject)  
    {  
        this.\_dataGridView.Columns.Clear();  
        List<Student> students = Student.GetList(baseObject);

        this.\_dataGridView.DataSource = students;  
    }

    private void IntializeView()  
    {  
        foreach (string observerKey in this.ObserverKeys)  
        {  
            ConcreteModel.RegisterObserver(this, observerKey);  
        }

        this.\_dataGridView.SelectionChanged += this.dataGridView\_SelectionChanged;  
    }

    private void dataGridView\_SelectionChanged(object sender, EventArgs e)  
    {  
        // Do some business logic  
    }  
}

ConcreteDataGridViewRequest

3、Controller 包含一个 Model ,当某一view “主题”变化时,调用Model 通知对应的订阅对象。

public interface IController { void GetDatas(BaseObject baseObject, string subscriberKey); }

public class ConcreteController : IController  
{  
    private readonly IModel \_model;

    public ConcreteController(IModel model)  
    {  
        this.\_model = model;  
    }

    public void GetDatas(BaseObject baseObject, string subscriberKey)  
    {  
        this.\_model.GetData(baseObject, subscriberKey);  
    }  
}

ConcreteController