Я пишу упрощенную игру, чтобы научиться получать больше опыта работы с C++, и у меня есть идея, где, как мне кажется, полиморфизм почти работает, но не работает. В этой игре Party движется довольно линейно через Map, но иногда может столкнуться с Fork на дороге. Форк - это (в основном) std::vector<location*>. Первоначально я собирался закодировать что-то вроде следующего в функцию-член Party:
if (!CurrLocation->fork_.empty())
// Loop through forks and show options to the player, go where s/he wants
else
(CurrLocation++)
Но мне было интересно, может ли быть лучше какой-нибудь вариант из следующего:
CurrLocation = CurrLocation->getNext();
Fork фактически является производным от Location и перегружает некоторую новую функцию getNext(). Но в последнем случае location (структура низкого уровня) должен был бы представлять сообщение пользователю вместо того, чтобы «передавать эту резервную копию», что я не считаю элегантным, поскольку он связывает location с UserInterface::*. .
Ваше мнение?





Вы должны использовать полиморфизм, если он имеет смысл и упрощает ваш дизайн. Вы не должны использовать его только потому, что он существует и имеет красивое название. Если это действительно упрощает ваш дизайн, то стоит связать его.
Правильность и простота должны быть конечной целью любого дизайнерского решения.
Полиморфизм не способствует большей связанности, я думаю, что это отдельные проблемы.
Фактически, если вы программируете интерфейсы и следуете общей инверсии шаблонов управления, то это приведет к меньшей или нулевой связи.
В вашем примере я не вижу, как местоположение связано с UserInterface?
Если это так, можно ли устранить связь с помощью другого уровня абстракции между UserInterface и Location, например LocationViewAdapter?
Верно, но если вы вводите свои базовые классы в свои методы, то это способ уменьшить вашу связь.
Я думаю, вы сами заметили проблемы и, вероятно, сможете решить их, зная остальную часть системы или несколько дополнительных деталей, которые мы можем рассмотреть.
Как уже упоминалось:
Все проблемы можно решить, добавив уровень косвенности. Я бы использовал предложенный вами вариант и отделил Location от Party, позволив getNext принимать объект, который разрешает выбор направления. Вот пример (непроверенный):
class Location;
class IDirectionChooser
{
public:
virtual bool ShouldIGoThisWay(Location & way) = 0;
};
class Location
{
public:
virtual Location * GetNext(IDirectionChooser & chooser)
{
return nextLocation;
}
virtual Describe();
private:
Location * nextLocation;
};
class Fork : public Location
{
public:
virtual Location * GetNext(IDirectionChooser & chooser)
{
for (int i = 0; i < locations.size(); i++)
if (chooser.ShouldIGoThisWay(*locations[i]))
return locations[i];
}
virtual Describe();
private:
vector<Location *> locations;
};
class Party : public IDirectionChooser
{
public:
void Move()
{
currentLocation = currentLocation->GetNext(GetDirectionChooser());
}
virtual IDirectionChooser & GetDirectionChooser() { return *this; }
virtual bool ShouldIGoThisWay(Location & way)
{
way.Describe();
cout << "Do you want to go that way? y/n" << endl;
char ans;
cin >> ans;
return ans == 'y';
}
};
Жалко по поводу номенклатуры интерфейса. В остальном идеально.
Действительно? Почему? Мы используем его там, где я работаю. Если есть веская причина не использовать его, дайте мне знать, и я отредактирую ответ.
Хороший! Я бы предложил добавить в Party: "virtual IDirectionChooser & getDirectionChooser () {return * this;}" и заставить Move () вызывать this вместо прямого использования * this - это позволит подклассу использовать другой (например, внешний) IDirectionChooser реализация, переопределив только этот метод.
Обновлено на основе комментариев j_random_hacker.
Наследование может увеличить сцепление, а полиморфизм, как правило, зависит от наследования.