Создание версий классов только для чтения в сложной структуре объектов - PullRequest
4 голосов
/ 27 августа 2010

В моем текущем проекте мне нужно иметь как редактируемые, так и доступные только для чтения версии классов.Таким образом, когда классы отображаются в List или PropertGrid, пользователь не может редактировать объекты, на которые ему нельзя разрешать.

Для этого я следую шаблону проектирования, показанному на диаграмме ниже.Я начинаю с интерфейса только для чтения (IWidget), а затем создаю редактируемый класс, который реализует этот интерфейс (Widget).Затем я создаю класс только для чтения (ReadOnlyWidget), который просто оборачивает изменяемый класс, а также реализует интерфейс только для чтения.

Я следую этому шаблону для ряда различных несвязанных типов.Но теперь я хочу добавить функцию поиска в мою программу, которая может генерировать результаты, которые включают любые типы, включая как изменяемые, так и неизменяемые версии.Поэтому теперь я хочу добавить еще один набор интерфейсов (IItem, IMutableItem), которые определяют свойства, которые применяются ко всем типам.Таким образом, IItem определяет набор общих неизменяемых свойств, а IMutableItem определяет те же свойства, но редактируемые.В конце поиска будет возвращен набор IItems, который затем можно будет при необходимости привести к более конкретным типам.

Тем не менее, я не уверен, что я устанавливаю отношения к IMutable и IItem правильно.Прямо сейчас у меня есть каждый из интерфейсов (IWidget, IDooHickey), наследуемых от IItem, и затем изменяемые классы (Widget, DooHickey), кроме того, также реализуют IMutableItem.

В качестве альтернативы я также подумал, что мог бы установить IMutableItem для наследования от IItem, что скрыло бы его свойства только для чтения с новыми свойствами, которые имеют как get, так и set accessors.Тогда изменяемые классы реализуют IMutableItem, а классы только для чтения - IItem.

Буду признателен за любые предложения или критику по этому поводу.

Диаграмма классов

alt text

Код

public interface IItem
{
    string ItemName { get; }
}

public interface IMutableItem
{
    string ItemName { get; set; }
}

public interface IWidget:IItem
{
    void Wiggle();
}

public abstract class Widget : IWidget, IMutableItem
{
    public string ItemName
    {
        get;
        set;
    }

    public void Wiggle()
    {
        //wiggle a little
    }
}

public class ReadOnlyWidget : IWidget
{
    private Widget _widget;
    public ReadOnlyWidget(Widget widget)
    {
        this._widget = widget;
    }

    public void Wiggle()
    {
        _widget.Wiggle();
    }

    public string ItemName
    {
        get {return  _widget.ItemName; }
    }
}

public interface IDoohickey:IItem
{
    void DoSomthing();
}


public abstract class Doohickey : IDoohickey, IMutableItem
{
    public void DoSomthing()
    {
        //work it, work it
    }

    public string ItemName
    {
        get;
        set;
    }
}

public class ReadOnlyDoohickey : IDoohickey
{
    private Doohickey _doohicky;
    public ReadOnlyDoohickey(Doohickey doohicky)
    {
        this._doohicky = doohicky;
    }

    public string ItemName
    {
        get { return _doohicky.ItemName; }
    }

    public void DoSomthing()
    {
        this._doohicky.DoSomthing();
    }
}

Ответы [ 4 ]

1 голос
/ 17 января 2012

Я бы предложил, чтобы для каждого основного класса или интерфейса было три определенных класса: «читаемый» класс, «изменяемый» класс и «неизменяемый» класс. Только «изменяемые» или «неизменяемые» классы должны существовать как конкретные типы; они оба должны происходить из абстрактного «читаемого» класса. Код, который хочет хранить объект в безопасности, зная, что он никогда не изменяется, должен хранить «неизменный» класс; код, который хочет редактировать объект, должен использовать «изменяемый» класс. Код, который не собирается записывать что-либо, но не заботится о том, чтобы он всегда имел одно и то же значение, может принимать объекты «читаемого» базового типа.

Читаемая версия должна включать открытые абстрактные методы AsChangeable(), AsImmutable(), открытый виртуальный метод AsNewChangeable() и защищенный виртуальный метод AsNewImmutable(). «Изменяемые» классы должны определять AsChangeable() для возврата this и AsImmutable для возврата AsNewImmutable(). «Неизменяемые» классы должны определять AsChangeable() для возврата AsNewChangeable() и AsImmutable() для возврата this.

Самая большая трудность во всем этом заключается в том, что наследование работает не очень хорошо, если кто-то пытается использовать типы классов, а не интерфейсы. Например, если вы хотите иметь класс EnhancedCustomer, который наследуется от BasicCustomer, то ImmutableEnhancedCustomer должен наследоваться от ImmutableBasicCustomer и ReadableEnhancedCustomer, но .net не допускает такого двойного наследования. Можно использовать интерфейс IImmutableEnhancedCustomer, а не класс, но некоторые люди считают, что «неизменяемое взаимодействие» является чем-то вроде запаха, поскольку нет способа, чтобы модуль, определяющий интерфейс таким образом, чтобы посторонние могли использовать его без также позволяет посторонним определять свои собственные реализации.

1 голос
/ 30 августа 2010

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

0 голосов
/ 14 февраля 2013

Может быть слишком поздно :-), но причина "Ключевое слово 'new' требуется для свойства, потому что оно скрывает свойство ..." - ошибка в Resharper, нет проблем с компилятором.См. Пример ниже:

public interface IEntityReadOnly
{
    int Prop { get; }
}


public interface IEntity : IEntityReadOnly
{
    int Prop { set; }
}

public class Entity : IEntity
{
    public int Prop { get; set; }
}

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        var entity = new Entity();
        (entity as IEntity).Prop = 2;
        Assert.AreEqual(2, (entity as IEntityReadOnly).Prop);
    }
}

То же самое для случая без интерфейсов.Единственное ограничение, вы не можете использовать авто-свойства

public class User
{
    public User(string userName)
    {
        this.userName = userName;
    }

    protected string userName;
    public string UserName { get { return userName; } }
}

public class UserUpdatable : User
{
    public UserUpdatable()
        : base(null)
    {
    }

    public string UserName { set { userName = value; } }
}

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        var user = new UserUpdatable {UserName = "George"};
        Assert.AreEqual("George", (user as User).UserName);
    }
}
0 голосов
/ 15 января 2012

Оставь надежду на всех, кто сюда входит !!!

Я подозреваю, что в конечном счете твой код будет очень запутанным.Ваша диаграмма классов показывает, что все свойства доступны для редактирования (или нет) в данном объекте.Или ваши (я) изменяемые интерфейсы вводят новые свойства, которые являются неизменяемыми или нет, отдельно от «базового» / наследующего класса?

В любом случае, я думаю, вы в конечном итоге будете играть в игрыс изменениями имени свойства и / или скрытием унаследованных свойств

Интерфейсы маркеров Возможно?
Подумайте о том, чтобы сделать все свойства в классах изменяемыми.Затем реализуйте IMutable (мне не нравится имя IItem) и IImutable в качестве маркеров интерфейсов.То есть в теле интерфейса буквально ничего не определено.Но это позволяет клиентскому коду обрабатывать объекты в качестве ссылки IImutable, например.

Это подразумевает, что (а) ваш клиентский код работает хорошо и учитывает его изменчивость, или (б) все ваши объекты обернутыкласс «controller», который обеспечивает изменчивость данного объекта.

...