【问题标题】:C#: How would you suggest to refactor these classes? (Interfaces, Aggregation or anything else)C#:您建议如何重构这些类? (接口,聚合或其他任何东西)
【发布时间】:2009-06-30 18:05:24
【问题描述】:

在两个不同的程序集中有以下类:

class Member
{
    public string Label {get;set;}
    // Lots of other fields...

    public double Thickness {get;set;}
    public double Width {get;set;}
    public double Length {get;set;}
    public double GetVolume ()
    {
        return Thickness * Width * Length;
    }

    // Other methods
}

class OtherMember
{
    public string CompositeLabel {get;set;}
    // Lots of other fields (not related to other class: Member)

    public double Thickness {get;set;}
    public double Width {get;set;}
    public double Length {get;set;}
    public double GetVolume ()
    {
        return Thickness * Width * Length;
    }

    // Other methods
}

我想将厚度、宽度、长度属性和 GetVolume 方法重构为另一个类(尺寸?)作为 DRY...

然后我可以在这些类中拥有一个字段/属性来访问维度实例。

这是一个好方法吗?

我已经简洁地阅读了有关使用接口而不是具体类的信息,我应该从接口继承吗?

另外,不变性呢?

我觉得我经常迷失在正确地设置我的域类。有人对此有什么建议吗?

