Вы уже получили несколько хороших ответов, но вот огромный слон в комнате в вашем вопросе:
слышал от кого-то, что следует избегать использования наследования, и вместо этого мы должны использовать интерфейсы
Как правило, когда кто-то дает вам правило, игнорируйте его. Это касается не только «того, кто вам что-то говорит», но и чтения материалов в Интернете. Если вы не знаете, почему (и действительно можете это поддержать), такой совет бесполезен и часто очень вреден.
По моему опыту, наиболее важными и полезными концепциями в ООП являются «слабая связь» и «высокая сплоченность» (классы / объекты знают как можно меньше друг о друге, и каждая единица отвечает за как можно меньше вещей).
Низкая связь
Это означает, что любой «набор вещей» в вашем коде должен как можно меньше зависеть от его окружения. Это относится как к классам (дизайн классов), так и к объектам (фактическая реализация), к «файлам» в целом (т. Е. К числу #include
s в одном .cpp
файле, к числу файлов import
в одном .java
файле и т. Д.).
Признаком того, что две сущности связаны, является то, что одна из них сломается (или должна быть изменена), когда другая изменится каким-либо образом.
Наследование увеличивает сцепление, очевидно; изменение базового класса изменяет все подклассы.
Интерфейсы уменьшают связь: определяя четкий контракт на основе методов, вы можете свободно изменять что-либо в обеих сторонах интерфейса, если вы не измените контракт. (Обратите внимание, что «интерфейс» - это общая концепция, interface
абстрактные классы Java или C ++ - это просто детали реализации).
Высокая сплоченность
Это означает, что каждый класс, объект, файл и т. Д. Должны быть связаны или отвечать за как можно меньше. Т.е. избегайте больших классов, которые делают много вещей. В вашем примере, если ваше оружие имеет совершенно разные аспекты (боеприпасы, поведение при стрельбе, графическое представление, представление инвентаря и т. Д.), То вы можете иметь разные классы, которые представляют именно одну из этих вещей. Основной класс оружия затем превращается в «держателя» этих деталей; тогда объект оружия - это всего лишь несколько указателей на эти детали.
В этом примере вы должны убедиться, что ваш класс, представляющий «поведение при стрельбе», знает как можно меньше о главном классе оружия. Оптимально, вообще ничего. Это может означать, например, что вы можете дать «Поведение огня» любому объекту в вашем мире (башенкам, вулканам, неигровым персонажам ...) одним щелчком пальца. Если вы в какой-то момент захотите изменить способ представления оружия в инвентаре, вы можете просто сделать это - об этом знает только ваш класс инвентаря.
Признаком того, что сущность не является связной, является то, что она становится все больше и больше, разветвляясь в нескольких направлениях одновременно.
Наследование, как вы его описываете, снижает сплоченность - ваши классы оружия, в конце концов, являются большими кусками, которые обрабатывают все виды различных, не связанных аспектов вашего оружия.
Интерфейсы косвенно повышают согласованность, четко разделяя обязанности между двумя сторонами интерфейса.
что делать сейчас
Там до сих пор нет жестких и быстрых правил, все это только рекомендации. В целом, как отметил пользователь TKK в своем ответе, наследование многому учат в школе и книгах; это модные вещи об ООП. Интерфейсы, вероятно, более скучны в обучении, а также (если вы пропустите тривиальные примеры) немного сложнее, открывая область внедрения зависимостей, которая не так очевидна, как наследование.
В конце концов, ваша схема, основанная на наследовании, все же лучше, чем отсутствие четкой схемы ООП вообще. Так что не стесняйтесь придерживаться этого. Если вы хотите, вы можете поразмышлять / погуглить немного о Low Coupling, High Cohesion и посмотреть, хотите ли вы добавить такой тип мышления в свой арсенал. Вы всегда можете рефакторинг, чтобы попробовать это, если хотите, позже; или опробуйте подходы, основанные на интерфейсе, на следующем крупном модуле кода.