Você pode ter aulas / resumos "vazios"?

10

Claro que você pode, eu só estou me perguntando se é racional projetar dessa maneira.

Estou criando um clone de fuga e desenvolvendo um design de classe. Eu queria usar a herança, mesmo que não precisasse, para aplicar o que aprendi em C ++. Eu estava pensando em design de classe e criei algo assim:

GameObject -> classe base (consiste em membros de dados como xey deslocamentos e um vetor de SDL_Surface *

MovableObject: GameObject -> classe abstrata + classe derivada de GameObject (um método void move () = 0;)

NonMovableObject: GameObject -> classe vazia ... nenhum método ou membro de dados além de construtor e destruidor (pelo menos por enquanto?).

Mais tarde, planejei derivar uma classe de NonMovableObject, como Tileset: NonMovableObject. Eu estava pensando se classes abstratas "vazias" ou apenas classes vazias são frequentemente usadas ... Percebo que, do jeito que estou fazendo isso, estou apenas criando a classe NonMovableObject apenas por uma questão de categorização.

Eu sei que estou pensando demais nas coisas apenas para criar um clone de fuga, mas meu foco é menos no jogo e mais no uso de herança e no design de algum tipo de estrutura de jogo.

Camarão Crackers
fonte

Respostas:

2

No C ++, você tem herança múltipla, portanto não há nenhum benefício em ter essas classes vazias. Se você deseja que o Ball seja herdado do GameObject e MovableObject mais tarde (digamos, por exemplo, você deseja manter uma matriz de GameObjects e chamar um método Tick a cada segundo de um segundo para fazer com que todos se movam), isso é fácil de fazer.

Mas, nessa situação, eu pessoalmente sugeriria que você se lembrasse do axioma " prefere composição (encapsulamento) ao invés de herança " e observe o padrão do Estado . Se você deseja que cada objeto tenha seu estado de movimento posteriormente, passe-o para o GameObject. Por exemplo: DiagonalBouncingMovementState para a bola, HoriazontalControlledMovementState para a raquete, NoMovementState para uma peça.

1) Isso facilitará a criação de testes de unidade , se você optar por (o que eu recomendaria novamente) - porque você pode testar o GameObject e cada um de seus estados independentemente.

2) Digamos que você tenha fichas caindo dos tijolos, mas deseje alterar uma delas para que elas se movam na diagonal - em vez de alterá -la em torno de uma hierarquia complexa de herança de classe, basta alterar o estado do movimento codificado que é passado a ela .

3) Mais importante: quando você quer começar a mudar no jogo como a bola e / ou a raquete se movem com base nessas fichas, basta mudar o estado do movimento (DiagonalMovementState se torna DiagonalWithGravityMovementState).

Agora, nada disso sugeria que a herança é sempre ruim, apenas para demonstrar por que preferimos o encapsulamento ao invés da herança. Você ainda pode derivar cada tipo de objeto do GameObject e fazer com que essas classes entendam seu estado de movimento inicial. Você quase certamente desejará derivar cada um de seus estados de movimento de uma classe MovementState abstrata porque o C ++ não possui interfaces.

$ 0,02

pdr
fonte
8

Em Java, as interfaces "vazias" são usadas como marcadores (por exemplo, serializáveis), porque em tempo de execução, os objetos podem ser verificados ou não "implementam" essa interface; mas em C ++, parece-me inútil.

Na IMO, os recursos de idioma devem ser considerados ferramentas, algo que ajuda você a atingir determinados objetivos, e não obrigações. Só porque você pode usar um recurso não significa que você precisa . A herança sem sentido não melhora um programa.

user281377
fonte
3

Você não está pensando demais; você está pensando e isso é bom.

Como já foi dito, não use um recurso de idioma apenas porque está lá. As trocas de recursos do idioma de aprendizagem são essenciais para um bom design.

Não se deve usar classes base para categorização. Isso pode implicar a verificação do tipo. Essa é uma péssima ideia.

Considere escrever abstrações puras que definem o contrato de seus objetos. O contrato dos seus objetos é a interface externa. Sua interface pública. O que faz.

