У меня такой тип:
// incomplete class definition
public class Person
{
private string name;
public string Name
{
get { return this.name; }
}
}
Я хочу, чтобы этот тип был созданный и обновлено с каким-то выделенным контроллером / построителем, но я хочу, чтобы он оставался только для чтения для других типов.
Этот объект также должен запускать событие каждый раз, когда он обновляется его контроллером / построителем.
Подводя итог, согласно предыдущему каркасу определения типа:
Person может быть создан только конкретным контроллером.Person (поле name) в любое времяPerson должен отправить уведомление остальному миру, когда это произойдет.PersonКак мне это реализовать? Я говорю здесь о контроллере / построителе, но все остальные решения приветствуются.
Примечание: Я мог бы полагаться на модификатор internal, но в идеале все мои вещи должны быть в одной сборке.





Мне нравится иметь интерфейс только для чтения. Затем строитель / контроллер / что угодно может ссылаться на объект напрямую, но когда вы открываете этот объект снаружи, вы показываете только интерфейс.
Используйте интерфейс 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.
}
}
Создайте интерфейс IReadOnlyPerson, который предоставляет только методы доступа. Попросите Person реализовать IReadOnlyPerson. Сохраните ссылку на Person в своем контроллере. Дайте другим клиентам только версию только для чтения.
Это защитит от ошибок, но не от мошенничества, как с большинством объектно-ориентированных функций. Клиенты могут выполнять приведение к Person, если они знают (или подозревают), что IReadOnlyPerson реализован Person.
Обновление, за комментарий:
Интерфейс только для чтения может также предоставлять делегат события, как и любой другой объект. Идиома, обычно используемая в C#, не мешает клиентам вмешиваться в список слушателей, но соглашение заключается только в добавлении слушателей, так что этого должно быть достаточно. Внутри любого метода доступа или функции set с побочными эффектами изменения состояния просто вызовите делегат события с защитой для случая null (без слушателей).
Ознакомьтесь с интерфейсом «System.ComponentModel.INotifyPropertyChanged», чтобы узнать о стандартном методе уведомления заинтересованных сторон об изменении свойства объекта.
Может что-то в этом роде?
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 в редакторе.
Я думаю, что internal - наименее сложный и лучший подход (это, конечно, предполагает использование нескольких сборок). Если не выполнять некоторые накладные расходы на интенсивный обход стека для определения вызывающего в установщике свойств, вы можете попробовать:
interface IPerson
{
Name { get; set; }
}
и явно реализовать этот интерфейс:
class Person : IPerson
{
Name { get; private set; }
string IPerson.Name { get { return Name; } set { Name = value; } }
}
затем выполните явное приведение интерфейсов в вашем построителе для установки свойств. Это по-прежнему не защищает вашу реализацию и не является хорошим решением, хотя каким-то образом подчеркивает ваше намерение.
В ваших установщиках свойств вам нужно будет реализовать уведомление о событии. Подходя к этой проблеме самостоятельно, я бы не стал создавать отдельные события и обработчики событий для каждого свойства, а вместо этого создавал бы одно событие PropertyChanged и запускал его в каждом свойстве, когда происходит изменение (где аргументы события будут включать имя свойства, старое значение и новое значение. ).
Кажется странным, что, хотя я не могу изменить имя объекта 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; }
}
}
Этот ответ может быть хорошим, но он не охватывает вторую часть вопроса: Человеку необходимо отправить уведомление остальному миру, когда это произойдет.