Game engine design choice [closed]

2020-07-31 01:00发布

问题:

I heard composition is preferable over inheritance. So when I design my simple game engine I tend to do the following:

http://yuml.me/1252399d

instead of:

http://yuml.me/51cacb4f

Is this a good approach, or should I reconsider?

Edit: To clarify the notation. The arrows means inheritance, the arrows with a diamond means composition and the <<>> means interface. More info on the notation is available at the yUML webpage.

回答1:

OK, there are a few things to cover here.

First: why do you need to express commonality between PointLight and Spotlight? Is it

  1. Because there are clients who simply want to use them as a Light, and don't care which variety they get?
  2. Because they share some implementation and you don't want to duplicate?

Because Clients need to treat Lights uniformly

This is Interface inheritance, also known as subtyping. Composition doesn't apply here - because there's nothing to compose. You can realise this in two ways: which you choose depends largely on your language.

  • If you're using Java/C# this would usually be achieved by defining an Interface (ILight) that exposes all the common characteristics of Lights. Clients would depend only on the interface, not the implementations of it. PointLight and SpotLight would each implement the interface.
  • If you're not using a language with first class Interfaces, declare Light as an abstract base class with PointLight and SpotLight inheriting from it.

Because you want to share implementation

There are generally two approaches to achieving this:

  • Implementation inheritance. Define class Light and implement common behaviour in it. Declare PointLight and SpotLight as inheriting from Light, overriding/adding behaviour as required.
  • Composition. Define classes PointLight and SpotLight without inheriting from a common superclass. Implement a third class (which you'd still probably call Light) that implements the common behaviour. Both PointLight and SpotLight would include an instance of 'Light` and delegate to it for the shared behaviour.

For your specific example there's relatively little to choose between Implementation inheritance and Composition. Issues with the former arise when the inheritance hierarchy gets deeper. It's also notoriously error prone in languages that support multiple implementation inheritance (e.g. the fragile base class problem). On the other hand, Implementation inheritance means less typing: the language autoamtically delegates to the shared behaviour, whereas with Composition you need to write the delegation methods.

Summary

Note also the above are not mutually exclusive: you could, for example:

  • Declare an Interface ILight that both SpotLight and PointLight implement
  • Share common behaviour using either implementation inheritance or composition

Fundamentally you need to be clear what you're trying to achieve.

hth.



回答2:

I think you should not take decision on base of some sort of rumors. Sometimes composition is really good, but it require more code writing and more difficult in support (if you change BasicLight interface, you should manually change all dependent classes). Inheritance solve this problems, and some others. There are some cases when composition is preferable, but they appear mostly in complex projects.