samedi 5 février 2022

Calling an overridden function from a base constructor

It makes sense that I should not call an overridden function from a base constructor, since the derived class is not yet constructed.

But I want to use this design pattern, where each derived class provides methods for calculating the properties of the base class since the properties should be immutable and assigned at the constructor.

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

So what is the solution here? I want to use the base constructor to define the properties, and the calculation depends on the implementation of the derived class.

Workaround 1 - Future calculator

A workaround would be to provide a protected function that calculates the properties, each to be called from each constructor of the derived class, but there is no enforcement here. If one class forgets to call the calculator function the whole thing falls apart. And the properties are now private set which is not immutable really.

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)
    {
        ...
    }
}

Workaround 2 - Function delegates

Another workaround would be to supply the delegates to the required function implementations as arguments to the base class constructor.

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

This seems a bit clunky to me, and I am not sure I like the function delegate properties, instead of overridden methods.

Question/Challege

Can [SO] provide some other ways of achieving the above-stated goals

  • Base properties are immutable
  • Base properties are calculated at the constructor based on the implementation details of the derived classes.
  • Each derived class holds its own immutable properties used to describe the derived class.

Aucun commentaire:

Enregistrer un commentaire