Nota: É importante entender que não são esses dados x, ymas o que eles têm move().

No seu design, você tem uma classe GameObject. Você está confiando nele para seus dados e não para sua funcionalidade. Ele se sente eficiente e correta de usar a herança para compartilhar o que parece ser dados comuns partilhados, no seu caso xe y.

Este não é o uso correto da herança. Lembre-se sempre de que a herança é a forma mais rígida de acoplamento que você pode ter e deve procurar o acoplamento solto o tempo todo.

Por sua própria natureza, NonMovableObjectnão se move. É xe ydeve certamente ser declarado const. Por sua própria natureza, MoveableObjectprecisa se mover. Não pode declarar seu xe ycomo const. Portanto, esses não são os mesmos dados e não podem ser compartilhados.

Considere a funcionalidade ou o contrato dos seus objetos. O que eles deveriam fazer ? Faça isso direito e os dados virão. Trabalhe em um objeto de cada vez. Se preocupe com cada um por sua vez. Você verá que seus bons projetos são reutilizáveis.

Talvez não exista NonMovableObject. Que tal uma abstração pura GameObjectBaseque define um move()método? Seu sistema instancia GameObjects que implementam move()e o sistema move apenas os que precisam ser movidos.

Isto é apenas o começo; um gosto. A toca do coelho é muito mais profunda. Não tem colher.

hiwaylon
fonte
Embora seja verdade que nem MovableObjectnem NonMovableObjectpode herdar um do outro, ambos têm uma localização; como tal, ambos devem ser consumíveis por código que, por exemplo, deseja direcionar o objetivo de um monstro para um objeto, sem levar em consideração se esse objeto pode se mover ou não.
Supercat
2

Ele possui aplicativos em C #, como o ammoQ mencionado, mas em C ++ o único aplicativo seria se você quisesse ter uma lista de ponteiros NonMovableObject.

Mas então eu imagino que você estaria destruindo os objetos através do ponteiro NonMoveableObject, nesse caso você precisaria de destruidores virtuais e não seria mais uma classe vazia. :)

tenpn
fonte
Também pensei sobre esse caso, mas o que você poderia fazer com essa lista de objetos que não expõem nenhum comportamento? Provavelmente, você descobriria o tipo real e usaria uma conversão para acessar os métodos e campos disponíveis - definitivamente um cheiro de código.
user281377
Sim, esse é um bom argumento.
Dezpn #
1

Uma situação em que você pode ver isso é onde deseja que uma coleção fortemente tipada contenha tipos heterogêneos. Não estou dizendo que é uma ótima idéia, pode indicar uma abstração fraca ou um design deficiente, mas acontece. Como você mencionou, é uma maneira de categorizar as coisas e pode ser útil e perfeitamente válido.

Por exemplo, você quer uma coleção para guardar gatos e cães. No seu design, você decide que eles têm muito em comum; portanto, tenha uma classe base de Mamal e use esse tipo em sua coleção (por exemplo, Lista). Tudo bom. Então você decide que deseja adicionar alguns sapos à sua coleção, mas eles não são mamals; portanto, uma maneira de fazer isso seria transformar a subclasse de animais de sapos e mamals, mas talvez você não consiga pensar muito nos sapos e nos mamals. comum, então o Animal fica vazio. Você digita sua coleção com Animal em vez de Mamal e tudo está bem.

Na vida real, acho que, mesmo que eu crie uma classe base vazia mais cedo ou mais tarde, algumas funcionalidades acabam aí, mas certamente há momentos em que elas existem.

Steve
fonte
Uma pergunta a fazer, no entanto, é: se os tipos não têm nada em comum, por que estão sendo mantidos na mesma coleção? Não há nenhuma operação que você possa executar em todos os itens da coleção, o que meio que anula o propósito de ter essa coleção. Agora, pode haver algumas operações artificiais que você pode querer adicionar a ambas para simplificar a lógica - por exemplo, implementar o padrão de design do Visitor -, quando pode se tornar útil novamente, mas agora elas têm algo em comum ...
Jules