2008-09-17 48 views
4

我有以下类型:某种在C#中需要创建模式的

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

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

我想这个类型为创建更新带有某种专用控制器/建设者,但我想它保持对于其他类型的只读

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

为了总结,根据前述类型定义骨架:

  • Person只能由特定的控制器被实例化
  • 该控制器能更新Personname场)的状态在任何时间
  • Person需要发送通知到世界其他地方时
  • 所有其他类型应该只能够读取Person属性

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

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

回答

1

我喜欢有一个只读接口。然后,构建器/控制器/任何可以直接引用该对象的方法,但是当您将该对象暴露给外部时,只会显示该接口。

1

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

创建界面IReadOnlyPerson暴露只有get访问。有人实施IReadOnlyPerson。将引用存储在您的控制器中。给其他客户端只读只读版本。

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

更新,每评论:

的只读接口也暴露事件委托,就像任何其他对象。在C#中通常使用的惯用法并不能阻止客户端混淆侦听器列表,但惯例只是添加侦听器,因此应该足够。在具有状态改变副作用的任何set访问器或函数中,只需调用具有空值(无侦听器)情况的警卫的事件委托。

+0

这个答案可能是好的,但它并不能掩盖问题的第二部分: 的人需要发送的通知发送到世界其他地方 – Seb 2008-09-18 07:51:28

0

也许这样的事情?

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

1

我认为internal是最简单和最好的方法(这当然涉及多个程序集)。短做一些开销密集堆栈行走,以确定在属性setter调用者的你可以尝试:

interface IPerson 
{ 
    Name { get; set; } 
} 

和实现这个接口明确:

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

然后执行显式接口在你的建设者蒙上设置属性。这仍然不能保护你的实施,并不是一个好的解决方案,尽管它确实有助于强调你的意图。

在你的财产制定者,你将不得不实施一个事件通知。自己处理这个问题我不会为每个属性创建单独的事件和事件处理程序,而是创建单个PropertyChanged事件并在发生更改时在每个属性中触发它(事件参数将包含属性名称,旧值和新值)。

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