【问题标题】:Delegates as Properties: Bad Idea?代表属性:坏主意?
【发布时间】:2011-11-26 08:46:49
【问题描述】:

考虑以下控件(为简洁起见):

public partial class ConfigurationManagerControl : UserControl
{

    public Func<string, bool> CanEdit { get; set;}
    public Func<string, bool> CanDelete { get; set; }

    public Dictionary<string, string> Settings
    {
        get { return InnerSettings; }
        set
        {
            InnerSettings = value;
            BindData();
        }
    }
    private Dictionary<string, string> InnerSettings;

    private void OnListIndexChanged(object sender, EventArgs e)
    {
        this.EditButton.Enabled = false;
        this.DeleteButton.Enabled = false;

        var indices = this.List.SelectedIndices;
        if (indices.Count != 1)
        {
            return;
        }

        var index = indices[0];
        var item = this.List.Items[index];

        if (this.CanEdit != null)
        {
            this.EditButton.Enabled = this.CanEdit(item.Text);
        }

        if (this.CanDelete != null)
        {
            this.DeleteButton.Enabled = this.CanDelete(item.Text);
        }

    }
}

此控件还有更多功能,但只要说它允许用户添加、编辑和删除 Dictionary 中的条目就足够了。为了确定是否应该允许用户编辑或删除条目,它使用了表单提供的委托方法properties、CanDelete和CanEdit或托管它的控件:

public class SetupWizard : Form
{
    public SetupWizard()
    {
        InitializeComponent();

        this.SettingManager.CanEdit = CanEditSetting;
        this.SettingManager.CanDelete = CanDeleteSetting;
    }

    private static bool CanEditSetting(string item)
    {
        var lockedSettings = new[] { "LicenseHash", "ProductHash" };
        return !lockedSettings.Contains(item.ToLower());
    }

    private static bool CanDeleteSetting(string item)
    {
        var lockedSettings = new[] {
                                        "LicenseHash",
                                        "ProductHash", 
                                        "UserName", 
                                        "CompanyName"
                                    };
        return !lockedSettings.Contains(item.ToLower());
    }
}

我发现这种设计既令人满意又令人担忧。一方面,它似乎使用最简单的有效解决方案来解决问题(它肯定很好地分离了关注点)。另一方面,我有一个令人烦恼的问题,即我不正确地使用了委托,应该使用一个事件,而不是(即使我确实 不需要 需要多个侦听器,并且只需要调用者告诉我是否该项目是可编辑的)。

然后,另一方面,有可能存在一种我什至没有考虑过的完全不同的设计,它可能会以一种非常优越的方式解决问题。

所以。这种设计在技术上是否正确、可维护和灵活?还是我应该做得更好?

【问题讨论】:

  • 似乎这个问题更适合 codereview.se。
  • 您应该看看(但不使用)WPF 中的 RoutedCommands。如果您使用的是 WPF,那就另当别论了……
  • 遗憾的是,还没有在 WPF 上。仍在经典的 WinForms 上。

标签: c# delegates software-design


【解决方案1】:

这可能是@Daniel Hilgarth 在说“使用接口”时所建议的内容(注意 - 他的回答现在反映了实现接口的更通用/更灵活的方法)。与其直接将委托分配给您的方法,不如给控件一个属性,例如DataState 或您想调用的任何内容,使用封装您需要的信息的接口,并让所有者决定如何来实现它。

interface IDataState
{
    bool CanEdit(string item);
    bool CanDelete(string item);
}

public partial class ConfigurationManagerControl : UserControl
{
    public IDataState DataState {get;set;}
    // your code checks DataState.CanEdit & DataState.CanDelete
}

public class SetupWizard : Form, IDataState
{
    public SetupWizard()
    {
        InitializeComponent();
        SettingManager.DataState =this;
    }
    public bool CanEdit(string item)
    { 
        ... implement directly or return from your private function
    }
    public bool CanDelete(string item)
    { 

    }
}

但这使您可以灵活地以任何您选择的方式实现该接口,使用另一个对象等,并且它还可以很容易地传递所有者本身(实现接口)。

【讨论】:

    【解决方案2】:

    我建议使用这两种方法的接口。干净多了:

    interface ICantThinkOfAGoodName
    {
        bool CanEdit(string item);
        bool CanDelete(string item);
    }
    

    您可以创建类似于许多 MVVM 框架中使用的 RelayCommand 的东西:

    public class RelayObject : ICantThinkOfAGoodName
    {
        public RelayObject() : this(null, null) {}
        public RelayObject(Func<string, bool> canEdit, Func<string, bool> canDelete)
        {
            if(canEdit == null) canEdit = s => true;
            if(canDelete == null) canDelete = s => true;
    
            _canEdit = canEdit;
            _canDelete = canDelete;
        }
    
        public bool CanEdit(string item)
        {
            return _canEdit(item);
        }
        public bool CanDelete(string item)
        {
            return _canDelete(item);
        }
    }
    

    像这样使用它:

    public SetupWizard()
    {
        InitializeComponent();
    
        this.SettingManager.PropertyName = new RelayObject(CanEditSetting, 
                                                           CanDeleteSetting);
        // or (all can be deleted)
        this.SettingManager.PropertyName = new RelayObject(CanEditSetting, null);
        // or (all can be edited)
        this.SettingManager.PropertyName = new RelayObject(null, CanDeleteSetting);
        // or (all can be edited and deleted)
        this.SettingManager.PropertyName = new RelayObject();
    
    }
    

    顺便说一句:我在这里使用属性注入,因为它是一个控件。通常,我会在ConfigurationManagerControl 的构造函数中传递ICantThinkOfAGoodName 依赖项。

    【讨论】:

    • 我实际上考虑过这条路。那么问题是我必须改造任何想要承载 SettingControl 以实现 ISettingManager 的表单或控件。这对我来说似乎不太理想。单独的委托方法的优点是容器在不需要它们时不必实现任何一种方法。如果 CanDelete 不存在,则所有项目都是可删除的。无法通过界面来解决这个问题。
    • 顺便说一句,这并不意味着我是对的——这就是我排除它的原因! :)
    • @Mike Hofer 您仍然可以将代表包装到一个对象中,以确保它们被捆绑到一个地方。在您的示例中,如果 CanDelete 不存在,则该按钮将保持禁用状态。似乎默认值取决于形式,在这种情况下,改造继承可能不是一个坏主意。
    • @Daniel Hilgarth:我不得不说,RelayObject 与界面的结合非常漂亮。它是两全其美的,在保持关注点分离的同时满足了我对代码极简主义的感觉。我想我们赢了!
    • @MikeHofer:很高兴我能帮上忙。那是SRP 应用:-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多