【问题讨论】:

    标签: c# refactoring interface model


    【解决方案1】:

    由于您有很多共同的状态和行为,我会推荐一个基类。试试这样的:

    class Member
    {
        public string Label { get; set; }
        public double Thickness { get; set; }
        public double Width { get; set; }
        public double Length { get; set; }
        public double GetVolume()
        {
            return Thickness * Width * Length;
        }
    }
    
    class OtherMember : Member
    {
        public string CompositeLabel { get; set; }
    }
    

    根据Label 属性的用途,您可以选择将其设为虚拟并覆盖派生类中的实现:

    class Member
    {
        public virtual string Label { get; set; }
        public double Thickness { get; set; }
        public double Width { get; set; }
        public double Length { get; set; }
        public double GetVolume()
        {
            return Thickness * Width * Length;
        }
    }
    
    class OtherMember : Member
    {
        string label;
    
        public override string Label
        {
            get { return this.label; }
            set { this.label = value; }
        }
    }
    

    【讨论】:

    • Member 和 OtherMember 可能是不相关的类型(这会使继承变得不合适),这取决于所谓的“// 其他方法”方法可能是什么。
    • @ChrisW:是的,它们可能不相关,但鉴于它们 90% 相同,我认为它们很有可能像我建议的那样合并.您仍然提出了一个很好的观点,这是需要考虑的事情:)
    • 也许 Member 和 OtherMember 是相关的,但它们更像是兄弟关系(同一级别)而不是父子关系,在这种情况下,我可能会创建一个公共基类,其中 Member 和 OtherMember可以得出。然后,让基类包含共享方法,例如 GetVolume 和 getter/setter,例如宽度。
    • 这些类有些相关,但 Member 中有一些方法在 OtherMember 中没有意义,反之亦然......
    • 它们可能有 90% 的不同(使用“许多其他字段”和“其他方法”),并且 OP 仅显示其中 10% 的相同。继承不是将相同功能引入两个类的唯一方法。
    【解决方案2】:

    包含还是继承?

    如果这两个东西都是维度,那么可以将属性放入一个名为Dimensions的超类中,这些东西都从该超类中继承。

    如果这两个东西都有维度,那么可以将属性放入名为Dimensions的类中,这些东西可以作为属性/数据成员包含。

    其他人已经展示了继承的样子;这就是收容的样子:

    class Dimensions
    {
        public double Thickness {get;set;}
        public double Width {get;set;}
        public double Length {get;set;}
        public double GetVolume ()
        {
            return Thickness * Width * Length;
        }
    }
    
    class Member
    {
        public string Label {get;set;}
        // Lots of other fields...
        public Dimensions Dimensions {get;set;}
    
        // Other methods
    }
    
    class OtherMember
    {
        public string CompositeLabel {get;set;}
        // Lots of other fields (not related to other class: Member)
    
        public Dimensions Dimensions {get;set;}
    
        // Other methods
    }
    

    有些人说,作为设计原则或经验法则,“更喜欢包含而不是继承”;另见Inheritance vs. Aggregation


    接口而不是具体的类?

    接口而不是具体的类增加了一个额外的间接层。它们在两个方面优于子类:

    • 它们完全独立于实现;我可能想要一些东西的两种实现,一种用于现实生活,一种用于单元测试,它们具有相同的接口,但它们的实现没有任何共同之处。

    • 一个类可以实现多个接口(但只能有一个子类)。这就是为什么 IEnumerable 和 IDisposable 作为接口比作为子类更好的原因:因为某些东西可能是 IEnumerable、 IDisposable、其他东西的子类。

    这些论点都不一定适用于您的情况。


    不变性呢?

    如果事物是​​结构,则不变性尤其重要(在这里它们是类而不是结构时并不那么重要)。

    【讨论】:

      【解决方案3】:

      我同意您应该创建一个具有ThicknessLengthWidth 属性的Dimensions 类以及GetVolume() 方法。您可能不想用这些成员创建基类并让MemberOtherMember 从它继承,因为MemberOtherMember 有维度,它们不是维度的超类。我将尝试列出一些指导方针,以便您在下面为您的案例做出最佳决定。

      对于界面,这可能是一个好主意,因为可以通过多种方式计算体积。

      public interface ISolid{
        double Volume { get; }
      }
      
      public class Cylinder: ISolid{
        public double Height { get; set; }
        public double Radius { get; set; }
        public double Volume {
          get
          {
            return (2 * Math.Pi * Radius ^ 2) * Height;
          }
        }
      }
      
      public class Cube: ISolid{
        public double Width { get; set; }
        public double Height { get; set; }
        public double Depth { get; set; }
        public double Volume {
          get
          {
            return Width * Height * Depth;
          }
        }
      }
      

      不清楚Member 和OtherMember 是什么,它们有标签和其他东西,通过直接从上面定义的这些类型继承可能无法很好地表示。此外,多重继承会使事情变得复杂。所以你需要看看你自己的领域对象并问自己,这个类是超类(继承)还是只需要使用另一个类来定义自己的一些特征(组合)

      public class SodaCan: Cylinder
      {
        public SodaCan(string brand)
        {
          Brand = brand;
          IsFull = true;
          Height = 5D;
          Radius = 1.5D;
        }
      
        public string Brand { get; private set; }
        public bool IsFull { get; set; }
      }
      

      public class SodaCan: BeverageHolder
      {
        public SodaCan(string brand)
        {
          Brand = brand;
          IsFull = true;
          Dimensions = new Cylinder { Height = 5D, Radius = 1.5D };
        }
      
        private ISolid Dimensions { get; set; }
      
        public double Volume {
          get {
            return Dimensions.Volume;
          }
        }
      }
      

      如果可以使用 Lenth * Height * Thickness 找到所有对象体积,那么您可能不需要该接口。当您可能有一堆具有相同行为的对象但行为不同时,接口是好的。例如圆柱体的体积与立方体的体积。无论如何创建一个接口并没有什么坏处,但在这种情况下你可能不需要它。至于您应该创建一个基类,还是一个封装常见行为和使用组合的类,这取决于您的域。苏打水可以是圆柱体,也可以是饮料架。一盒酒也可以是一个饮料架,而且它们都不是 Cylinders,所以你必须看看你的域中可能还想继承什么。

      【讨论】:

        【解决方案4】:

        我将通过使用扩展方法 GetVolume() 创建一个名为 IThreeDimensional 的接口来解决这个问题。我会这样做:

        interface IThreeDimensional
        {
            double Thickness {get; set;}
            double Width {get; set;}
            double Length {get; set;}
        }
        
        static double GetVolume(this IThreeDimensional value)
        {
            return value.Thickness * value.Width * value.Length;
        }
        

        然后你可以让你的所有类都实现 IThreeDimensional,然后它们将免费继承 GetVolume()。

        【讨论】:

        • 如果它描述了一个盒子形状,我可能会在这个界面中将厚度重命名为深度。
        • @Fredrik 我愿意,但为了清楚起见,我坚持他的措辞。
        【解决方案5】:

        对我来说,有厚度、宽度、长度和体积的东西听起来就像一个实体。所以我们就这么称呼吧。

        成员是实体吗?对我来说看起来像,但必须更明确地指定类才能确定。 OtherMember 也是如此。因此,您可以简单地让 Member 和 OtherMember 将 Solid 扩展为子类。

        如果你不想走那条路——也许对 Member 和 OtherMember 有更重要的东西,它们应该扩展其他一些类,或者与 Solid 没有真正的“is-a”关系——你可以让他们实现 ISolid 接口,并将所有 ISolid 方法委托给 Solid 实例成员。一般来说,我们更喜欢组合(这里描述的第二种方法)而不是继承。

        【讨论】:

        • 我在同一个方向摇摆。但是,我对这样做持谨慎态度,因为它的接口与抽象类具有相同的签名(我在某处读到这不是一件好事)。
        • 通过组合方法,接口不必与任何抽象类具有相同的签名 - 可以有一个具体的实现,组合类可以简单地委托给它。跨度>
        【解决方案6】:

        我相信你正在寻找这样的东西

        interface I3d
        {
            double Thickness {get; set;}
            double Width {get; set;}
            double Length {get; set;}
            double GetVolume
        }
        
        
        public class ThreeDimensionalShape : I3d
        {
          public double Thickness {get; set;}
          public double Width {get; set;}
          public double Length {get; set;}
          public double GetVolume()
          {
              return this.Thickness * this.Width * this.Length;
          }
        }
        
        class Member : ThreeDimensionalShape
        {
            public string Label {get;set;}
            // Lots of other fields...
        
            // Other methods
        }
        
        class OtherMember : ThreeDimensionalShape
        {
            public string CompositeLabel {get;set;}
            // Lots of other fields (not related to other class: Member)
        
            // Other methods
        }
        

        【讨论】:

          【解决方案7】:

          我会为属性和方法做一个接口:

          interface IThreeD
          {
              double Thickness {get; set;}
              double Width {get; set;}
              double Length {get; set;}
          
              double GetVolume();
          }
          

          然后是提供通用实现的基类。比如:

          class BaseMember
          {
              public string Label { get; set; }
              public double Thickness { get; set; }
              public double Width { get; set; }
              public double Length { get; set; }
              public double GetVolume()
              {
                  return Thickness * Width * Length;
              }
          }
          
          class Member : BaseMember
          { 
              // Things specific to Member
          }
          
          class OtherMember : BaseMember
          {
              // Things specific to OtherMember
          }
          

          【讨论】:

            【解决方案8】:

            我讨厌严格依赖基于接口的实现。这种厌恶源于接口是不可变的一个简单事实。一旦你部署它们,你就会被它们困住。

            话虽如此,我也讨厌严格依赖基于继承的实现。在 .NET 领域,我们只能选择 一个 基类。确保它是一个好的! (并确保它是正确的。)

            我认为正确的做法是为您的维度定义一个界面。然后,像其他人建议的那样,定义一个实现接口的类,提供 GetVolume 方法的最常见实现。确保该方法是可重写的,并且消费者可以从该类派生。

            我会全心全意地接受这样一个概念,即对象的宽度、高度和深度都是属性,并且应该通过属性将它们插入其中。因此:

            var dimensions = new Dimensions(x, y, z);
            var some3dObject = new ThreeDObject();
            some3dObject.Dimensions = dimensions;
            

            对我来说很有意义。

            接口与继承是一个权衡问题。无论哪种方式,你都会放弃一些东西:要么扩展接口的能力,要么改变你的基类的能力。你觉得哪一个更舒服?

            另一方面,还有扩展方法,可以解决很多问题。请务必小心确保您的代码不会随着时间的推移变得难以管理。

            祝你好运!

            【讨论】:

              猜你喜欢
              • 2014-03-30
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-06-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多