Pergunta

Eu quero fazer uma pergunta sobre como você se aproximar de um problema de design orientado a objeto simples. Eu tenho algumas idéias próprias sobre o que a melhor maneira de combater este cenário, mas eu estaria interessado em ouvir algumas opiniões da comunidade Stack Overflow. Links para artigos on-line relevantes também são apreciados. Eu estou usando C #, mas a questão não é específica da linguagem.

Suponha que eu estou escrevendo um aplicativo de locadora de vídeo, cujo banco de dados tem uma tabela Person, com campos PersonId, Name, DateOfBirth e Address. Ele também tem uma mesa Staff, que tem um link para um PersonId, e uma mesa de Customer que também links para PersonId.

Uma abordagem orientada a objetos simples seria dizer que um Customer "é um" Person e, portanto, criar classes um pouco como este:

class Person {
    public int PersonId { get; set; }
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string Address { get; set; }
}

class Customer : Person {
    public int CustomerId { get; set; }
    public DateTime JoinedDate { get; set; }
}

class Staff : Person {
    public int StaffId { get; set; }
    public string JobTitle { get; set; }
}

Agora podemos escrever uma função digamos a e-mails de envio para todos os clientes:

static void SendEmailToCustomers(IEnumerable<Person> everyone) { 
    foreach(Person p in everyone)
        if(p is Customer)
            SendEmail(p);
}

Este sistema funciona bem até que tenhamos alguém que é tanto um cliente e um membro do pessoal. Assumindo que nós realmente não queremos que a nossa lista everyone ter a mesma pessoa duas vezes, uma como um Customer e uma vez como um Staff, fazemos uma escolha arbitrária entre:

class StaffCustomer : Customer { ...

e

class StaffCustomer : Staff { ...

Obviamente apenas o primeiro destes dois não iria quebrar a função SendEmailToCustomers.

Então, o que você faria?

