基于MVC模式的汽车类UI与数据分离OOP设计咨询
设计思路评估
你的拆分思路完全符合单一职责原则,是正确的优化方向,只是双向引用的设计存在紧耦合的问题,可以进一步优化。
疑问1:双向引用的替代方案
不建议使用双向引用,会导致Model层和View层紧耦合,还会带来内存回收风险,更优的方案如下:
- 去掉
carDataElement(Model层)中对carUIElement(View层)的所有依赖,Model层只保留纯业务数据和计算逻辑,不需要知道View的存在 - 仅保留
carUIElement对carDataElement的单向引用 - 如果需要Model数据变更时自动更新UI,可以让
carDataElement实现C#自带的INotifyPropertyChanged接口,在属性变更时触发事件,carUIElement订阅该事件实现自动更新,示例代码如下:
// Model层实现属性变更通知 public class CarDataElement : INotifyPropertyChanged { private double _weight; public double Weight { get => _weight; set { _weight = value; OnPropertyChanged(nameof(Weight)); } } public string Manufacturer { get; set; } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public double CalculatePrice() { // 业务逻辑 return 0; } } // View层订阅事件 public class CarUIElement { public Size DrawSize { get; set; } public Point Location { get; set; } public CarDataElement LinkedData { get; } // 单向引用Model public CarUIElement(CarDataElement data) { LinkedData = data; // 订阅Model变更事件 LinkedData.PropertyChanged += (s,e) => Draw(); } public void Draw() { // 绘制逻辑 } }
这种方案完全解耦了Model和View,Model可以独立复用、单元测试,不会受UI层逻辑影响。
疑问2:属性网格同时展示所有属性的实现
有两种成熟方案可以选择:
方案1:使用SelectedObjects属性(零额外代码)
C#的PropertyGrid原生支持同时展示多个对象的属性,点击CarUIElement时,将对应的UI实例和Data实例放到数组中赋值给SelectedObjects即可:
// 点击事件处理逻辑 var clickedUI = GetClickedCarUIElement(); propertyGrid.SelectedObjects = new object[] { clickedUI, clickedUI.LinkedData };
这种方案适合简单场景,无需额外编写代码。
方案2:编写聚合ViewModel(推荐,可定制性高)
新增一个专门给属性网格用的ViewModel类,包装UI和Model的所有属性,还可以通过特性控制属性的分组、显示名称、编辑权限等,用户体验更好:
public class CarPropertyViewModel { private readonly CarUIElement _ui; private readonly CarDataElement _data; public CarPropertyViewModel(CarUIElement ui, CarDataElement data) { _ui = ui; _data = data; } [Category("UI属性")] public Size DrawSize { get => _ui.DrawSize; set => _ui.DrawSize = value; } [Category("UI属性")] public Point Location { get => _ui.Location; set => _ui.Location = value; } [Category("数据属性")] public double Weight { get => _data.Weight; set => _data.Weight = value; } [Category("数据属性")] public string Manufacturer { get => _data.Manufacturer; set => _data.Manufacturer = value; } [Category("业务方法")] public double CalculatePrice() => _data.CalculatePrice(); }
使用时直接将ViewModel实例赋值给SelectedObject即可:
var clickedUI = GetClickedCarUIElement(); propertyGrid.SelectedObject = new CarPropertyViewModel(clickedUI, clickedUI.LinkedData);
额外优化建议
如果要严格遵循MVC模式,可以额外增加Controller层处理交互逻辑:
- 画布点击事件、属性修改后的同步逻辑都放到Controller中实现,View层只负责渲染和触发事件,Model层只负责数据和业务计算,三层职责完全分离,后续维护和扩展会更方便。
内容的提问来源于stack exchange,提问作者SenZe
相关产品推荐
相关产品推荐

