C
C#4mo ago
eysidi

Dependency Inversion

Hello all, I had asked this before and was answered I need to implement dependency inversion I have Shape abstract class and and interface which is responsible for drawing the shape.
c#
public interface IShapeDrawer
{
void Draw(Vector2 position);
}
public abstract class Shape : IShapeDrawer
{
public abstract string Name; // Name to display

public abstract void Draw(Vector2 position); // Interface to abstract
}
c#
public interface IShapeDrawer
{
void Draw(Vector2 position);
}
public abstract class Shape : IShapeDrawer
{
public abstract string Name; // Name to display

public abstract void Draw(Vector2 position); // Interface to abstract
}
The problem is: each shape uses position to calculate coordinates, for circle it would be single Vector2 for square, rectangle etc it would be 2 and for triangle it would be 3. So I need an interface, right
c#
public interface ICoordinateCalculator
{
Vector2 CalculateCoordinate(Vector2 position);
}

public interface IMultipleCoodinateCalculator
{
Vector2[] CalculateCoordinates(Vector2 position);
}

public CircleCoordinateCalculator : ICoordinateCalculator
{
Vector2 CalculateCoordinate(Vector2 position) => position;
}
c#
public interface ICoordinateCalculator
{
Vector2 CalculateCoordinate(Vector2 position);
}

public interface IMultipleCoodinateCalculator
{
Vector2[] CalculateCoordinates(Vector2 position);
}

