Делегаты как свойства: плохая идея? - PullRequest
15 голосов
/ 28 сентября 2011

Рассмотрим следующий элемент управления (сокращенный для краткости):

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);
        }

    }
}

Это еще не все, но достаточно сказать, что он позволяет пользователю добавлять, редактировать и удалять записи в словаре.,Чтобы определить, должен ли он разрешать пользователю редактировать или удалять записи, он использует метод делегата 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());
    }
}

Я считаю, что этот дизайн одновременно и удовлетворительный, и вызывающий беспокойство.С одной стороны, кажется, что она решает проблему, используя простейшее решение, которое работает (оно, безусловно, хорошо разделяет проблемы).С другой стороны, меня беспокоит то, что я использую делегатов ненадлежащим образом и должен использовать событие вместо этого (хотя мне не нужно несколько слушателей, и мне нужен только вызывающий, чтобы сказать мне, еслиэлемент редактируемый).

А потом, с другой стороны, есть вероятность, что есть совершенно другой дизайн, который я даже не рассматривал, который мог бы решить проблему значительно превосходящим способом.

Итак.Является ли этот дизайн технически правильным, ремонтопригодным и гибким?Или я должен делать что-то лучше?

Ответы [ 2 ]

11 голосов
/ 28 сентября 2011

Я предлагаю использовать интерфейс с этими двумя методами. Это намного чище:

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

Вы можете создать что-то похожее на RelayCommand, используемый во многих средах MVVM:

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();

}

Кстати: я использую инъекцию свойств здесь, потому что это элемент управления. Обычно я передаю зависимость ICantThinkOfAGoodName в конструкторе ConfigurationManagerControl.

3 голосов
/ 28 сентября 2011

Может быть, это то, что @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)
    { 

    }
}

Но это дает вам гибкость для реализации этого интерфейса любым выбранным вами способом, с другим объектом и т. Д., А также позволяет просто передать самого владельца (реализацию интерфейса).

...