Вопрос

Suppose we have a class Player with classes PhysicsComponent, InputComponent and StandingState, DuckingState.

The class Player itself does not have an input function, the InputComponent does. But when we change states to DuckingState, for example, the behavior of the input function changes. So how is it set up?

a) The Player class has a current_state variable, which in turn has a variable for each component. This would create a lot of additional classes: StandingStatePhysicsComponent, DuckingStatePhysicsComponent, etc., since you'd want to "override" the input function, for example, you'd have to do that in the corresponding component class, which has that method.

b) The Player class has a current_state variable and a variable for each component. This would be possible by passing a lambda to each component when the state changes, that is executed on that component's update method.

c) The Player class has a variable for each component, which each in turn have a current_state variable (This makes least sense to me, since each component would have it's own state).

Am I on the right track?

Это было полезно?

Решение

Be wary of YAGNI and consider this answer to a similar question which asserts that Patterns are not building blocks.


The GoF State Pattern solves a problem of selection of a single behavioural object to represent a class' functionality.

Meanwhile, the GoF Composite Pattern solves a problem of combining multiple behavioural objects to represent a class' functionality.

On that basis, the two patterns are designed for two almost-opposing objectives. One pattern seeks to single out just one behavioural object, while the other pattern seeks to combine multiple behavioural objects.

You may of course find a purpose for each of them somewhere in your program to solve different problems, but it's very difficult to see how you could gain any benefit from trying to use them together to solve the same problem.

Based on your description, it sounds like you could need either the Composite pattern or the State pattern, but probably not both (yet), and probably not both together (at least not directly).

Consider looking at how much variation in behaviour/logic you need from the state pattern, versus how much of your behaviour can simply be structured as data.

State Pattern

  • If you have a lot of state-dependent logic in your Component classes (i.e. behaviour which follows different rules/algorithms/etc), then consider abandoning the composite pattern.
  • State-dependent logic in a single Component suggests those components have a very weak identity and shouldn't be treated as components because the state doesn't belong to the component, yet it deactivates large portions of its behaviour.
  • Those components may also contain some related fields/data which might be common to all states; those could be separated out into simple data objects away from the state-specific behaviour (Remember that the state pattern is about behaviour and not about data).
  • You might also have some common 'helper' methods in each component whose behaviour isn't state-specific, which could be moved into a common class.
  • Consider how your code would look if you redistributed all of that Component logic into State classes instead, possibly passing data objects into them.
  • If any particular State class starts to look too big or takes on too many responsibilities, you could apply a composite pattern just to that particular state.

Component Pattern

  • If most of the differences between your states are based on data (i.e. information which you might reasonably include in a lookup table, a JSON/XML structure, or even a database), then consider scrapping the State classes
  • When 'state' is just a description of a dataset, you only need a simple representation such as an enum variable which can act as the key to that dataset.
  • Data-driven variations in behaviour could be defined in a simple map or dictionary-like structure keyed by a state variable.
  • Each variation could be a reference data object which defines rules/constraints and parameters that your components need to obey. (for example - a parameter object passed into each component and used to inform the logic of those those components).

Final (somewhat unrelated) note: Be wary of class names which sound like entities. Useful class names are often those which somebody reading your code can immediately relate either to some behavioural aspect of a program, or to a functional requirement.

Names like Player tell you nothing about what a Player object should be responsible for; the real danger is that inevitably leads developers down an inside-the-box type mentality toward design; in turn that often results in God Objects which become responsible for doing everything that might conceivably relate to a player.

Лицензировано под: CC-BY-SA с атрибуция
scroll top