Para classes com campos opcionais, é melhor usar herança ou uma propriedade anulável? Considere este exemplo:
class Book {
private String name;
}
class BookWithColor extends Book {
private String color;
}
ou
class Book {
private String name;
private String color;
//when this is null then it is "Book" otherwise "BookWithColor"
}
ou
class Book {
private String name;
private Optional<String> color;
//when isPresent() is false it is "Book" otherwise "BookWithColor"
}
O código, dependendo dessas três opções, seria:
if (book instanceof BookWithColor) { ((BookWithColor)book).getColor(); }
ou
if (book.getColor() != null) { book.getColor(); }
ou
if (book.getColor().isPresent()) { book.getColor(); }
A primeira abordagem me parece mais natural, mas talvez seja menos legível por causa da necessidade de fazer o casting. Existe alguma outra maneira de alcançar esse comportamento?
java
inheritance
class
null
Bojan VukasovicTest
fonte
fonte
Respostas:
Isso depende das circunstâncias. O exemplo específico não é realista, pois você não teria uma subclasse chamada
BookWithColor
em um programa real.Mas, em geral, uma propriedade que só faz sentido para determinadas subclasses deve existir apenas nessas subclasses.
Por exemplo, se
Book
temPhysicalBook
eDigialBook
como descendentes,PhysicalBook
pode ter umaweight
propriedade eDigitalBook
umasizeInKb
propriedade. MasDigitalBook
não teráweight
e vice-versa.Book
não terá nenhuma propriedade, pois uma classe deve ter apenas propriedades compartilhadas por todos os descendentes.Um exemplo melhor é olhar para as classes de software real. O
JSlider
componente tem o campomajorTickSpacing
. Como apenas um controle deslizante tem "ticks", esse campo só faz sentido paraJSlider
e seus descendentes. Seria muito confuso se outros componentes irmãosJButton
tivessem ummajorTickSpacing
campo.fonte
Um ponto importante que parece não ter sido mencionado: Na maioria dos idiomas, as instâncias de uma classe não podem alterar de qual classe elas são. Portanto, se você tivesse um livro sem uma cor e desejasse adicionar uma cor, seria necessário criar um novo objeto se estiver usando classes diferentes. E então você provavelmente precisará substituir todas as referências ao objeto antigo por referências ao novo objeto.
Se "livros sem cor" e "livros com cor" são instâncias da mesma classe, adicionar ou remover a cor será muito menos problemático. (Se a interface do usuário mostrar uma lista de "livros com cores" e "livros sem cores", a interface do usuário precisará ser alterada obviamente, mas seria de esperar que você precise lidar com isso de qualquer maneira, semelhante a uma "lista de cores vermelhas" livros "e" lista de livros verdes ").
fonte
Pense em um JavaBean (como o seu
Book
) como um registro em um banco de dados. As colunas opcionais sãonull
quando não têm valor, mas é perfeitamente legal. Portanto, sua segunda opção:É o mais razoável. 1
Tenha cuidado com o modo como você usa a
Optional
classe. Por exemplo, não éSerializable
, o que geralmente é uma característica de um JavaBean. Aqui estão algumas dicas de Stephen Colebourne :Portanto, dentro da sua classe, você deve usar
null
para representar que o campo não está presente, mas quandocolor
deixar oBook
(como um tipo de retorno), ele deve ser envolvido com umOptional
.Isso fornece um design claro e um código mais legível.
1 Se você pretende para um
BookWithColor
para encapsular uma "classe" inteira de livros que se especializaram capacidades sobre outros livros, então não faria sentido a herança uso.fonte
Optional
classe ao Java para esse fim exato. Pode não ser OO, mas certamente é uma opinião válida.Você deve usar uma interface (não herança) ou algum tipo de mecânico de propriedade (talvez até algo que funcione como a estrutura de descrição de recursos).
Então também
ou
Esclarecimento (editar):
Simplesmente adicionando campos opcionais na classe de entidade
Especialmente com o Wrapper opcional(editar: veja a resposta do 4castle ) Eu acho que isso (adicionar campos na entidade original) é uma maneira viável de adicionar novas propriedades em pequena escala. O maior problema com essa abordagem é que ela pode funcionar contra baixo acoplamento.Imagine que sua classe de livros é definida em um projeto dedicado ao seu modelo de domínio. Agora você adiciona outro projeto que usa o modelo de domínio para executar uma tarefa especial. Esta tarefa requer uma propriedade adicional na classe book. Você acaba com herança (veja abaixo) ou precisa alterar o modelo de domínio comum para tornar possível a nova tarefa. Nesse último caso, você pode acabar com vários projetos que dependem de suas próprias propriedades adicionadas à classe book, enquanto a própria classe book de certa forma depende desses projetos, porque você não é capaz de entender a classe book sem o projetos adicionais.
Por que a herança é problemática quando se trata de uma maneira de fornecer propriedades adicionais?
Quando vejo sua classe de exemplo "Livro", penso em um objeto de domínio que muitas vezes acaba tendo muitos campos e subtipos opcionais. Imagine que você deseja adicionar uma propriedade para livros que incluem um CD. Existem agora quatro tipos de livros: livros, livros com cores, livros com CD, livros com cores e CD. Você não pode descrever essa situação com herança em Java.
Com interfaces, você contorna esse problema. Você pode facilmente compor as propriedades de uma classe de livro específica com interfaces. A delegação e a composição facilitarão a obtenção da turma desejada. Com a herança, você geralmente acaba com algumas propriedades opcionais na classe que estão lá apenas porque uma classe irmã precisa delas.
Leitura adicional por que a herança geralmente é uma ideia problemática:
Por que a herança geralmente é vista como algo ruim pelos proponentes da OOP
JavaWorld: Por que estender é mau
O problema com a implementação de um conjunto de interfaces para compor um conjunto de propriedades
Quando você usa interfaces para extensão, tudo fica bem, desde que você tenha apenas um pequeno conjunto delas. Especialmente quando seu modelo de objeto é usado e estendido por outros desenvolvedores, por exemplo, em sua empresa, a quantidade de interfaces aumentará. E, finalmente, você acaba criando uma nova "interface de propriedade" oficial que adiciona um método que seus colegas já usaram em seu projeto para o cliente X para um caso de uso completamente não relacionado - Ugh.
editar: Outro aspecto importante é mencionado por gnasher729 . Você geralmente deseja adicionar dinamicamente propriedades opcionais a um objeto existente. Com herança ou interfaces, você teria que recriar o objeto inteiro com outra classe que está longe de ser opcional.
Quando você espera extensões para o seu modelo de objeto a tal ponto, será melhor modelar explicitamente a possibilidade de extensão dinâmica . Eu proponho algo como acima, onde cada "extensão" (neste caso, propriedade) tem sua própria classe. A classe funciona como espaço para nome e identificador para a extensão. Quando você define as convenções de nomenclatura de pacotes dessa maneira, permite uma quantidade infinita de extensões sem poluir o espaço para nome dos métodos na classe de entidade original.
No desenvolvimento de jogos, você geralmente encontra situações em que deseja compor comportamentos e dados em diversas variações. É por isso que o padrão de arquitetura entidade-componente-sistema ficou bastante popular nos círculos de desenvolvimento de jogos. Essa também é uma abordagem interessante que você pode querer observar quando espera muitas extensões para o seu modelo de objeto.
fonte
PropertiesHolder
provavelmente seria uma classe abstrata. Mas o que você sugere ser uma implementaçãogetProperty
ougetPropertyValue
? Os dados ainda precisam ser armazenados em algum tipo de campo de instância em algum lugar. Ou você usaria umCollection<Property>
para armazenar as propriedades em vez de usar campos?Book
extensãoPropertiesHolder
por razões de clareza. APropertiesHolder
regra geral deve ser um membro em todas as classes que precisam dessa funcionalidade. A implementação conteriaMap<Class<? extends Property<?>>,Property<?>>
algo assim. Essa é a parte mais feia da implementação, mas fornece uma estrutura muito boa que até funciona com JAXB e JPA.Se
Book
classe é derivável sem propriedade de cor como abaixoentão a herança é razoável.
fonte
color
for privado, qualquer classe derivada não deverá se preocupar com issocolor
. Basta adicionar alguns construtores paraBook
que ignoremcolor
completamente e o padrão seránull
.