C#中需要的某种创建模式

时间:2020-03-05 18:59:47  来源:igfitidea点击:

我有以下类型:

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

    public string Name
    {
        get { return this.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,该接口仅公开get访问器。让Person实现IReadOnlyPerson。将对Person的引用存储在控制器中。仅向其他客户端提供只读版本。

与大多数OO功能一样,这可以防止错误,但不能防止欺诈。如果客户端碰巧知道(或者怀疑)IReadOnlyPerson由Person实现,则可以将运行时强制转换为Person。

根据评论更新:

只读接口也可以公开事件委托,就像其他任何对象一样。 C语言中通常使用的惯用法不会阻止客户端弄乱侦听器列表,但是约定只是添加侦听器,因此应该足够了。在任何具有状态改变副作用的集合访问器或者函数中,只需在事件为null(无侦听器)的情况下使用守护程序调用事件委托即可。

回答

也许是这样吗?

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方法。

回答

我认为"内部"是最简单,最好的方法(当然,这涉及多个程序集)。只需执行一些开销大的堆栈遍历来确定属性设置器中的调用方,我们可以尝试:

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