4

我有以下类型:

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

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

我希望使用某种专用的控制器/构建器来创建更新这种类型,但我希望它对其他类型保持只读

该对象还需要在其控制器/构建器每次更新时触发一个事件。

总结一下,根据前面的类型定义骨架:

  • Person只能由特定控制器实例化
  • 该控制器可以随时更新Personname字段)的状态
  • 发生时需要向世界其他地方Person发送通知
  • 所有其他类型应该只能读取 Person属性

我应该如何实现这个?我在这里谈论的是控制器/构建器,但欢迎所有其他解决方案。

注意:我可以依赖internal修饰符,但理想情况下我所有的东西都应该在同一个程序集中。

4

6 回答 6

5

Create an interface IReadOnlyPerson which exposes only get accessors. Have Person implement IReadOnlyPerson. Store the reference to Person in your controller. Give other clients only the read only version.

This will protect against mistakes, but not fraud, as with most OO features. Clients can runtime cast to Person if they happen to know (or suspect) IReadOnlyPerson is implemented by Person.

Update, per the comment:

The Read Only interface may also expose an event delegate, just like any other object. The idiom generally used in C# doesn't prevent clients from messing with the list of listeners, but convention is only to add listeners, so that should be adequate. Inside any set accessor or function with state-changing side effects, just call the event delegate with a guard for the null (no listeners) case.

于 2008-09-17T19:39:35.013 回答
1

我认为internal是最简单和最好的方法(这当然涉及多个程序集)。如果不做一些开销密集的堆栈遍历来确定属性设置器中的调用者,您可以尝试:

interface IPerson 
{
    Name { get; set; } 
}

并显式实现此接口:

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

然后在构建器中执行显式接口转换以设置属性。这仍然不能保护您的实现,也不是一个好的解决方案,尽管它确实在某种程度上强调了您的意图。

在您的属性设置器中,您必须实现事件通知。我自己解决这个问题我不会为每个属性创建单独的事件和事件处理程序,而是创建一个 PropertyChanged 事件并在发生更改时在每个属性中触发它(其中事件参数将包括属性名称、旧值和新值)。

于 2008-09-17T19:48:09.050 回答
1

奇怪的是,虽然我无法更改 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; }
        }
    }
于 2008-09-17T20:00:17.537 回答
1

Use an interface IPerson and a nested class:

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.
    }
}
于 2008-09-17T19:38:09.137 回答
1

我喜欢有一个只读界面。然后builder/controller/whatever可以直接引用该对象,但是当您将该对象暴露给外部时,您只显示接口。

于 2008-09-17T19:34:31.733 回答
0

Maybe something like that ?

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

Not pretty, but I think it works. Alternatively you can use properties instead of SetX methods on the editor.

于 2008-09-17T19:41:45.990 回答