【问题标题】:Some sort of creational pattern needed in C#C# 中需要的某种创建模式
【发布时间】:2008-09-17 19:32:02
【问题描述】:

我有以下类型:

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

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

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

这个对象也需要在每次被它的控制器/构建器更新时触发一个事件。

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

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

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

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

【问题讨论】:

    标签: c# .net design-patterns


    【解决方案1】:

    创建一个接口 IReadOnlyPerson,它只公开 get 访问器。让 Person 实现 IReadOnlyPerson。将 Person 的引用存储在您的控制器中。只为其他客户端提供只读版本。

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

    根据评论更新:

    只读接口也可以公开一个事件委托,就像任何其他对象一样。 C#中常用的习惯用法并不能防止客户端弄乱侦听器列表,但约定只是添加侦听器,所以应该足够了。在任何具有状态更改副作用的 set 访问器或函数中,只需调用带有保护 null(无侦听器)情况的事件委托。

    【讨论】:

    • 这个答案可能是一个不错的答案,但它没有涵盖问题的第二部分:当它发生时,这个人需要向世界其他地方发送通知
    • 查看“System.ComponentModel.INotifyPropertyChanged”接口,了解通知相关方对象属性已更改的标准方法。
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      使用接口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.
          }
      }
      

      【讨论】:

        【解决方案4】:

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

        interface IPerson 
        {
            Name { get; set; } 
        }
        

        并显式实现此接口:

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

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

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

        【讨论】:

          【解决方案5】:

          看起来很奇怪,虽然我不能更改 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; }
                  }
              }
          

          【讨论】:

            【解决方案6】:

            也许是这样的?

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-03-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2022-01-13
              相关资源
              最近更新 更多