Debería usar una interfaz (no herencia) o algún tipo de mecánica de propiedad (tal vez incluso algo que funcione como el marco de descripción de recursos).
Entonces tampoco
interface Colored {
Color getColor();
}
class ColoredBook extends Book implements Colored {
...
}
o
class PropertiesHolder {
<T> extends Property<?>> Optional<T> getProperty( Class<T> propertyClass ) { ... }
<V, T extends Property<V>> Optional<V> getPropertyValue( Class<T> propertyClass ) { ... }
}
interface Property<T> {
String getName();
T getValue();
}
class ColorProperty implements Property<Color> {
public Color getValue() { ... }
public String getName() { return "color"; }
}
class Book extends PropertiesHolder {
}
Aclaración (editar):
Simplemente agregando campos opcionales en la clase de entidad
Especialmente con el contenedor opcional (editar: ver la respuesta de 4castle ) Creo que esto (agregar campos en la entidad original) es una forma viable de agregar nuevas propiedades a pequeña escala. El mayor problema con este enfoque es que podría funcionar contra el bajo acoplamiento.
Imagine que su clase de libro se define en un proyecto dedicado para su modelo de dominio. Ahora agrega otro proyecto que utiliza el modelo de dominio para realizar una tarea especial. Esta tarea requiere una propiedad adicional en la clase de libro. O terminas con herencia (ver más abajo) o tienes que cambiar el modelo de dominio común para hacer posible la nueva tarea. En el último caso, puede terminar con un montón de proyectos que dependen de sus propias propiedades agregadas a la clase de libro, mientras que la clase de libro en sí depende de estos proyectos, porque no puede comprender la clase de libro sin el proyectos adicionales
¿Por qué la herencia es problemática cuando se trata de proporcionar propiedades adicionales?
Cuando veo su clase de ejemplo "Libro", pienso en un objeto de dominio que a menudo termina teniendo muchos campos y subtipos opcionales. Solo imagine que desea agregar una propiedad para libros que incluyen un CD. Ahora hay cuatro tipos de libros: libros, libros con color, libros con CD, libros con color y CD. No puede describir esta situación con herencia en Java.
Con las interfaces, evita este problema. Puede componer fácilmente las propiedades de una clase de libro específica con interfaces. La delegación y la composición harán que sea fácil obtener exactamente la clase que desea. Con la herencia, generalmente terminas con algunas propiedades opcionales en la clase que solo están allí porque una clase hermana las necesita.
Más información sobre por qué la herencia es a menudo una idea problemática:
¿Por qué los proponentes de la OOP generalmente ven la herencia como algo malo?
JavaWorld: ¿Por qué se extiende es malo?
El problema con la implementación de un conjunto de interfaces para componer un conjunto de propiedades
Cuando usa interfaces para extensión, todo está bien siempre que tenga solo un pequeño conjunto de ellas. Especialmente cuando su modelo de objetos es utilizado y extendido por otros desarrolladores, por ejemplo, en su empresa, la cantidad de interfaces crecerá. Y eventualmente terminas creando una nueva "interfaz de propiedad" oficial que agrega un método que tus colegas ya usaron en su proyecto para el cliente X para un caso de uso completamente no relacionado: Ugh.
editar: otro aspecto importante es mencionado por gnasher729 . A menudo desea agregar dinámicamente propiedades opcionales a un objeto existente. Con herencia o interfaces, tendría que recrear todo el objeto con otra clase que está lejos de ser opcional.
Cuando espere extensiones a su modelo de objetos hasta tal punto, estará mejor modelando explícitamente la posibilidad de una extensión dinámica . Propongo algo como arriba donde cada "extensión" (en este caso propiedad) tiene su propia clase. La clase funciona como espacio de nombres e identificador para la extensión. Cuando configura las convenciones de nomenclatura del paquete de esta manera, permite una cantidad infinita de extensiones sin contaminar el espacio de nombres para los métodos en la clase de entidad original.
En el desarrollo del juego, a menudo te encuentras con situaciones en las que deseas componer el comportamiento y los datos en muchas variaciones. Esta es la razón por la cual el patrón de arquitectura entidad-componente-sistema se hizo bastante popular en los círculos de desarrollo de juegos. Este es también un enfoque interesante que puede considerar, cuando espera muchas extensiones para su modelo de objetos.