Какой-то творческий паттерн нужен в C # - PullRequest
4 голосов
/ 17 сентября 2008

У меня есть следующий тип:

// incomplete class definition
public class Person
{
    private string name;

    public string Name
    {
        get { return this.name; }
    }
}

Я хочу, чтобы этот тип был создан и обновлен с помощью какого-то специального контроллера / компоновщика, но я хочу, чтобы он оставался только для чтения для других типов .

Этот объект также должен запускать событие каждый раз, когда он обновляется контроллером / строителем.

Подводя итог, согласно предыдущему определению типа скелета:

  • Person может быть создан только конкретным контроллером
  • Этот контроллер может обновлять состояние Person (name поле) в любое время
  • Person необходимо отправить уведомление остальному миру, когда это произойдет
  • Все остальные типы должны иметь возможность читать Person атрибуты

Как мне это реализовать? Я говорю о контроллере / сборщике здесь, но приветствуются все другие решения.

Примечание: Я мог бы положиться на модификатор internal, но в идеале все мои вещи должны быть в одной сборке.

Ответы [ 6 ]

5 голосов
/ 17 сентября 2008

Создайте интерфейс IReadOnlyPerson, который предоставляет доступ только для доступа. Попросите человека реализовать IReadOnlyPerson. Сохраните ссылку на Персона в вашем контроллере. Дайте другим клиентам только версию только для чтения.

Это защитит от ошибок, но не от мошенничества, как с большинством функций OO. Клиенты могут во время выполнения привести к Person, если они узнают (или подозревают), что Person реализован IReadOnlyPerson.

Обновление, за комментарий:

Интерфейс только для чтения может также предоставлять делегат события, как и любой другой объект. Идиома, обычно используемая в C #, не препятствует тому, чтобы клиенты возились со списком слушателей, но соглашение состоит только в том, чтобы добавить слушателей, поэтому этого должно быть достаточно. Внутри любого метода доступа или функции с побочными эффектами, изменяющими состояние, просто вызовите делегат события с защитой для нулевого (без слушателей) случая.

1 голос
/ 18 сентября 2008

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

Но, несмотря на это, есть способ сделать это:

    /// <summary>
    /// A controlled person.  Not production worthy code.
    /// </summary>
    public class Person
    {
        private string _name;
        public string Name
        {
            get { return _name; }
            private set
            {
                _name = value;
                OnNameChanged();
            }
        }
        /// <summary>
        /// This person's controller
        /// </summary>
        public PersonController Controller
        {
            get { return _controller ?? (_controller = new PersonController(this)); }
        }
        private PersonController _controller;

        /// <summary>
        /// Fires when <seealso cref="Name"/> changes.  Go get the new name yourself.
        /// </summary>
        public event EventHandler NameChanged;

        private void OnNameChanged()
        {
            if (NameChanged != null)
                NameChanged(this, EventArgs.Empty);
        }

        /// <summary>
        /// A Person controller.
        /// </summary>
        public class PersonController
        {
            Person _slave;
            public PersonController(Person slave)
            {
                _slave = slave;
            }
            /// <summary>
            /// Sets the name on the controlled person.
            /// </summary>
            /// <param name="name">The name to set.</param>
            public void SetName(string name) { _slave.Name = name; }
        }
    }
1 голос
/ 17 сентября 2008

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

interface IPerson 
{
    Name { get; set; } 
}

и реализовать этот интерфейс явно:

class Person : IPerson 
{
    Name { get; private set; }
    string IPerson.Name { get { return Name; } set { Name = value; } } 
}

затем выполните явное приведение интерфейса в вашем конструкторе для установки свойств. Это по-прежнему не защищает вашу реализацию и не является хорошим решением, хотя оно как-то подчеркивает ваши намерения.

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

1 голос
/ 17 сентября 2008

Используйте интерфейс IPerson и вложенный класс:

public class Creator
{
    private class Person : IPerson
    {
        public string Name { get; set; }
    }

    public IPerson Create(...) ...


    public void Modify(IPerson person, ...)
    {
        Person dude = person as Person;
        if (dude == null)
            // wasn't created by this class.
        else
            // update the data.
    }
}
1 голос
/ 17 сентября 2008

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

0 голосов
/ 17 сентября 2008

Может быть, что-то подобное?

public class Person
{
    public class Editor
    {
        private readonly Person person;

        public Editor(Person p)
        {
            person = p;
        }

        public void SetName(string name)
        {
            person.name = name;
        }

        public static Person Create(string name)
        {
            return new Person(name);
        }
    }

    protected string name;

    public string Name
    {
        get { return this.name; }
    }

    protected Person(string name)
    {
        this.name = name;
    }
}

Person p = Person.Editor.Create("John");
Person.Editor e = new Person.Editor(p);
e.SetName("Jane");

Не красиво, но я думаю, что это работает. В качестве альтернативы вы можете использовать свойства вместо методов SetX в редакторе.

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