C#使属性原子化的基础知识

C#使属性原子化的基础知识,c#,design-patterns,C#,Design Patterns,它是由微软提供的一个框架设计指南,应该相互独立,不依赖于以任何特定顺序设置 假设您有一个三角形类,它需要支持调光和面积计算。您将如何对此进行建模 这当然是被认为是笨拙的设计,因为面积取决于首先设置的基础和高度: class Triangle{ public double Base {get;set;} public double Height {get;set;} public double Area { get{ return (

它是由微软提供的一个框架设计指南,应该相互独立,不依赖于以任何特定顺序设置

假设您有一个三角形类,它需要支持调光和面积计算。您将如何对此进行建模

这当然是被认为是笨拙的设计,因为面积取决于首先设置的基础和高度:

class Triangle{
    public double Base {get;set;}
    public double Height {get;set;}
    public double Area {
        get{
            return (Base * Height) / 2;
        }
    }
}
假设您使用构造函数,您可以确保这种情况下的默认值,但这是正确的方法吗

class Triangle{
    public Triangle(double b, double h){
        Base = b;
        Height = h;
    }

    public double Base {get;set;}
    public double Height {get;set;}
    public double Area {
        get{
            return (Base * Height) / 2;
        }
    }
}
您仍然有一个依赖于其他属性的属性。为了成为一名纯粹主义者,我只能看到几种方法(我想它们可以结合使用):

  • 使基础/高度具有只能在构造函数中设置的只读成员

  • 将面积计算转化为一种方法

  • 使用某种factory模式+只读成员来确保尽管依赖关系可能存在,但只能通过实例化Triangle类的方法来设置值

  • 问题:

  • 该指南实用吗(为了支持它,您是否必须在类中建模大量的复杂性)? [例如,SqlConnection类允许您初始化connection string属性,但允许您更改其各个部分,如命令超时]

  • 您如何使您的财产相互独立

  • 另外,对于使用Silverlight/MVVM类型体系结构的人,您是否因为对象的数据绑定方式而接受属性中的依赖关系?例如,绑定在屏幕上显示高度、底面和面积的三角形实例


  • Microsoft实际上是在试图说“不要以这样的方式设计类:以任意顺序调用属性将导致意外行为”。您的类的用户不希望请求值(使用属性)会产生尴尬的副作用

    这符合最不出人意料的原则

    我认为这是一个完全实用的指导方针。属性不应有意外的副作用

    你提出了一个很好的问题:“你如何使你的财产彼此独立?”。非常小心!我尽可能地消除状态(并相应地减少属性的数量)。另外,通过划分类来划分状态也是一种策略。这本身就是一个很好的问题


    就三角形类而言,我认为您在代码中提供的两种解决方案都是有效的。如果由我决定,我会设计三角形,使其不可变,并在构造函数中采用宽度和高度。

    最简单的方法是只在构造函数中接受底部和高度值,然后将它们全部作为只读属性公开:

    class Triangle
    {
       private double base;
       private double height;
    
       private Triangle() { }
    
       public Triangle(double base, double height)
       {
          this.base = base;
          this.height = height;
       }
    
       public double Base 
       {
          get 
          {
             return this.base;
          }
       }
    
       public double Height 
       {
          get 
          {
             return this.height;
          }
       }
    
       public double Area 
       {
          get 
          {
             return (this.base * this.height) / 2;
          }
       }
    }
    

    我想我会选择一个三角形类,它有一个构造函数,它以底和高度为参数,因为没有底或高度,三角形就不可能存在(imho)

    当然,Area属性依赖于Base&Height,但我认为这没有问题? 我认为MS指南应解释如下:

    属性应该独立于 彼此之间不依赖存在 按任何特定顺序设置

    这意味着-imho-您不应该有一个必须首先设置基本属性的约束,并且只有在设置了基本属性之后,您才能设置高度属性。
    我认为这就是上述指引的意思


    除此之外,您的Area属性是不可设置的,因此它没有问题。:)

    某些属性将具有内在的相关性和相互依赖性。重点是,先设置“基础”,然后设置“高度”,应该与先设置“高度”,然后设置“基础”具有相同的效果。如果不设置其中一个,则检查该区域将没有意义。

    选择选项2。将Area设置为一个方法,并将其称为CalculateArea。属性应该用来封装状态,而不是行为。面积可以从底部和高度计算,但这并不意味着它本身应该是状态;相反,这是一个强有力的论点,即该地区不应是国家

    class Triangle
    {
        public Triangle(double b, double h)
        {
            Base = b;
            Height = h;
        }
    
        public double Base { get; set; }
        public double Height { get; set; }
    
        public double CalculateArea()
        {
            return (Base * Height) / 2;
        }
    }
    

    这让消费者知道,获取三角形区域需要实际工作,而不是简单地访问状态。

    为什么要将它们公开为只读?那么,你不能改变三角形。。。当然,如果您将三角形视为一种值类型(因此是不可变的),那么您就有了一个点,但是在本例中是否有必要将三角形视为一种值类型。。。我认为话题发起人刚刚错误地解释了指南。:)除非你有令人信服的理由不这样做,否则一定要使对象不可变。三角形就是一个很好的例子——你到底为什么要改变它而不是创建一个新的呢?这并不是说可变对象一开始很可怕,而是将一个对象放入多线程场景中,突然之间,除了“三角形”对象的程序员决定使用setter进行编程外,你没有什么好的理由需要同步。你不能使用base作为变量名,它是一个关键字。它也不能是参数名和类变量名