8

A quick search of this stackexchange shows that in general composition is generally considered more flexible than inheritance but as always it depends on the project etc and there are times when inheritance is the better choice. I want to make a 3D chess game where each piece has a mesh, possibly different animations and so on. In this concrete example it seems like you could argue a case for both approaches am I wrong?

Inheritance would look something like this (with proper constructor etc)

class BasePiece
{
    virtual Squares GetValidMoveSquares() = 0;
    Mesh* mesh;
    // Other fields
}

class Pawn : public BasePiece
{
   Squares GetValidMoveSquares() override;
}

which certainly obeys the "is-a" principle whereas composition would look something like this

class MovementComponent
{
    virtual Squares GetValidMoveSquares() = 0;
}

class PawnMovementComponent
{
     Squares GetValidMoveSquares() override;
}

enum class Type
{
     PAWN,
     BISHOP, //etc
}


class Piece
{
    MovementComponent* movementComponent;
    MeshComponent* mesh;
    Type type;
    // Other fields
 }

Is it more a matter of personal preference or is one approach clearly a smarter choice than the other here?

EDIT: I think I learned something from every answer so I feel bad for only picking one. My final solution will take inspiration from several of the posts here (still working on it). Thanks to everyone who took the time to answer.

New contributor
CanISleepYet is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
  • I believe both can exist side by side, these two are for different purpose, but these are not exclusive to each other.. The chess is composed of different pieces and boards, and pieces are of different types. These pieces share some basic property and behavior, and also have specific property and behavior. So according to me, composition should be applied over board and pieces, while inheritance should be followed over types of pieces. – Hitesh Gaur May 15 at 20:38
  • While you might get some people claiming that all inheritance is bad, I think the general idea is that interface inheritance is good/fine and that implementation inheritance is useful but can be problematic. Anything beyond a single level of inheritance is questionable, in my experience. It can be OK beyond that but it's not simple to pull off without making a convoluted mess. – JimmyJames May 16 at 16:09
  • The strategy pattern is common in games programming for a reason. var Pawn = new Piece(howIMove, howITake, whatILookLike) seems much simpler, more manageable and more maintainable to me than an inheritance hierarchy. – Ant P May 17 at 14:17
  • @AntP That is a good point, thanks! – CanISleepYet 2 days ago
  • @AntP Make it an answer! ... There's plenty of merit to that! – svidgen 2 days ago
3

At a first glance, your answer pretends the "composition" solution does not use inheritance. But I guess you simply forgot to add this:

class PawnMovementComponent : MovementComponent
{
     Squares GetValidMoveSquares() override;
}

(and more of these derivations, one for each of the six piece types). Now this looks more like the classical "Strategy" pattern, and it is also utilizing inheritance.

Unfortunately, this solution has a flaw: each Piece now holds it's type information redundantly twice:

  • inside the member variable type

  • inside movementComponent (represened by the subtype)

This redundancy is what could really bring you into trouble - a simple class like a Piece should provide a "single source of truth", not two sources.

Of course, you could try to store the type information only in type, and create no child classes of MovementComponent as well. But this design would most probably lead to a huge "switch(type)" statement in the implementation of GetValidMoveSquares. And that is definitely a strong indication for inheritance being the better choice here.

Note in the "inheritance" design, it is quite easy to provide the "type" field in a non-redundant way: add a virtual method GetType() to BasePiece and implement it accordingly in each base class.

Concerning "promotion": I am here with @svidgen, I find the arguments presented in @TheCatWhisperer's answer debatable.

Interpreting "promotion" as a physical exchange of pieces instead of interpreting it as a change of the type of the same piece feels quite more natural to me. So implementing this as in a similar manner - by exchanging one piece by another of a different type - will most probably not cause any huge problems - at least not for the specific case of chess.

  • Thanks for telling me the name of the pattern, I didn't realise it was called Strategy. You are also right I missed the inheritance of movement component in my question. Is the type really redundant though? If I want to illuminate all the queens on the board it would seem strange to check what movement component they had, plus I would need to cast the movement component to see what type it is which would be super ugly no? – CanISleepYet 2 days ago
  • @CanISleepYet: the problem with your proposed "type" field is, it could diverge from the subtype of movementComponent. See my edit how to provide a type field in a safe way in the "inheritance" design. – Doc Brown 2 days ago
4

I'd probably prefer the simpler option unless you have explicit, well-articulated reasons or strong extensibility and maintenance concerns.