public CircleCoordinateCalculator : ICoordinateCalculator
{
Vector2 CalculateCoordinate(Vector2 position) => position;
}
Now my problem is, how do I implement this in my Shapeclass
21 Replies
Tvde1
Tvde14mo ago
What attributes does a shape have? I can imagine you would
var Rectangle(width: 10, height: 5);
var Circle(radius: 3);
var Rectangle(width: 10, height: 5);
var Circle(radius: 3);
then you can draw them using an X and Y the Draw method for each shape, uses its attributes
eysidi
eysidi4mo ago
I'm using IMGui so, for circle I need single Vector2 for square I need pMin pMax 2 Vector2s for triangle I need 3 Vector2s
Tvde1
Tvde14mo ago
I wouldn't make a "coordinate calculator". It's hard to model that into the real world too
eysidi
eysidi4mo ago
I definetely can do the calculation under Draw implementation of each shape however I want to apply dependency inversion and be able to test my code because Draw returns nothing and I can't create a unit test for it
Tvde1
Tvde14mo ago
what do you want to unit test exactly?
eysidi
eysidi4mo ago
calculation of the coordinate for each shape
Tvde1
Tvde14mo ago
sometimes it helps to first write an unit test (which will not compile because the methods or classes don't exist yet), so you get a feel for what you want your structure to look like that would be top-down programming
eysidi
eysidi4mo ago
I understand, have already thought of the logic and had asked this question. Was responded I need dependency inversion which made/makes sense. I'm trying to practice SOLID so want to implement things correctly.
Tvde1
Tvde14mo ago
SOLID is some nice concepts, but for all of these principles it holds that they are more guidelines than rules. So apply them where you see use for this
eysidi
eysidi4mo ago
sure I can just implement my calculation under Draw implementation but as I said I won't be able to test
Tvde1
Tvde14mo ago
If I were to overengineer this, I would have a method on a shape CreateDrawRequests(Vector2 position) which returns an object like
{
x: 123,
y: 374,
width: 10,
height: 5
}
{
x: 123,
y: 374,
width: 10,
height: 5
}
eysidi
eysidi4mo ago
so the solution is abstracting the Calculator
Tvde1
Tvde14mo ago
and the thing that draws, can take in these "draw requests" and execute them I'd expect something like
var shape = new Circle(radius: 5);
var drawRequest = shape.Draw(x: 10, y: 10);

drawRequest.Should()....
var shape = new Circle(radius: 5);
var drawRequest = shape.Draw(x: 10, y: 10);

drawRequest.Should()....
eysidi
eysidi4mo ago
this is not the case though,
c#
ImGui.AddCircle(Vector2 pos);
ImGui.AddTriangle(Vector2 p1, Vector2 p2, Vector3);
ImGui.AddRect(Vector2 pMin, Vector2 pMax);
c#
ImGui.AddCircle(Vector2 pos);
ImGui.AddTriangle(Vector2 p1, Vector2 p2, Vector3);
ImGui.AddRect(Vector2 pMin, Vector2 pMax);
you see each shape has different position requirements,
Tvde1
Tvde14mo ago
because what is the exact calculation you'll be doing? going e.g. from a midpoint x and y to a top-left x and y
eysidi
eysidi4mo ago
so I have
c#
void Draw(Vector2 position);
c#
void Draw(Vector2 position);
I pass 300x500 for example, find the center, calculate Vectors for the shape
Tvde1
Tvde14mo ago
so for a rectangle, it would be e.g.
var (pMin, pMax) = CreateRectangle(x: 10, y: 20, width: 10, height: 5);

pMin.Should().Be((5, 17.5));
pMax.Should().Be((10, 22.5));
var (pMin, pMax) = CreateRectangle(x: 10, y: 20, width: 10, height: 5);

pMin.Should().Be((5, 17.5));
pMax.Should().Be((10, 22.5));
` (not sure if those values are correct)
eysidi
eysidi4mo ago
let's consider this is an interview question You get Vector2 position as argument, create classes/interfaces for Shape where each shape takes different number of position arguments Circle -> 1 Square -> 2 Triangle -> 3 MyWeirdShape -> N What classes/interfaces would you create that applies SOLID I already can just write the code and keep on going, as I said my goal is to apply SOLID principles 🙂
Tvde1
Tvde14mo ago
hmm it's tricky doing something for the sake of doing it. Decoupling the calculation of a shape, and the call of the drawing would be something that seems SOLID, but is probably unproductive in the end so what classes/interfaces are you already thinking of?
eysidi
eysidi4mo ago
I will try again and see what I come up with I think I will just create static methods for each shape
c#
public class CoordinateCalculator
{
static Vector2 CalculateCircleCoords(Vector2 position) => position;

static (Vector2, Vector2) CalculateRectCoords(Vector2 position)
{
// Calc here
return (Vector2 pMin, Vector2 pMax);
}

...
}
c#
public class CoordinateCalculator
{
static Vector2 CalculateCircleCoords(Vector2 position) => position;

static (Vector2, Vector2) CalculateRectCoords(Vector2 position)
{
// Calc here
return (Vector2 pMin, Vector2 pMax);
}

...
}
Not sure how else to deal with varying number of Vector2 s returned Would generics help?
Tvde1
Tvde14mo ago
you could over engineer and have a
interface ICoordinateCalculator<TShape, TShapeAttributes> Where TShape : IShape<TShapeAttributes>
{
TShapeAttributes CalculateCoordinates(TShape shape, Vector2 location);
}

class ICircleCoordinateCalculator : ICoordinateCalculator<Circle, CircleAttributes>
{
public CircleAttributesCalculateCoordinates(Circle shape, Vector2 location)
{
....
return CircleCoordinateAttributes(midpoint: xyz, diameter: xyz);
}
}

public record CircleAttributes(Vector2 Midpoint, float Diameter);

class Circle : IShape<CircleAttributes>
{
public Circle(float Diameter) { ... }
}
interface ICoordinateCalculator<TShape, TShapeAttributes> Where TShape : IShape<TShapeAttributes>
{
TShapeAttributes CalculateCoordinates(TShape shape, Vector2 location);
}

class ICircleCoordinateCalculator : ICoordinateCalculator<Circle, CircleAttributes>
{
public CircleAttributesCalculateCoordinates(Circle shape, Vector2 location)
{
....
return CircleCoordinateAttributes(midpoint: xyz, diameter: xyz);
}
}

public record CircleAttributes(Vector2 Midpoint, float Diameter);

class Circle : IShape<CircleAttributes>
{
public Circle(float Diameter) { ... }
}
something like this but this does not seem very maintainable or productive 😁 but it's fun this links the shape to the class that holds the attributes so you cannot make a
class SquareCoordinateCalculator : ICoordinateCalculator<Square, CircleAttributes>
{
class SquareCoordinateCalculator : ICoordinateCalculator<Square, CircleAttributes>
{
it wouldn't compile