Структуризация управлением определёнными объектами
Пытаюсь реализовать простую систему инвентаря.
Есть базовый абстрактный класс Item, который требует переопределение getName(), getType().
class Item
{
public:
virtual string getName() = 0;
virtual ItemType getType() = 0;
};
Так-же от него наследуются классы различных предметов
class SymCard : public Item
{
public:
SymCard(int number);
int getNumber() const;
string getName() override;
ItemType getType() override;
private:
int _number;
};
Существует класс Cell где содержится эти самые предметы
class Cell
{
public:
Cell();
bool Add(Item& item, int count = 0);
private:
Item* _item;
int _count;
};
Так-как класс абстрактный, то при добавлении предмета в Cell я могу лишь использовать методы getName(), getType(). А конкретно методы getNumber() предмета SymCard - нет.
Были предложения с помощью type узнавать какой предмет находиться в инвентаре и каждый раз кастовать для каждого предмета. Но это не удобно и думаю, что есть возможно лучше.
Как быть или же где про это можно почитать?
Ответы (3 шт):
Допустим возвращаемый тип методов, которые нужно вызвать _ одинаковый. Тогда для какого то типа R я просто напишу пример кода, и, если появятся вопросы, добавлю разъяснения:
/*Сделаем базовый класс шаблонным,
добавим do_it(), которую производные классы могут и не переопределять*/
template <class R>
class Item
{
protected:
using type = R;
public:
virtual type do_it() { return R(); }
virtual string getName() = 0;
virtual ItemType getType() = 0;
};
//допустим нам нужны методы, возвращающие int
class SymCard : public Item<int>
{
public:
virtual type do_it() {
return getNumber();
}
SymCard(int number){}
int getNumber() const { return _number; }
string getName() override { return "SymCard"; }
ItemType getType() override { }
SymCard() : _number(1){}
private:
int _number{};
};
class Obj : public Item<int>
{
char c = 'a';
public:
Obj() = default;
virtual type do_it() {
return getCh();
}
string getName() override { return "Obj"; }
ItemType getType() override { }
int getCh() const { return c; }
};
template <class R>
class Cell
{
public:
Cell() :_item(0), _count(0) {}
bool Add(Item<R>& item, int count = 0)
{
++count;
_item = &item;
return _item;
}
virtual void show() const {
std::cout << _item->do_it() << std::endl;
}
//...
private:
Item<R>* _item;
int _count;
};
int main() {
Cell<int> c;
SymCard s;
Obj ob;
c.Add(s);
c.show();
c.Add(ob);
c.show();
return 0;
}
Конечно код не имеет товарный вид, но, по мне, отвечает на вопрос. Ну как быть, если хотим, чтобы do_it() вызвал функции_члены с разной сигнатурой? Тут нужно импровизировать с того, что имеем...
P.S. Исходя из комментариев автора вопроса, задача сводится к более тривиальной. Я не буду писать разные производные классы, потому что они все будут одинаковыми, потому я напишу просто шаблон производного класса. Базовому классу нужен лишь один виртуальный метод, чтобы вызвать переопределенный метод производного класса, который и выводит информацию в нужный нам поток:
struct Item {
virtual void getData(std::ostream&) = 0;
virtual ~Item() = default;
};
template <class R>
class Product : public Item {
public:
R obj;
explicit Product(const R& data)
: obj(data) { }
void getData(std::ostream& out) override
{
out <<"id: " << obj << '\n';
}
};
Теперь, для списка товаров(в примере будет массив указателей на них) напишем простой пример:
int main() {
Item* arr[] = {
new Product<int>(5),
new Product<std::string>("five"),
new Product<char>('p')
};
//получим храниую информацию каждого объекта
for (auto ptr : arr) {
ptr->getData(std::cout);
//...
}
return 0;
}
Код, вероятно, вышел натянутым и кажется есть намек на утечку памяти (мне не нравится, как я на быструю руку накатал конструктор, но ладно).
#include <iostream>
#include <memory>
using namespace std;
enum {_SomeStruct, _AnotherOne};
template<int StructType>
struct Item {
int struct_type = StructType;
virtual string getName() = 0;
virtual int getType() = 0;
};
constexpr int exprType = _SomeStruct;
struct SomeStruct : Item<exprType>
{
string getName() override { return "SomeStruct"; }
int getType() override { return struct_type; }
string getSpecificSome() { return "Specific"; }
};
template<class ItemType>
class Cell
{
public:
Cell() = delete;
Cell(ItemType* item) {
_item.reset(item);
}
string getSpecificSome() {
if(_item->getType() == _SomeStruct) {
auto casted = static_cast<SomeStruct*>(_item.get());
return casted->getSpecificSome();
}
else
return "No method";
}
private:
std::unique_ptr<ItemType> _item;
int _count;
};
int main()
{
Cell<SomeStruct> cell(new SomeStruct);
std::cout << cell.getSpecificSome();
return 0;
}
при показе игроку, я должен буду указать, что у симки есть свой номер, у телефона есть своя марка. И вопрос, здесь постоянно надо будет кастовать или же можно как-то сделать практичней?
На сколько я понимаю, ячейке интерфейса всё равно, будет ли это марка мобильного телефона или номер сим карты. Тогда можно просто сделать так, чтобы Item возвращал список своих свойств. В самом примитивном случае это будет выглядеть как-то так:
class PropertyData {
std::string name;
std::string prettyName;
std::string desc;
public:
friend class Property;
PropertyData (std::string name, std::string prettyName, std::string desc)
:name(name), prettyName(prettyName), desc(desc);
};
class Property {
const PropertyData* d;
std::string val;
public:
Property(const PropertyData* d, std::variant val): d(d), val(val);
const std::string& getName() const {return d->name;};
const std::string& getPrettyName() const {return d->prettyName;};
const std::string& getDescription() const {return d->desc;};
std::string& value() {return val;}
const std::string& value() const {return val;}
}
// ------------------------------------------------------------
class Item {
public:
// ...
virtual std::vector<Property> getPropertyList{return std::vector<Property>();}
};
class SimCard : public Item{
public:
private:
int _number;
};
// ------------------------------------------------------------
class SimCard : public Item {
static const PropertyData numberProperty;
public:
// ...
virtual std::vector<Property> getPropertyList() override;
};
const PropertyData SimCard::numberProperty{"phone_number", "Phone number",
"A sequence of digits assigned to a fixed-line telephone subscriber station "
"connected to a telephone line or to a wireless electronic telephony device, "
"such as a radio telephone or a mobile telephone, or to other devices for data "
"transmission via the public switched telephone network (PSTN) or other public and "
"private networks"};
std::vector<Property> SimCard::getPropertyList {
std::vector<Property> rv;
rv.emplace_back(&numberProperty, std::to_string(number));
return rv;
}
// ------------------------------------------------------------
class Cell {
std::vector<Item*> items;
public:
// ...
};
void Cell::printItem() {
for(const Item* item: items) {
std::cout << item->getName() << std::endl
for (Property & prop: item->getPropertiesList()) {
std::cout << " " << prop.getPrettyName() << ": " << prop.value() << std::endl;
}
}
}