The inheritance option is fewer line of code and less complexity. Each type of piece has an "immutable" 1-to-1 relationship with its movement characteristics. (Unlike it's representation, which is 1-to-1, but not necessarily immutable — you might want to offer various "skins".)

If you break this immutable 1-to-1 relationship between pieces and their behavior/movement into multiple classes, you're probably only adding complexity — and not much else. So, if I were reviewing or inheriting that code, I'd expect to see good, documented reasons for the complexity.

All that said, an even simpler option, IMO, is to create a Piece interface that your individual Pieces implement. In most languages, this is actually very different from inheritance, because an interface will not restrict you from implementing another interface. ... You just don't get any base class behavior for free in that case: You'd want to put shared behavior somewhere else.

  • I don't buy that the inheritance solution is 'less lines of code'. Perhaps in this one class but not overall. – JimmyJames May 16 at 16:19
  • @JimmyJames Really? ... Just count the lines of code in the OP ... admittedly not the whole solution, but not a bad indicator of which solution is more LOC and complexity to maintain. – svidgen May 16 at 16:23
  • I don't want to put too fine a point on this because it's all notional at this point but yeah, I really think so. My contention being that this bit of code is negligible compared to the entire application. If this simplifies the implementation of the rules (debatable), then you might save dozens or even hundreds of lines of code. – JimmyJames May 16 at 18:58
  • Thanks, I didn't think of the interface but you might well be right that this is the best way to go. Simpler is almost always better. – CanISleepYet 2 days ago
2

The thing with chess is that the game play and piece rules are prescribed and fixed.  Any design that works is fine — use whatever you like!  Experiment and try them all.

In the business world, however, nothing is so strictly prescribed like this — business rules & requirements change over time, and programs have to change over time to accommodate.  This is where is-a vs. has-a vs. data make a difference.  Here, simplicity make complex solutions easier to maintain over time and changing requirements.  In general, in business, we also have to deal with persistence, which may involve a database as well.  So, we have rules like don't use multiple classes when a single class will do the job, and, don't use inheritance when composition is sufficient.  But these rules are all geared toward making the code maintainable over the long run in the face of changing rules & requirements — which is just not the case with chess.

With chess, the most likely long term maintenance path is that your program needs to get smarter and smarter, which eventually means speed and storage optimizations will dominate.  For that, you will generally have to make trade offs that sacrifice readability for performance, and so even the best OO design will eventually go by the wayside.

  • Note that there have been many sucessful games which take a concept and then throw some new things into the mix. If you might want to do this in the future with your chess game, you should actually take possible future changes into account. – Flater May 16 at 15:00
2

I would start by making some observations about the problem domain (i.e. rules of chess):

  • The set of valid moves for a piece depends not just on the type of the piece but the state of the board (pawn can move forward if the square is empty / can move diagonally if capturing a piece).
  • Certain actions involve removing an existing piece from play (promotion / capturing a piece) or moving multiple pieces (castling).

These are awkward to model as a responsibility of the piece itself, and neither inheritance nor composition feels great here. It would be more natural to model these rules as first-class citizens:

// pseudo-code, not pure C++

interface MovementRule {
  set<Square> getValidMoves(Board board, Square from); // what moves can I make from the given square?
  void makeMove(Board board, Square from, Square to); // update board state to reflect a specific move
}

class Game {
  Board board;
  map<PieceType, MovementRule> rules;
}

MovementRule is a simple but flexible interface that allows implementations to update the board state in any way required to support complex moves such as castling and promotion. For standard chess, you would have one implementation for each type of piece, but you can also plug in different rules in a Game instance to support different variants of chess.

  • Interesting approach - Good food for thought – CanISleepYet 2 days ago
1

I would think in this instance inheritance would be the cleaner choice. Composition might be a little more elegant, but it seems to me to be a little more forced.

If you were planning on developing other games that use moving pieces that behave differently, composition might be a better choice, especially if you employed a factory pattern to generate the pieces needed for each game.

  • 2
    How would you deal with promotion using inheritance? – TheCatWhisperer May 15 at 20:02
  • 3
    @TheCatWhisperer How do you handle it when you're physically playing the game? (Hopefully you replace the piece -- you don't re-carve the pawn, do you!?) – svidgen May 15 at 21:27
0

which certainly obeys the "is-a" principle

Right, so you use inheritance. You can still compose away within your Piece class but at least you will have a backbone. Composition is more flexible but flexibility is overrated, in general, and certainly in this case because the chances of someone introducing a new kind of piece that would require you to abandon your rigid model are zero. The chess game is not going to change anytime soon. Inheritance gives you a guiding model, something to relate too, teaching code, direction. Composition not so much, the most important domain entities do not stand out, it is spineless, you have to understand the game in order to understand the code.

  • thanks for adding the point that with composition it is harder to reason about the code – CanISleepYet 2 days ago
0

Please do not use inheritance here. While the other answers certainly have many gems of wisdom, using inheritance here would certainly be a mistake. Using composition not only helps you deal with issues you do not anticipate, but I already see a problem here that inheritance can not handle gracefully.

Promotion, when a pawn gets converted to a more valuable piece, could be an issue with inheritance. Technically, you could deal with this by replacing one piece with another, but this approach has limitations. One of these limitations would be tracking per piece statistics where you might not want to reset the piece information upon promotion (or write the extra code to copy them over).

Now, as far as your composition design in your question, I think it is unnecessarily complicated. I don't suspect you need to have a separate component for each action and could stick to one class per piece type. The type enum might also be unneeded.

