You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WinForms中基于组件模型类型匹配视图绘图逻辑的MVC/MVVM实现方案咨询

穿孔板布局程序:M-V分离下的组件绘图解决方案

你的核心需求很明确:既要严格遵循模型-视图分离(保证模型可二进制序列化),又要避免类型检查、字符串/枚举标记,用纯面向对象的方式实现视图根据模型类型选择绘图逻辑。下面提供两种最适合的方案,结合你的代码场景展开说明:


方案1:访客模式(Visitor Pattern)—— 最纯粹的面向对象实现

访客模式完美解决了"在不修改模型的前提下,为不同类型模型添加操作"的问题,完全符合你的M-V分离要求,且彻底避免类型检查。

步骤1:重构模型层(完全独立,无视图依赖)

模型只保留业务数据,通过IComponent接口定义接受访客的方法,具体组件实现该接口:

using System.Drawing; // 仅用基础数据类型,无WinForms绘图依赖

[Serializable] // 标记为可二进制序列化
public interface IComponent
{
    Point Position { get; set; } // 模型数据:组件位置
    void Accept(IComponentVisitor visitor); // 接受访客的方法
}

[Serializable]
public class Wire : IComponent
{
    public Point Position { get; set; }
    public int Length { get; set; } // 模型数据:导线长度

    public void Accept(IComponentVisitor visitor)
    {
        visitor.Visit(this); // 引导访客处理当前导线
    }
}

[Serializable]
public class Transistor : IComponent
{
    public Point Position { get; set; }
    public enum PinType { EBC, BCE }
    public PinType PinConfiguration { get; set; } // 模型数据:引脚配置

    public void Accept(IComponentVisitor visitor)
    {
        visitor.Visit(this); // 引导访客处理当前晶体管
    }
}

// 访客接口:定义针对每个组件类型的操作
public interface IComponentVisitor
{
    void Visit(Wire wire);
    void Visit(Transistor transistor);
    // 新增组件时,只需在这里添加对应的Visit方法
}

[Serializable]
public class Perfboard
{
    public List<IComponent> Components { get; } = new List<IComponent>();
    public int Width { get; set; }
    public int Height { get; set; }

    public Perfboard() { }
    public Perfboard(int width, int height)
    {
        Width = width;
        Height = height;
    }
}

步骤2:实现视图层的绘图访客

绘图逻辑全部封装在访客类中,模型完全不知道视图的存在:

using System.Drawing;

// 绘图访客:实现IComponentVisitor,处理每个组件的绘制逻辑
public class ComponentDrawingVisitor : IComponentVisitor
{
    private readonly Graphics _graphics;
    private readonly float _scale = 25.4f; // 单位转换:毫米→像素

    public ComponentDrawingVisitor(Graphics graphics)
    {
        _graphics = graphics;
    }

    public void Visit(Wire wire)
    {
        // 用模型数据绘制导线
        var start = new PointF(wire.Position.X * _scale, wire.Position.Y * _scale);
        var end = new PointF((wire.Position.X + wire.Length) * _scale, wire.Position.Y * _scale);
        _graphics.DrawLine(Pens.Black, start, end);
    }

    public void Visit(Transistor transistor)
    {
        // 用模型数据绘制晶体管
        var bounds = new RectangleF(
            transistor.Position.X * _scale,
            transistor.Position.Y * _scale,
            3 * _scale,
            5 * _scale
        );
        _graphics.DrawRectangle(Pens.DarkRed, bounds);
        // 根据引脚配置绘制引脚细节
        if (transistor.PinConfiguration == Transistor.PinType.EBC)
        {
            // 绘制EBC引脚布局
        }
    }
}

// 穿孔板视图类
public class PerfboardView
{
    public Perfboard Board { get; }
    public BoardCursor Cursor { get; } = new BoardCursor();

    public PerfboardView(Perfboard board)
    {
        Board = board;
    }

