I think that object oriented programming has one major flaw. Consider the type below.
struct Circle {
Vector2 center;
float radius;
};
Now imagine we want to add functionality so the circle can
- Calculate area/circumference of the shape
- Draw it to a canvas
- Add the shape to a spacial index
- Serialize the shape to a file
A naive way to implement this with OOP is
class Circle : SpacialIndexLeaf {
public Vector2 center;
public float radius;
public float CalculateArea();
public void DrawToCanvas(Canvas canvas);
// spacial index interface
public BoundingBox GetBoundingBox();
public void WriteToFile(File file);
}
This creates a hard dependency from Circle on Canvas, File, and a soft dependency in the spacial index by implementing an interface.
This makes it much harder organize dependencies in a meaningful manner. The shape code, which should be just a simple representation of geometry. Now depends on spacial index code, rendering code, and io code. How did we get into the mess? The circular dependency between the data of a class and the functions of a class inherent in object oriented programming.
Lets try to fix this problem using OOP approved methods. First lets try wrapper classes
class Circle {
public Vector2 center;
public float radius;
// no dependencies, this one stays
public float CalculateArea();
};
class CircleRenderer : Renderable {
public Circle circle;
public CircleRenderer(Circle circle);
public DrawToCanvas(Canvas canvas);
};
public CircleSerializer : Serializable {
public Circle circle;
public CircleSerializer(Circle circle);
public void WriteToFile(File file);
};
This definitely fixes the dependency problem. It has the unfortunate side effect of greatly multiplying the number of classes in the code. Adding new shapes would require having many parallel classes scattered throughout the code base. How about combining all the shape rendering code into a single class.
class Circle {
public Vector2 origin;
public float radius;
public float GetArea();
};
class ShapeRenderer {
public void RenderCircle(Canvas canvas, Circle circle);
public void RenderRectangle(Canvas canvas, Rectangle rectangle);
...
};
class ShapeSerializer {
public void SerializeCircle(File file, Circle circle);
public void SerializeRectable(File file, Rectangle rectangle);
...
};
Less classes to manage, that is a plus. This has the downside of having a ShapeRenderer and ShapeSerializer that could grow to depend on a lot of different classes. If you only need to render a circle you still need to use a class that references all the other classes you don't care about. You also need to construct a class just to call a function. Moving where code is implemented changes what class may need to be constructed.
Lets solve this problem without OOP.
struct Circle {
Vector2 origin;
float radius;
};
float CalculateArea(Circle circle);
void Render(Canvas canvas, Circle circle);
void Serialize(File file, Circle circle);
No more circular dependencies between functions and data, or classes. Data depends on other data. You could define all the data structures in a completely different module from all the functions. Functions depend on data and other functions. The implementation of functions can be moved freely around in files without having to worry about which class the function should belong to. Moving where functions only changes where the functions are imported from.
Classes to solve polymorphism nicely, but I think there are other solutions that don't have the inherent circular depedency problem that OOP has. I personally think that golang has a good approach here. Any thoughts on this?