Быстрый поиск этого стека обмена показывает, что в целом состав обычно считается более гибким, чем наследование, но, как всегда, это зависит от проекта и т. Д., И бывают случаи, когда наследование является лучшим выбором. Я хочу создать трехмерную шахматную игру, в которой каждая фигура имеет сетку, возможно, различные анимации и так далее. В этом конкретном примере кажется, что вы могли бы аргументировать оба подхода, я не прав?
Наследование будет выглядеть примерно так (с правильным конструктором и т. Д.)
class BasePiece
{
virtual Squares GetValidMoveSquares() = 0;
Mesh* mesh;
// Other fields
}
class Pawn : public BasePiece
{
Squares GetValidMoveSquares() override;
}
который, безусловно, подчиняется принципу «есть», тогда как композиция будет выглядеть примерно так
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
}
Это больше вопрос личных предпочтений или один подход явно более разумный, чем другой здесь?
РЕДАКТИРОВАТЬ: Я думаю, что я узнал кое-что из каждого ответа, поэтому я чувствую себя плохо, выбирая только один. Мое окончательное решение будет вдохновлено несколькими постами здесь (все еще работаем над этим). Спасибо всем, кто нашел время, чтобы ответить.
источник
var Pawn = new Piece(howIMove, howITake, whatILookLike)
мне кажется намного проще, более управляемым и более обслуживаемым, чем иерархия наследования.Ответы:
На первый взгляд ваш ответ делает вид, что «композиция» решения не использует наследование. Но я думаю, вы просто забыли добавить это:
(и больше из этих производных, один для каждого из шести типов частей). Теперь это больше похоже на классический паттерн «Стратегия» и также использует наследование.
К сожалению, у этого решения есть недостаток:
Piece
теперь каждый из них дважды сохраняет свою информацию о типе:внутри переменной-члена
type
внутри
movementComponent
(представленный подтипом)Избыточность - это то, что действительно может доставить вам неприятности - простой класс, подобный a,
Piece
должен обеспечивать «единый источник правды», а не два источника.Конечно, вы можете попытаться хранить информацию о типе только в
type
дочерних классах и не создавать ихMovementComponent
. Но этот дизайн, скорее всего, приведет к огромному «switch(type)
» заявлению в реализацииGetValidMoveSquares
. И это, безусловно, убедительный признак того, что наследование является лучшим выбором здесь .Обратите внимание на «наследство» дизайн, довольно легко обеспечить поле «тип» в нерезервированной образом: добавить виртуальный метод
GetType()
вBasePiece
и реализовать его соответствующим образом в каждом базовом классе.Что касается «продвижения»: я нахожусь здесь с @svidgen, я считаю спорные аргументы, представленные в ответе @ TheCatWhisperer .
Интерпретация «продвижения» как физического обмена произведений вместо того, чтобы интерпретировать его как смену типа одного и того же произведения, кажется мне более естественной. Поэтому реализация этого аналогичным образом - путем обмена одной фигуры на другую другого типа - скорее всего, не вызовет каких-либо огромных проблем - по крайней мере, не для конкретного случая шахмат.
источник
movementComponent
. Посмотрите мое редактирование, как обеспечить поле типа безопасным способом в дизайне «наследования».Вероятно, я бы предпочел более простой вариант, если у вас нет явных, четко сформулированных причин или серьезных проблем с расширяемостью и обслуживанием.
Опция наследования состоит в меньшем количестве строк кода и меньшей сложности. Каждый тип фигуры имеет «неизменное» соотношение 1 к 1 с его характеристиками движения. (В отличие от его представления, которое является 1-к-1, но не обязательно неизменным - вы можете предложить различные «скины».)
Если вы разбиваете эти неизменные отношения «один к одному» между фигурами и их поведением / движением на несколько классов, вы, вероятно, только добавляете сложность - и не намного больше. Итак, если бы я просматривал или наследовал этот код, я бы ожидал увидеть хорошие, документированные причины сложности.
Все это говорит о том, что еще более простой вариант , IMO, - создать
Piece
интерфейс, который реализуют ваши отдельные пьесы . В большинстве языков это на самом деле очень отличается от наследования , потому что интерфейс не будет ограничивать вас от реализации другого интерфейса . ... В этом случае вы просто не получаете никакого поведения базового класса бесплатно: вы бы хотели поместить общее поведение в другое место.источник
С шахматами дело в том, что правила игры и правила фигуры прописаны и зафиксированы. Любой дизайн, который работает хорошо - используйте все, что вам нравится! Экспериментируйте и попробуйте их все.
В деловом мире, однако, ничто так строго не предписано, как это - бизнес-правила и требования меняются со временем, и программы должны меняться со временем, чтобы приспособиться. Вот где разница между данными и данными. Здесь простота упрощает сопровождение сложных решений в течение долгого времени и меняющихся требований. В целом, в бизнесе нам также приходится иметь дело с постоянством, которое также может включать базу данных. Итак, у нас есть правила, например, не использовать несколько классов, когда один класс будет выполнять работу, и не использовать наследование, когда композиции достаточно. Но все эти правила направлены на то, чтобы сделать код обслуживаемым в долгосрочной перспективе перед лицом изменяющихся правил и требований - что не так в шахматах.
Что касается шахмат, наиболее вероятный путь долгосрочного обслуживания заключается в том, что ваша программа должна становиться все умнее и умнее, что в конечном итоге означает, что оптимизация скорости и хранилища будет доминировать. Для этого вам, как правило, придется идти на компромиссы, которые жертвуют удобочитаемостью и производительностью, поэтому даже самый лучший дизайн ОО в конечном итоге уйдет на второй план.
источник
Я хотел бы начать с некоторых замечаний о проблемной области (то есть правила шахмат):
Это неловко моделировать как ответственность самой пьесы, и ни наследование, ни композиция не чувствуются здесь великолепно. Было бы более естественно смоделировать эти правила как первоклассные граждане:
MovementRule
это простой, но гибкий интерфейс, который позволяет реализациям обновлять состояние платы любым способом, необходимым для поддержки сложных движений, таких как рокировка и повышение по службе. Для стандартных шахмат у вас была бы одна реализация для каждого типа фигуры, но вы также можете подключить различные правила вGame
экземпляре для поддержки разных вариантов шахмат.источник
Я думаю, что в этом случае наследование будет более чистым выбором. Композиция может быть немного более элегантной, но мне кажется, что она немного более вынужденная.
Если вы планируете разрабатывать другие игры, в которых используются движущиеся фигуры, которые ведут себя по-разному, композиция может быть лучшим выбором, особенно если вы использовали фабричный шаблон для генерации фигур, необходимых для каждой игры.
источник
Правильно, значит, вы используете наследование. Вы все еще можете сочинять в своем классе Piece, но, по крайней мере, у вас будет основа. Композиция более гибкая, но гибкость, в общем, переоценена, и, конечно, в этом случае, потому что шансы того, что кто-то представит новый тип пьесы, который потребует от вас отказаться от вашей жесткой модели, равны нулю. Игра в шахматы не изменится в ближайшее время. Наследование дает вам руководящую модель, что-то, что также должно быть связано, обучающий код, направление. Композиции не так много, самые важные доменные сущности не выделяются, она бесхребетна, вам нужно разбираться в игре, чтобы понимать код.
источник
Пожалуйста, не используйте наследование здесь. В то время как другие ответы, несомненно, имеют много драгоценных камней, использование наследования здесь, безусловно, было бы ошибкой. Использование композиции не только помогает вам решать проблемы, которые вы не ожидаете, но я уже вижу здесь проблему, которую наследование не может обработать изящно.
Повышение в должности, когда пешка превращается в более ценную фигуру, может стать проблемой наследования. Технически, вы можете справиться с этим, заменив одну часть другой, но у этого подхода есть ограничения. Одним из таких ограничений будет отслеживание статистики по частям, когда вы можете не захотеть сбрасывать информацию о деталях при продвижении (или писать дополнительный код для их копирования).
Теперь, что касается дизайна вашей композиции в вашем вопросе, я думаю, что это излишне сложно. Я не подозреваю, что вам нужно иметь отдельный компонент для каждого действия и можно придерживаться одного класса для каждого типа элемента. Тип enum также может быть ненужным.
Я бы определил
Piece
класс (как у вас уже есть), а такжеPieceType
класс. ВPieceType
классе определяет все методы , которые пешка, ферзь, ЭСТ должен определить, такие как,CanMoveTo
,GetPieceTypeName
, ЭСТ. Тогда классыPawn
andQueen
, ect фактически наследуются отPieceType
класса.источник
Piece
не виртуальный, я согласен с этим решением. Хотя дизайн класса на первый взгляд может показаться немного более сложным, я думаю, что реализация в целом будет проще. Главные вещи, которые могут быть связаны с,Piece
а не с,PieceType
- это его личность и, возможно, его история перемещений.С моей точки зрения, с учетом предоставленной информации не имеет смысла отвечать, должно ли быть наследство или состав.
Ваши модели совершенно разные, в первой вы смоделировали концепцию шахматных фигур, во второй - концепцию движения фигур. Они могут быть функционально эквивалентными, и я бы выбрал тот, который лучше отражает область и помогает мне легче рассуждать об этом.
Кроме того, во втором дизайне тот факт, что у вашего
Piece
класса естьtype
поле, ясно показывает, что здесь есть проблема дизайна, так как сама часть может быть нескольких типов. Разве это не похоже на то, что, вероятно, следует использовать какое-то наследование, где сама фигура является типом?Итак, вы видите, очень трудно спорить о вашем решении. Важно не то, использовали ли вы наследование или композицию, а то, точно ли ваша модель отражает суть проблемы, и полезно ли ее рассуждать и обеспечивать ее реализацию.
Требуется некоторый опыт для правильного использования наследования и композиции, но это совершенно разные инструменты, и я не верю, что можно «заменить» один другим, хотя я могу согласиться с тем, что система может быть спроектирована полностью с использованием только композиции.
источник
Ну, я не предлагаю ни того, ни другого.
Хотя объектная ориентация является хорошим инструментом и часто помогает упростить вещи, это не единственный инструмент в наборе инструментов.
Вместо этого рассмотрите возможность реализации класса Board, содержащего простой байтовый массив.
Интерпретация и вычисление чего-либо происходит в функциях-членах с использованием битовой маскировки и переключения.
Используйте MSB для владельца, оставив 7 бит для части, хотя оставьте ноль для пустого.
Альтернативно, ноль для пустого, знак для владельца, абсолютное значение для части.
Если вы хотите, вы можете использовать битовые поля, чтобы избежать ручной маскировки, хотя они непереносимы:
источник