Você já tem algumas respostas legais, mas o enorme elefante na sala da sua pergunta é este:
ouvimos de alguém que o uso de herança deve ser evitado e, em vez disso, devemos usar interfaces
Como regra geral, quando alguém fornece uma regra geral, ignore-a. Isso não vale apenas para "alguém lhe dizendo algo", mas também para ler coisas na internet. A menos que você saiba o porquê (e possa realmente apoiá-lo), esse conselho é inútil e geralmente muito prejudicial.
Na minha experiência, os conceitos mais importantes e úteis no POO são "baixo acoplamento" e "alta coesão" (classes / objetos sabem o mínimo possível um do outro e cada unidade é responsável pelo mínimo de coisas possível).
Baixo acoplamento
Isso significa que qualquer "pacote de coisas" no seu código deve depender do ambiente ao menor tempo possível. Isso vale para classes (design da classe), mas também para objetos (implementação real), "arquivos" em geral (ou seja, número de #include
s por .cpp
arquivo único , número de import
por .java
arquivo único e assim por diante).
Um sinal de que duas entidades estão acopladas é que uma delas quebrará (ou precisará ser alterada) quando a outra for alterada de alguma forma.
A herança aumenta o acoplamento, obviamente; alterar a classe base altera todas as subclasses.
As interfaces reduzem o acoplamento: definindo um contrato claro e baseado em método, você pode alterar qualquer coisa sobre os dois lados da interface livremente, desde que não altere o contrato. (Observe que "interface" é um conceito geral, as interface
classes abstratas Java ou C ++ são apenas detalhes de implementação).
Coesão alta
Isso significa que cada classe, objeto, arquivo etc. se preocupe ou seja responsável pelo mínimo possível. Ou seja, evite classes grandes que fazem muitas coisas. No seu exemplo, se suas armas tiverem aspectos completamente separados (munição, comportamento de tiro, representação gráfica, representação de inventário etc.), você poderá ter diferentes classes que representam exatamente uma dessas coisas. A classe principal de armas se transforma em um "detentor" desses detalhes; um objeto de arma é pouco mais do que alguns ponteiros para esses detalhes.
Neste exemplo, você garantiria que sua classe que representa o "comportamento de tiro" saiba o mínimo humanamente possível sobre a classe principal de armas. Idealmente, nada. Isso significaria, por exemplo, que você poderia atribuir "Comportamento de disparo" a qualquer objeto em seu mundo (torres, vulcões, NPCs ...) com apenas um clique. Se em algum momento você quiser alterar a forma como as armas são representadas no inventário, basta fazê-lo - apenas sua classe de inventário sabe disso.
Um sinal de que uma entidade não é coesa é se cresce cada vez mais, ramificando-se em várias direções ao mesmo tempo.
A herança que você descreve diminui a coesão - suas classes de armas são, no final das contas, grandes pedaços que lidam com todos os tipos de aspectos diferentes e não relacionados a suas armas.
As interfaces indiretamente aumentam a coesão, dividindo claramente as responsabilidades entre os dois lados da interface.
O que fazer agora
Ainda não existem regras rígidas, tudo isso são apenas diretrizes. Em geral, como o usuário TKK mencionou em sua resposta, a herança é ensinada muito na escola e nos livros; é a coisa mais chique sobre OOP. As interfaces são provavelmente mais chatas de ensinar, e também (se você passar por exemplos triviais) um pouco mais difícil, abrindo o campo da injeção de dependência, que não é tão clara quanto a herança.
No final do dia, seu esquema baseado em herança ainda é melhor do que não ter um design claro de OOP. Portanto, fique à vontade para ficar com ela. Se desejar, você pode refletir um pouco no Google sobre baixo acoplamento, alta coesão e ver se deseja adicionar esse tipo de pensamento ao seu arsenal. Você sempre pode refatorar para tentar isso, se desejar, mais tarde; ou experimente abordagens baseadas em interface em seu próximo novo módulo de código.