I would define a Piece class (as you already have), as well as a PieceType class. The PieceType class defines all the methods which a pawn, queen, ect must define, such as, CanMoveTo, GetPieceTypeName, ect. Then the Pawn and Queen, ect classes would virtually inherit from the PieceType class.

  • 3
    The problems you see with promotion and replacement are trivial, IMO. Separation of concerns pretty much mandates that such "per piece tracking" (or whatever) be handled independently anyway. ... Any potential advantages your solution offers are eclipsed by the imaginary concerns you're trying to address, IMO. – svidgen May 15 at 23:13
  • 5
    To clarify: There are undoubtedly some merits to your proposed solution. But, they're hard to find in your answer, which focuses primarily on a problems that are really really easy to solve in the "inheritance solution." ... That kind of detracts (for me anyway) from whatever merits you're trying to communicate about your opinion. – svidgen May 15 at 23:27
  • 1
    If Piece is not virtual, I agree with this solution. While the class design may seem a little more complex on the surface, I think the implementation will be simpler overall. The main things that would be associated with the Piece and not the PieceType are it's identity and potentially its move history. – JimmyJames May 16 at 16:04
  • 1
    @svidgen An implementation that uses a composed Piece and an implementation that uses a inheritance-based piece will look basically identical aside from the details described in this solution. This composition solution adds a single level of indirection. I don't see that as something worth avoiding. – JimmyJames May 16 at 16:18
  • 2
    @JimmyJames Right. But, the move history of multiple pieces needs to be tracked and correlated -- not something any single piece should be responsible for, IMO. It's a "separate concern" if you're into SOLID sorts of things. – svidgen May 16 at 16:30
0

From my point of view, with the information provided, it is not wise to answer whether inheritance or composition should be the way to go.

  • Inheritance and composition are just tools in the object-oriented designer's toolbelt.
  • You can use these tools to design many different models for the domain of the problem.
  • Since there are many such models that can represent a domain, depending on the designer's point of view of the requirements, you can combine inheritance and composition in multiple ways to come up with functionally equivalent models.

Your models are totally different, in the first one you modeled around the concept of chess pieces, in the second one around the concept of movement of the pieces. They might be functionally equivalent, and I would choose the one that represents the domain better and helps me reason about it more easily.

Also, in your second design, the fact that your Piece class has a type field, clearly reveals there is a design problem here since the piece itself can be of multiple types. Doesn't it sound like this should probably use some sort of inheritance, where the piece itself is the type?

So, you see, it is very hard to argue about your solution. What matters is not whether you used inheritance or composition, but whether your model reflects accurately the domain of the problem and if it is useful to reason about it and provide an implementation of it.

It takes some experience to use inheritance and composition properly, but they are entirely different tools and I don't believe one can "substitute" one with the other, although I can agree that a system could be designed entirely using just composition.

  • You make a good point that it is the model that is important and not the tool. In regards to the redundant type does inheritance really help? For example if I want to highlight all the pawns or check if a piece was a king then wouldn't I need to some casting? – CanISleepYet 2 days ago
-1

Well, I suggest neither.

While object-orientation is a nice tool, and often helps simplify things, it is not the only tool in the toolshed.

Consider implementing a board class instead, containing a simple bytearray.
Interpreting it, and calculate things on it is done in the member-functions using bit-masking and switching.

Use the MSB for the owner, leaving 7 bits for the piece, though reserve zero for empty.
Alternatively, zero for empty, sign for the owner, absolute value for the piece.

If you want, you could use bitfields to avoid manual masking, though they are unportable:

class field {
    signed char data = 0;
public:
    constexpr field() = default;
    constexpr field(bool black, piece x) noexcept
    : data(x < piece::pawn || x > piece::king ? 0 : (black << 7) | x))
    {}
    constexpr bool is_black() noexcept { return data < 0; }
    constexpr bool is_white() noexcept { return data > 0; }
    constexpr bool empty() noexcept { return data == 0; }
    constexpr piece piece() noexcept { return piece(data & 0x7f); }
};
  • Interesting ... and apt considering it's a C++ question. But, not being a C++ programmer, this wouldn't be my first (or second or third) instinct! ... This maybe be the most C++ answer here though! – svidgen May 15 at 23:23
  • 7
    This is a microoptimization that no real application should follow. – Lie Ryan May 16 at 0:08
  • @LieRyan If all you have is OOP, everything smells like an object. And whether it's really that "micro" is an interesting question too. Depending on what you do with it, that might be quite significant. – Deduplicator May 16 at 0:42
  • Heh ... that said, C++ is object oriented. I assume there's a reason the OP is using C++ instead of just C. – svidgen May 16 at 1:06
  • 3
    Here I'm less concerned about the lack of OOP, but rather obfuscating the code with bit twiddling. Unless you're writing code for 8-bit Nintendo or you are writing algorithms that requires dealing with millions of copies of the board state, this is premature microoptimization, and inadvisable. In the normal scenarios, it probably won't be faster anyway because the unaligned bit access requires extra bit-operations. – Lie Ryan May 16 at 10:20

Your Answer

CanISleepYet is a new contributor. Be nice, and check out our Code of Conduct.

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.