Избегайте проблемы с ромбами в абстрактных + конкретных иерархиях классов - PullRequest
1 голос
/ 21 июня 2020

Я работаю над игрой, и мы хотим обернуть физический движок, чтобы его можно было легко заменить.

Итак, мы хотим создать иерархию абстрактных классов в качестве шаблона, указав все, что движок должен поддерживать и работает как интерфейс для остальной части игры. Затем мы хотим создать конкретные классы, реализовать эти абстрактные классы и обернуть фактический движок. Проблема в том, что это создает множество ромбов, например:

example of a diamond problem

Where Shape and Circle contain engine-independent functionality but also some engine-dependent abstract methods and P2Shape and P2Circle are wrappers for the P2 engine and implement those engine-specific methods. And from a broader perspective, we don't just have these two classes but a whole hierarchy tree of (not purely) abstract template classes and a whole hierarchy tree of concrete implementations. Each of the concrete classes "is-a" -> its abstract template and "is-a" -> its base class in the hierarchy:

diamonds in abstract + concrete hierarchy trees

We are probably not the first people to encounter this problem and this is probably some symptom of bad design, so I am curious about the proper way to implement this. To make it a bit more interesting, we are working in TypeScript which does not support multiple inheritance.

Here are the approaches I considered:

  1. Favor composition over inheritance

Let's say a Shape has a position and a Circle has a radius. In my opinion, a Circle clearly "is a" Shape that has a position and a radius, it doesn't "have a" radius and a shape that has a position. Also, if I access the position I don't want to say myCircle.shape.position. Shapes are a textbook example of inheritance hierarchies. The other option would be that a Shape has a P2Shape but then all engine-specific stuff has to be squeezed in there and the type of the field has to be any or generic. Or a P2Shape has a Shape but then the rest of the game cannot work with it because it is unaware of the P2Shape type.

  1. Используйте интерфейсы вместо абстрактных классов

TypeScript поддерживает отдельная концепция интерфейсов, где множественное наследование и проблема ромба не являются проблемой. Но там мы не можем поместить какую-либо реализацию в независимые от двигателя части.

Примеси

Мне кажется, что концепция миксинов заключается в создании набора модульных и многократно используемых компонентов, которые можно объединить в классы, использующие эту функциональность. В нашем случае это будет означать, что P2Shape и Circle являются модульными компонентами и P2Circle использует их. Мне это почему-то не кажется семантически правильным.

Все три решения кажутся несколько неудовлетворительными. Любая помощь приветствуется!

...