    public void Draw(Graphics g)
    {
        g.TranslateTransform(Cursor.X * -25.4f, Cursor.Y * -25.4f);
        DrawHoles(g);

        // 创建绘图访客,遍历所有组件执行绘制
        var drawingVisitor = new ComponentDrawingVisitor(g);
        foreach (var component in Board.Components)
        {
            component.Accept(drawingVisitor);
        }

        Cursor.Draw(g);
    }

    private void DrawHoles(Graphics g)
    {
        // 绘制穿孔板孔位的逻辑
        for (int x = 0; x < Board.Width; x++)
        {
            for (int y = 0; y < Board.Height; y++)
            {
                var holePos = new PointF(x * _scale, y * _scale);
                g.FillEllipse(Brushes.Gray, holePos.X - 2, holePos.Y - 2, 4, 4);
            }
        }
    }
}

这个方案的优势:

  • 绝对的M-V分离,模型无任何视图依赖,二进制序列化完全不受影响
  • 彻底避免类型检查,每个组件的绘图逻辑对应专属的Visit方法
  • 新增组件时只需添加IComponent实现和对应的Visit方法,符合开闭原则

方案2:策略模式+类型匹配服务—— 无需修改现有模型

如果不想给模型添加Accept方法,可以用策略模式,为每个组件类型绑定一个绘图策略,通过类型匹配自动获取:

步骤1:模型层(无需修改,保持原有结构)

[Serializable]
public interface IComponent
{
    Point Position { get; set; }
}

[Serializable]
public class Wire : IComponent { /* 模型数据 */ }
[Serializable]
public class Transistor : IComponent { /* 模型数据 */ }
[Serializable]
public class Perfboard { /* 原有代码 */ }

步骤2:视图层的绘图策略与服务

using System.Drawing;
using System.Collections.Generic;

// 绘图策略接口
public interface IComponentDrawer
{
    void Draw(IComponent component, Graphics g, float scale);
}

// 导线绘图策略
public class WireDrawer : IComponentDrawer
{
    public void Draw(IComponent component, Graphics g, float scale)
    {
        var wire = (Wire)component; // 类型转换安全,因为按类型注册策略
        // 绘制逻辑同访客模式
    }
}

// 晶体管绘图策略
public class TransistorDrawer : IComponentDrawer
{
    public void Draw(IComponent component, Graphics g, float scale)
    {
        var transistor = (Transistor)component;
        // 绘制逻辑同访客模式
    }
}

// 策略注册与获取服务
public static class ComponentDrawerService
{
    private static readonly Dictionary<Type, IComponentDrawer> _drawers = new();

    static ComponentDrawerService()
    {
        // 注册所有组件的绘图策略
        RegisterDrawer<Wire>(new WireDrawer());
        RegisterDrawer<Transistor>(new TransistorDrawer());
    }

    public static void RegisterDrawer<T>(IComponentDrawer drawer) where T : IComponent
    {
        _drawers[typeof(T)] = drawer;
    }

    public static IComponentDrawer GetDrawer(IComponent component)
    {
        if (_drawers.TryGetValue(component.GetType(), out var drawer))
            return drawer;
        throw new InvalidOperationException($"未找到{component.GetType().Name}的绘图策略");
    }
}

// 修改PerfboardView的Draw方法
public void Draw(Graphics g)
{
    g.TranslateTransform(Cursor.X * -25.4f, Cursor.Y * -25.4f);
    DrawHoles(g);

    float scale = 25.4f;
    foreach (var component in Board.Components)
    {
        var drawer = ComponentDrawerService.GetDrawer(component);
        drawer.Draw(component, g, scale);
    }

    Cursor.Draw(g);
}

这个方案的优势:

  • 模型完全无需改动,适合已有模型代码的场景
  • 绘图策略可独立维护,新增组件只需添加策略并注册
  • 避免显式类型检查,类型匹配由服务层处理

二进制序列化注意事项

确保所有模型类标记[Serializable],且成员均为可序列化类型(如Point、基础类型、自定义枚举)。如果有自定义构造函数,需添加无参构造函数供序列化使用。

内容的提问来源于stack exchange,提问作者AntonJH

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 07:32:40