从基本构造函数调用重写的函数

Calling an overridden function from a base constructor

我不应该从基本构造函数调用重写函数是有道理的,因为派生的 class 尚未构造。

但我想使用这种设计模式,其中每个派生 class 都提供了计算基础属性的方法 class 因为属性应该是不可变的并在构造函数中分配。

Shape.cs

public abstract class Shape
{
    protected Shape()
    {
        Area = 0f;
        Center = Vector2.Zero;
        const int n = 36;
        float du = 1/(float)n, dv = 1/(float)n;
        for (int i = 0; i < n; i++)
        {
            float u = (i+0.5f)*du;
            for (int j = 0; j < n; j++)
            {
                float v = (i+0.5f)*dv;
                float f = GetAreaElement(u, v);
                // Warning: Remove this call from a constructor to the overridable 'GetAreaElement' method.
                Area += f*du*dv;
                Center += GetPosition(u, v)*f*du*dv;
                // Warning: Remove this call from a constructor to the overridable 'GetPosition' method.
            }
        }
        Center /= Area;
    }

    public abstract Vector2 GetPosition(float u, float v);
    protected abstract float GetAreaElement(float u, float v);

    public float Area { get;  }
    public Vector2 Center { get; }
}

public class Circle : Shape
{
    public Circle(float radius)
    {
        Radius=radius;
    }

    public float Radius { get; }

    public override Vector2 GetPosition(float u, float v)
    {
        float r = u*Radius, θ = (float)(2*Math.PI)*v;
        return new Vector2(
            r*(float)Math.Cos(θ),
            r*(float)Math.Sin(θ));
    }

    protected override float GetAreaElement(float u, float v)
    {
        return u*Radius;
    }
}

public class Rectangle : Shape
{
    public Rectangle(float side1, float side2)
    {
        Side1=side1;
        Side2=side2;
    }

    public float Side1 { get; }
    public float Side2 { get; }

    public override Vector2 GetPosition(float u, float v)
    {
        return new Vector2((u-0.5f)*Side1, (v-0.5f)*Side2);
    }

    protected override float GetAreaElement(float u, float v)
    {
        return Side1*Side2;
    }
}

那么这里的解决方案是什么?我想使用基础构造函数来定义属性,计算依赖于派生的实现class.

解决方法 1 - 未来计算器

一种解决方法是提供一个 protected 函数来计算属性,每个属性都会从派生 class 的每个构造函数中调用,但是 没有强制执行 这里。如果 class 忘记调用计算器函数,整个事情就会崩溃。属性现在是 private set,这确实 不是一成不变的

public abstract class Shape
{
    protected void Calculate()
    {
        ...
        float f = GetAreaElement(u, v);
        ...
        Center += GetPosition(u, v)*f*du*dv;
        ...
    }

    public abstract Vector2 GetPosition(float u, float v);
    protected abstract float GetAreaElement(float u, float v);

    public float Area { get; private set; }
    public Vector2 Center { get; private set; }
}

public class Circle : Shape
{
    public Circle(float radius)
    {
        Radius=radius;

        base.Calculate();
    }

    public float Radius { get; }

    public override Vector2 GetPosition(float u, float v)
    {
        ...
    }

    protected override float GetAreaElement(float u, float v)
    {
        ...
    }
}

解决方法 2 - 函数委托

另一种解决方法是将委托提供给所需的函数实现,作为基本 class 构造函数的参数。

public delegate float AreaFactor(float u, float v);
public delegate Vector2 PositionFunc(float u, float v);
public abstract class Shape
{
    protected Shape(AreaFactor a, PositionFunc p)
    {
        this.GetAreaElement = a;
        this.GetPosition = p;
        ...
        float f = a(u, v);
        this.Center += p(u, v)*f*du*dv;
        ...
    }

    public float Area { get; }
    public Vector2 Center { get;  }

    public AreaFactor GetAreaElement { get; }
    public PositionFunc GetPosition { get; }
}

public class Circle : Shape
{
    public Circle(float radius)
        : base(
              (u, v) => u*radius, 
              (u,v)=>
                {
                    float r = u*radius, θ = (float)(2*Math.PI)*v;
                    return new Vector2(
                        r*(float)Math.Cos(θ),
                        r*(float)Math.Sin(θ));
                })
    {
        Radius=radius;
    }

    public float Radius { get; }
}

这对我来说似乎有点笨拙,而且我不确定我是否喜欢函数委托属性,而不是重写方法。

Question/Challege

能否[SO]提供一些其他方法来实现上述目标

一种选择是,不在构造函数中计算 AreaCenter,而是在 属性 getter 中延迟计算它们。它将需要一个支持字段来了解 属性 是否已计算,但它会消除警告。

你没有中心传递给Shape构造函数,这对我来说也很奇怪,但我不完全理解你的设计使用.

我的偏好是选择选项 2 - 将生成派生属性的方法传递给基本构造函数。它确实满足您的所有要求。如果美学是一个问题,也许使用 Func 可能会使代码更易于阅读。关键是 Func 是静态方法,这应该不是问题,因为它们计算对象的不可变属性。

void Main()
{
    var square = new Square(5);
}

public abstract class Shape
{
    protected Shape(Func<int> areaFunc)
    {
        Area = areaFunc();
    }

    public int Area { get; }
}

public class Square : Shape
{
    public Square(int side): base( () =>  CalcArea(side) )
    {
        Side = side;
    }
    
    static int CalcArea(int side) => side * side;

    public int Side { get; }
}