I have a class Tiles
that looks something like this:
class Tiles {
public:
void AddTile(int x) { tiles_.push_back(x); }
std::vector<int> tiles_;
}
Now I want to create a class Tiles
with data, i.e.,
class TilesWithData : public Tiles {
public:
void AddTile(int x, double data) {
tiles_.push_back(x);
data_.push_back(data);
}
std::vector<double> data_;
}
In this scenario I want that if a TilesWithData
is created, the AddTile(int x)
deleted, so that only the version AddTile(int x, double data)
is available. Is that possible? What is the best design pattern for this case? Thanks!
-
1Could you elaborate your use-case? Generally speaking what you described sounds a bit as if your design violates the LSP (see here), or at least the principle of least astonishment, but it's a bit hard to tell without knowing what you are trying to achieve.Paul Kertscher– Paul Kertscher2018年02月08日 12:52:14 +00:00Commented Feb 8, 2018 at 12:52
3 Answers 3
What behaviours do these classes have? At the moment you'd do better with
using Tile = int;
using Tiles = std::vector<Tile>;
struct TileWithData { Tile tile; double data; };
using TilesWithData = std::vector<TileWithData>;
The simple answer to your question is prefer composition over inheritance
TilesWithData
is publicly derived from Tiles
, hence it can be assigned to a (smart or dumb) pointer of type Tiles
(my C++ is a bit rusted, but basically this should be sound)
Tiles *tiles = new TilesWithData();
Disallowing tiles->AddTile(123)
would violate the Liskov Substitution Principle (see here), since a client that has a pointer to a Tiles
instance (be it an actual Tiles
or a TilesWithData
) would not know not to call AddTile
.
If there is a common functionality, you might introduce an abstract base class for both
class Tiles
{
public:
virtual vector<int> GetTiles() = 0;
}
from which you can derive SimpleTiles
class SimpleTiles : public Tiles
{
public:
void AddTile(int x) { tiles_.push_back(x); }
std::vector<int> GetTiles
{
return tiles_;
}
private:
std::vector<int> tiles_;
}
and TilesWithData
class TilesWithData : Tiles
{
public:
void AddTile(int x, double data)
{
tiles_.push_back(x);
data_.push_back(data);
}
std::vector<int> GetTiles
{
return tiles_;
}
std::vector<double> GetData()
{
return data_;
}
private:
// elided
}
This way, the data (internals) of your class is encapsulated and you are free to change the actual internal implementation without breaking the clients of your code. Furthermore, any client, holding a reference to Tiles
can be sure about its capabilities: Getting the list of tiles. Anything further is up to the derivatives.
Remarks: There is still plenty of space for improvements. The Tiles
implementations are neither fish nor meat, they don't provide real logic, but they are neither simple collections. A better way could be abstrac tiles
class Tile
{
public:
virtual void Update() = 0;
}
and a single collection to hold all types derived from Tile
class Tiles
{
public:
void AddTile(std::shared_ptr<Tile> tileToAdd)
{
tiles_.push_back(tileToAdd);
}
void UpdateTiles()
{
for(std::vector<std::shared_ptr<Tile>>:iterator it = tiles_.begin(); it != tiles_.end(); it++)
{
(*it)->Update();
}
}
private:
std::vector<std::shared_ptr<Tile>> tiles_;
}
Now you can implement different types of tiles suited to your needs
class SimpleTile : public Tile
{
public:
SimpleTile(int whatever)
{
// ...
}
void Update()
{
// ...
}
// ...
}
class TileWithData : public Tile
{
public:
TileWithData(int whatever, double data)
{
// ...
data_ = data;
}
void Update()
{
// ...
}
// ...
}
Disclaimer: It might well be that there are some errors in my C++ syntax. If my intention is clear, feel free to edit, otherwith, please leave me a comment.
-
Great answer! And there's no need to apologize for syntax details. This site is intended for whiteboard-level questions, and as discussed elsewhere we cannot expect anyone to scribble perfect code on a whiteboard – a whiteboard doesn't have a compiler. Feel free to use any pseudocode you like, as long as it communicates your intent :)amon– amon2018年02月09日 11:30:30 +00:00Commented Feb 9, 2018 at 11:30
The Template way of life
The only thing in common between Tiles
and TilesWithData
seem to be that they do similar things but with different data type. So inheritance is not well suited. You should prefer a generic Tiles
instead:
template <class DataType>
class Tiles {
public:
void AddTile(DataType x) { tiles_.push_back(x); }
std::vector<DataType> tiles_;
};
Once this is defined, you can use any kind of tiles:
Tiles<int> a;
a.AddTile(10);
Tiles<std::string> b;
b.AddTile("hello, world");
Tiles<std::tuple<int, double>> c;
c.AddTile(std::make_tuple(20, 12.5));
Note the last example: you could use your own type. But if you just want to freely use a combination of several types without bothering, you can use std::tuple.
In case you'd prefer to keep inheritance...
Design issues
There are several problems with your design:
- first, the data members should be
private
orprotected
. What's the sense of worrying about the illegitimate use ofAddTile()
, if on the other hand, anybody could altertiles_
directly with apush_back()
? - second,
TilesWithData::AddTile()
should not mess withtiles_
directly, but rather use the base class interface (i.e. replace the explicit pushback withTiles::AddTile(x);
). Why ? Just imagine what would happen if you'd decide to switch tostd::list tiles_;
... - third, you use inheritance improperly: if
TilesWithData
has a different interface, it is not really a kind ofTiles
(and back to my first proposal ;-)
And it already works as you want !
With your classes, your scenario works already as desired ! This is because of the name hiding. For example, the following code will result in an error message:
TilesWithData b;
b.AddTile(30); /// OUCH !
The error message is:
prog.cpp:26:14: error: no matching function for call to ‘TilesWithData::AddTile(int)’
b.AddTile(30);
^
If you wanted to use the base classe's AddTile()
you'd need to "import" its name by inserting following declaration in TilesWithData
for the overload to work properly:
using Tile::AddTile;
Explore related questions
See similar questions with these tags.