C#:您如何建议重构这些类? (接口,聚合或其他任何东西)

时间:2009-06-30 18:05:24

标签: c# refactoring interface model

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

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方法重构为另一个类(Dimensions?)以及DRY ......

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

这是一个好方法吗?

我已经简明扼要地介绍了如何从接口继承接口而不是具体类?

另外,不变性呢?

我觉得我经常迷失于让我的域名课程正确。有人对此有一些建议吗?

8 个答案:

答案 0 :(得分:5)

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

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

答案 1 :(得分:5)

遏制还是继承?

如果这两个都是维度,那么这些属性可以放入一个名为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,是其他东西的子类。

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


不变性怎么办?

如果事物是​​结构的话,不变性尤为重要(在这里,它们不是结构而不是结构)。

答案 2 :(得分:3)

我同意您应该创建一个Dimensions类,其中包含ThicknessLengthWidth属性以及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;
    }
  }
}

目前尚不清楚会员和其他会员是什么,他们有标签和其他可能无法通过直接从上面定义的类型继承而表现出来的东西。而且,多重继承会使事情变得复杂。所以你需要查看自己的域对象并问自己,这个类是超类(继承)还是只需要使用其他类来定义自身的一些特性(组合)

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可以找到所有对象体积,那么您可能不需要该界面。当您可能有一堆具有相同行为的对象时,接口很好,但行为执行不同。例如,圆柱的体积与立方体的体积。无论如何创建一个接口并没有什么坏处,但在这种情况下你可能不需要它。您应该创建一个基类,还是一个用于封装常见行为和使用组合的类,这取决于您的域。汽水可以是汽缸,也可以是BeverageHolder。一盒葡萄酒也可以是一个饮料架,而这些不是两个圆筒,所以你必须看看你想要在你的领域继承的其他东西。

答案 3 :(得分:1)

我会通过使用扩展方法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()。

答案 4 :(得分:1)

有厚度,宽度,长度和音量的东西听起来就像一块实心。所以我们称之为。

会员是否坚实?看起来对我来说,但是必须更明确地指定类才能确定。其他会员也一样。因此,您可以简单地将Member和OtherMember扩展为Solid作为子类。

如果你不想走那条路 - 也许会员和其他会员有更重要的事情,他们应该延伸的其他课程,或者也许与Solid没有真正的“is-a”关系 - 你可以让它们实现ISolid接口,并将所有ISolid方法委托给Solid实例成员。通常,我们更喜欢组合(这里描述的第二种方法)继承。

答案 5 :(得分:1)

我相信这就是你想要的东西

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
}

答案 6 :(得分:0)

我会为属性和方法做一个界面:

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
}

答案 7 :(得分:0)

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

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

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

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

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

对我来说非常有意义。

接口与继承是一个权衡问题。无论如何,你要放弃一些东西:扩展界面的能力,或者改变你的基类的能力。你觉得哪一个更舒服?

另一方面,有扩展方法,因此可以解决很多问题。请特别注意确保您的代码不会随着时间的推移而变得无法管理。

祝你好运!