  • Faça a classe Person ter referências opcionais para uma classe StaffDetails e CustomerDetails?
  • Criar uma nova classe que continha uma Person, mais StaffDetails opcional e CustomerDetails?
  • Faça tudo uma interface (por exemplo IPerson, IStaff, ICustomer) e criar três classes que implementaram as interfaces apropriadas?
  • Tome uma outra abordagem completamente diferente?
Foi útil?

Solução

Mark, esta é uma pergunta interessante. Você vai descobrir como muitas opiniões sobre isso. Eu não acredito que há uma resposta 'certa'. Este é um grande exemplo de onde um design rígido objeto heirarchial pode realmente causar problemas depois de um sistema é construído.

Por exemplo, digamos que você foi com o "Cliente" e as classes "pessoal". Você implantar o sistema e tudo é feliz. Algumas semanas mais tarde, alguém aponta que ambos estão 'na equipe' e um 'cliente' e eles não estão recebendo e-mails de clientes. Neste caso, você tem um monte de código muda para fazer (re-design, não re-fator).

Eu acredito que seria excessivamente complexa e difícil de manter se você tentar ter um conjunto de classes derivadas que implementam todas as permutações e combinações de pessoas e seus papéis. Isto é especialmente verdade, dado que o exemplo acima é muito simples -. Na maioria das aplicações reais, as coisas serão mais complexo

Para o seu exemplo aqui, eu iria com "Take outra abordagem completamente diferente". Gostaria de implementar a classe Pessoa e incluir nele uma coleção de "papéis". Cada pessoa pode ter uma ou mais funções tais como "Cliente", "Pessoal" e "Fornecedor".

Isto irá tornar mais fácil para adicionar funções como novos requisitos são descobertos. Por exemplo, você pode simplesmente ter uma base de classe "Papel", e derivar novas funções a partir deles.

Outras dicas

Você pode querer considerar o uso do Partido e padrões de prestação de contas

Esta Pessoa maneira terá uma coleção de Responsabilidades que pode ser do tipo de cliente ou pessoal.

O modelo também será mais simples se você adicionar mais tipos de relacionamento mais tarde.

A abordagem pura seria: tornar tudo uma interface. Como detalhes de implementação, você pode opcionalmente usar qualquer das várias formas de composição ou implementação em herança. Desde que estes são detalhes de implementação, eles não importam para a sua API pública, assim que você é livre para escolher o que torna a sua vida mais simples.

Uma pessoa é um ser humano, enquanto um cliente é apenas um papel que uma pessoa pode tomar de vez em quando. Homem e mulher seriam candidatos para herdar Pessoa, mas cliente é um conceito diferente.

O princípio da substituição Liskov diz que devemos ser capazes de usar as classes derivadas onde temos referências a uma classe base, sem saber sobre ele. Tendo Cliente herdar Pessoa violaria isso. Um cliente pode, talvez, ser também um papel desempenhado por uma organização.

Deixe-me saber se eu entendi a resposta de Foredecker corretamente. Aqui está o meu código (em Python; desculpe, eu não sei C #). A única diferença é que eu não iria notificar alguma coisa, se uma pessoa "é um cliente", eu o faria se um de seu papel "está interessado em" aquela coisa. Isso é suficiente flexível?

# --------- PERSON ----------------

class Person:
    def __init__(self, personId, name, dateOfBirth, address):
        self.personId = personId
        self.name = name
        self.dateOfBirth = dateOfBirth
        self.address = address
        self.roles = []

    def addRole(self, role):
        self.roles.append(role)

    def interestedIn(self, subject):
        for role in self.roles:
            if role.interestedIn(subject):
                return True
        return False

    def sendEmail(self, email):
        # send the email
        print "Sent email to", self.name

# --------- ROLE ----------------

NEW_DVDS = 1
NEW_SCHEDULE = 2

class Role:
    def __init__(self):
        self.interests = []

    def interestedIn(self, subject):
        return subject in self.interests

class CustomerRole(Role):
    def __init__(self, customerId, joinedDate):
        self.customerId = customerId
        self.joinedDate = joinedDate
        self.interests.append(NEW_DVDS)

class StaffRole(Role):
    def __init__(self, staffId, jobTitle):
        self.staffId = staffId
        self.jobTitle = jobTitle
        self.interests.append(NEW_SCHEDULE)

# --------- NOTIFY STUFF ----------------

def notifyNewDVDs(emailWithTitles):
    for person in persons:
        if person.interestedIn(NEW_DVDS):
            person.sendEmail(emailWithTitles)

Gostaria de evitar o "é" de verificação ( "instanceof" em Java). Uma solução é usar um Decorator Pattern . Você poderia criar um EmailablePerson que decora Pessoa, onde EmailablePerson usa composição para manter uma instância privada de uma pessoa e delegados de todos os métodos não-mail para o objeto Pessoa.

Nós estudamos este problema na faculdade no ano passado, estávamos aprendendo Eiffel por isso usamos a herança múltipla. Enfim papéis Foredecker alternativa parece ser suficiente flexível.

O que há de errado no envio de um e-mail a um cliente que é um membro da equipe? Se ele é um cliente, então ele pode ser enviado o e-mail. Estou errado em pensar assim? E por que você deve tomar "todos" como a sua lista de e-mail? Woudlnt seria melhor ter uma lista de clientes, uma vez que estamos lidando com o método "sendEmailToCustomer" e não método "sendEmailToEveryone"? Mesmo se você quiser usar a lista "todos" não se pode permitir duplicatas nessa lista.

Se nenhum destes são realizáveis ??com um monte de redisgn, eu vou com a primeira resposta Foredecker e pode ser que você deve ter algumas funções atribuídas a cada pessoa.

As suas aulas são estruturas de dados apenas: nenhum deles tem qualquer comportamento, apenas getters e setters. A herança é inapropriado aqui.

Tome uma outra abordagem completamente diferente: O problema com a classe StaffCustomer se que o seu membro da equipe pode começar como apenas o pessoal e se tornar um cliente, mais tarde, assim que você precisa para excluí-los como pessoal e criar uma nova instância da classe StaffCustomer . Talvez um booleano simples dentro da classe Staff of 'isCustomer' permitiria nossa lista todos (presumivelmente compilado a partir recebendo todos os clientes e todos os funcionários a partir de tabelas apropriadas) para não ficar o membro do pessoal que vai saber que ele já foi como um cliente incluído.

Eis mais algumas dicas: Na categoria de “nem sequer pensar de fazer isso”, aqui estão alguns maus exemplos de código encontrado:

Encontre o método retorna objeto

Problema: Dependendo do número de ocorrências encontradas o método localizador retorna um número que representa o número de ocorrências - ou! Se apenas um encontrada retorna o objeto real.

Não faça isso! Esta é uma das práticas pior codificação e introduz ambigüidade e messes o código de uma forma que quando um desenvolvedor diferente entra em jogo ela ou ele vai te odeio por ter feito isso.

Solução: Se há uma necessidade de tais 2 funcionalidades:. Contagem e buscar uma instância criam 2 métodos aquele que retorna a contagem e que retorna a instância, mas nunca um único método de fazer os dois lados

Problema: Um derivado prática é má quando um método de localizar retorna ou a uma única ocorrência encontrado quer uma matriz de ocorrências se mais do que aquele encontrado. Este estilo de programação preguiçoso é feito um monte pelos programadores que fazem a anterior em geral.

Solução: Tendo isso em minhas mãos eu gostaria de voltar uma matriz de comprimento 1 (um) se apenas uma ocorrência é encontrada e uma matriz com comprimento> 1 se mais ocorrências encontradas. Além disso, encontrar nenhuma ocorrência em tudo retornaria nulo ou uma matriz de comprimento 0, dependendo da aplicação.

Programação para uma interface e usar tipos de retorno covariantes

Problema:. Programação de uma interface e usar tipos de retorno covariantes e lançando no código de chamada

Solução: Use em vez da mesma supertipo definido na interface para definir a variável que deve apontar para o valor retornado. Isso mantém a programação para uma abordagem de interface e seu código limpo.

Classes com mais de 1000 linhas são um perigo escondido Métodos com mais de 100 linhas são um perigo à espreita também!

Problema: Alguns desenvolvedores encher demasiado funcionalidade em uma classe / método, sendo preguiçoso demais para quebrar a funcionalidade - Isto leva a baixa coesão e talvez para acoplamento de alta - o inverso de um princípio muito importante em OOP! Solução: Evite usar muito interior / classes aninhadas - essas classes devem ser usados ??somente em uma base por necessidade, você não tem que fazer um hábito de usá-los! Usá-los pode levar a mais problemas como limitar herança. Lookout para duplicar o código! O mesmo ou muito semelhante código já poderia existir em alguns implementação supertipo ou talvez em outra classe. Se for em outra classe que não é um supertipo você também violou a regra de coesão. Cuidado com os métodos estáticos - talvez você precisa de uma classe de utilitário para adicionar!
Mais em: http://centraladvisor.com/it/oop-what -são-os-melhores-práticas-em-oop

Você provavelmente não quer usar a herança para isso. Tente isto em vez disso:

class Person {
    public int PersonId { get; set; }
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string Address { get; set; }
}

class Customer{
    public Person PersonInfo;
    public int CustomerId { get; set; }
    public DateTime JoinedDate { get; set; }
}

class Staff {
    public Person PersonInfo;
    public int StaffId { get; set; }
    public string JobTitle { get; set; }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top