Qual design de classe é melhor?[fechado]
-
09-06-2019 - |
Pergunta
Qual design de classe é melhor e por quê?
public class User
{
public String UserName;
public String Password;
public String FirstName;
public String LastName;
}
public class Employee : User
{
public String EmployeeId;
public String EmployeeCode;
public String DepartmentId;
}
public class Member : User
{
public String MemberId;
public String JoinDate;
public String ExpiryDate;
}
OU
public class User
{
public String UserId;
public String UserName;
public String Password;
public String FirstName;
public String LastName;
}
public class Employee
{
public User UserInfo;
public String EmployeeId;
public String EmployeeCode;
public String DepartmentId;
}
public class Member
{
public User UserInfo;
public String MemberId;
public String JoinDate;
public String ExpiryDate;
}
Solução
A questão é simplesmente respondida reconhecendo que a herança modela um relacionamento “É-A”, enquanto a associação modela um relacionamento “TEM-A”.
- Um funcionário É um usuário
- Um funcionário TEM informações de usuário
Qual deles está correto?Esta é a sua resposta.
Outras dicas
Eu não gosto de nenhum dos dois.O que acontece quando alguém é membro e funcionário?
Pergunte a si mesmo o seguinte:
- Você quer modelar um funcionário É um usuário?Se sim, escolha herança.
- Você quer modelar um funcionário TEM uma informação do usuário?Se sim, use composição.
- Existem funções virtuais envolvidas entre o Usuário (info) e o Colaborador?Nesse caso, use herança.
- Um Funcionário pode ter múltiplas instâncias de Usuário (informações)?Se sim, use composição.
- Faz sentido atribuir um objeto Employee a um objeto User (info)?Nesse caso, use herança.
Em geral, esforce-se para modelar a realidade que seu programa simula, sob as restrições da complexidade do código e da eficiência necessária.
Não acho que a composição seja sempre melhor que a herança (normalmente).Se Funcionário e Membro são realmente Usuários e são mutuamente exclusivos, então o primeiro design é melhor.Considere o cenário em que você precisa acessar o UserName de um Funcionário.Usando o segundo design você teria:
myEmployee.UserInfo.UserName
o que é ruim (lei de Deméter), então você refatoraria para:
myEmployee.UserName
que requer um pequeno método em Employee para delegar ao objeto User.Tudo isso é evitado pelo primeiro design.
Boa pergunta, embora para evitar distrações sobre certo e errado Eu consideraria perguntar os prós e os contras de cada abordagem - acho que é isso que você quis dizer com o que é melhor ou pior e por quê.De qualquer forma ....
A primeira abordagem, também conhecida como herança
Prós:
- Permite comportamento polimórfico.
- É inicialmente simples e conveniente.
Contras:
- Poderia tornar-se complexo ou desajeitado ao longo do tempo se mais comportamento e relações são adicionados.
A segunda abordagem, também conhecida como composição
Prós:
- Mapeia bem para cenários não-OOP, como tabelas relacionais, programação estruturada, etc.
- É simples (se não necessariamente conveniente) de incrementalmente ampliar relações e comportamento.
Contras:
- Nenhum polimorfismo, portanto, é menos conveniente usar informações e comportamentos relacionados
Listas como essas + as perguntas Jon Limjap mencionado irá ajudá-lo a tomar decisões e começar - então você poderá descobrir o que certo as respostas deveriam ter sido ;-)
Você também pode pensar em Funcionário como um papel do Usuário (Pessoa).A função de um usuário pode mudar com o tempo (o usuário pode ficar desempregado) ou o usuário pode ter várias funções ao mesmo tempo.
A herança é muito melhor quando há real "é uma" relação, por exemplo Maçã - Fruta.Mas tenha muito cuidado:Círculo - A elipse não é uma relação real "é uma", porque o círculo tem menos "liberdade" que a elipse (o círculo é um estado da elipse) - veja: Problema de círculo elipse.
As verdadeiras questões são:
- Quais são as regras de negócios e histórias de usuários por trás de um usuário?
- Quais são as regras de negócios e histórias de usuários por trás de um funcionário?
- Quais são as regras de negócios e histórias de usuários por trás de um membro?
Estas podem ser três entidades completamente não relacionadas ou não, e isso determinará se o seu primeiro ou segundo design funcionará, ou se outro design completamente diferente será adequado.
Nenhum dos dois é bom.Muito estado mutável.Você não deve conseguir construir uma instância de uma classe que esteja em um estado inválido ou parcialmente inicializado.
Dito isto, o segundo é melhor porque favorece a composição em vez da herança.
Declarar seus requisitos/especificações pode ajudar a chegar ao 'melhor design'.
Sua pergunta é muito 'sujeita à interpretação do leitor' no momento.
Aqui está um cenário em que você deve pensar:
A composição (2º exemplo) é preferível se o mesmo Utilizador puder ser Funcionário e Membro.Por que?Porque para duas instâncias (Funcionário e Membro) que representam o mesmo Usuário, se os dados do Usuário forem alterados, você não precisará atualizá-los em dois locais.Somente a instância User contém todas as informações do usuário e somente elas devem ser atualizadas.Como as classes Employee e Member contêm a mesma instância de User, ambas conterão automaticamente as informações atualizadas.
Mais três opções:
Tenha o
User
classe contém as informações suplementares para funcionários e membros, com campos não utilizados em branco (oID
de um determinadoUser
indicaria se o usuário era funcionário, membro, ambos ou qualquer outro).Tem um
User
classe que contém uma referência a umISupplementalInfo
, ondeISupplementalInfo
é herdado porISupplementalEmployeeInfo
,ISupplementalMemberInfo
, etc.Código que é aplicável a todos os usuários pode funcionar comUser
objetos de classe e código que tinha umUser
referência poderia obter acesso às informações suplementares de um usuário, mas esta abordagem evitaria ter que mudarUser
se diferentes combinações de informações suplementares forem necessárias no futuro.Como acima, mas tenha o
User
classe contém algum tipo de coleção deISupplementalInfo
.Esta abordagem teria a vantagem de facilitar a adição de propriedades em tempo de execução a um usuário (por exemplo,porque umMember
foi contratado).Ao utilizar a abordagem anterior, seria necessário definir diferentes classes para diferentes combinações de propriedades;transformar um "membro" em um "membro + cliente" exigiria um código diferente de transformar um "funcionário" em um "funcionário + cliente".A desvantagem desta última abordagem é que tornaria mais difícil proteger-se contra atributos redundantes ou inconsistentes (usando algo como umDictionary<Type, ISupplementalInfo>
armazenar informações suplementares poderia funcionar, mas pareceria um pouco "volumoso").
Eu tenderia a favorecer a segunda abordagem, na medida em que permite uma expansão futura melhor do que a herança direta.Trabalhar com uma coleção de objetos em vez de um único objeto pode ser um pouco trabalhoso, mas essa abordagem pode ser mais capaz do que as outras para lidar com requisitos variáveis.