4

Over many years, I always find myself reconsidering this design, so I wanted to get some feedback on my solution to it.

Problem:

I need a limited amount of objects = instances from a class, and I don't want to expose the option to create more. I also want easy access to them from everywhere, and operators like equal / not equal.
They need to be class instances, as they need to have methods, so a enum doesn't work.

(Simplified) Example:

Consider for example any chess-like game.

  1. There are three tile colors for the board needed, Lighter and Darker, and a different size tile where taken pieces are put. Also,
  2. the pieces have a color, typically White or Black; and I use NoColor for empty fields.

Both examples, each instance should know how to draw and serialize itself.

Obvious Option:

If I make a simple enum, the values cannot carry methods, so I end up with Draw and Serialize methods in some other class, which are basically an ugly switch/case.

My Solution:

I like to

  1. protect the constructor, and,
  2. create (public) static instances inside the class (C++17 now allows that even directly with inline).

'Each instance gets created with a const 'type' in it (basically a enum), set through the constructor.
Wherever I need the information, I use the const pointer to that static instance. This allows to easily _compare and assign them, and also allows calling their member functions as needed.
It even allows other games to derive from the class and add new static instances, for example, a three-player chess could add a Red instance inside a derived class. Because I hand around pointers to the base class everywhere, it is polymorphic, and the code doesn't have to handle the new color special in any way...

class Color
{
protected:
  Color(char c) : color{c} {}
public:
  void Serialize(Archive& ar) const { ar << color; }

private:
  const char color;

public:  // the only instances ever to exists; handed around by pointer
  inline const static Color NoColor{'X'};   // C++17 syntax, otherwise
  inline const static Color White{'W'};     // separate line in .cpp needed
  inline const static Color Black{'B'};
};

class Chess3Color : public Color
{
protected:
  Chess3Color(char c) : Color{c} {}

public:
  inline const static Chess3Color Red{'R'};
};

Example Usage:

...
// create a White King
Piece* p = new WhiteKing{&Type::King, &Color::White, IDB_W_K_BMP};
...
if (p->IsColor(&Color::White)) { ... }
...
p->Serialize(ar);
...
class Piece
{
  ...
  // Piece member function for serialization
  void Serialize(Archive& ar)
  {
    type->Serialize(ar);
    color->Serialize(ar);
  }
  ...
  // check if this piece is of a certain color
  // note that pointer compare is good enough!
  bool IsColor(const Color* c) { return color == c; }
  ...
}

Question:

I go back and forth in my mind between this being a code smell or a pretty good and clean solution. So is it a code smell?
How else would this be better designed?

I have read a lot about Singletons, and I don't think they would do me any good (aside from the fact that some people think they are a code smell too).

4
  • 2
    // which are basically an ugly switch/case. // It would be wrong to say that "all switch/case are ugly". There are ways to refactor switch/case so that they aren't ugly. Therefore, the beauty/ugly (the appropriateness) of each example of switch/case needs to be further analyzed.
    – rwong
    Commented Apr 29, 2018 at 3:49
  • 4
    Enum classes with methods are a nice-to-have in Java, but in C++ one should define non-member functions that take the enum as input argument and return data (property or attribute corresponding to each enum value) as needed. Then, a function that needs to perform behavior on the enum can consult these non-member functions. Classes would be appropriate if there are behavioral differences, i.e. they require different lines of code for each enum value.
    – rwong
    Commented Apr 29, 2018 at 3:52
  • 2
    In C++, non-member functions which provide behavior to enums can be looked up using argument dependent lookup (ADL).
    – rwong
    Commented Apr 29, 2018 at 4:07
  • 2
    Conceptually, the color is part of a piece's identity. It is not simply a property. In that sense, you should never really be checking to see if a piece is white or black, merely that it is the same color as another piece or not. There should be exactly two places where color is relevant and that's when you're drawing it and when you need to know whose turn it is.
    – Neil
    Commented May 23, 2018 at 6:27

1 Answer 1

2

I need a limited amount of objects = instances from a class, and I don't want to expose the option to create more. I also want easy access to them from everywhere

That is an unnecessary constraint that seems motivated by apriori notions of how you can design and implement a game of chess -- be it a two player chess game or a three player chess game.

What are the main objects (not the objective of the game) in a game of chess?

  1. A game -- the main object. Everything in the game stems from this object.
  2. Two (or three) players.
  3. A board.
  4. Chesss pieces.

Every object has state. Every object has behavior associated with it. In addition the rules of the game dictate how they can interact with one another and how the states of the objects change.

There is nothing to be gained by saying:

  1. One may not create more than two (or three) players. That's up to the game object. If a game chooses to create two players or ten players does not impact the state of a player or its behavior.

  2. One may not create more than one board. Once again, let the game object decide how many boards it wants to create.

  3. One may not create more than 32 pieces. Once again, that's up to the game object. One can easily create variations of the main game of chess by using different board sizes and different number of pieces.

The variations of chess are interesting. See https://en.wikipedia.org/wiki/List_of_chess_variants. There is no reason to constrain your classes the way you want to.

Both examples, each instance should know how to draw and serialize itself.

That's not a good design, IMHO.

The board and the pieces can be drawn in many different ways. Take a look at the images of king that one can find on the net. The board and the pieces can be drawn using many different methods.

A game, and its constituent objects, can be serialized in different way too.

It will be better to use The Strategy Pattern for both of those operations.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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