Un defecto conocido de las jerarquías de clase tradicionales es que son malas a la hora de modelar el mundo real. Como ejemplo, tratar de representar especies de animales con clases. En realidad, hay varios problemas al hacer eso, pero uno para el que nunca vi una solución es cuando una subclase "pierde" un comportamiento o propiedad que se definió en una superclase, como un pingüino que no puede volar (hay son probablemente mejores ejemplos, pero ese es el primero que me viene a la mente).
Por un lado, no desea definir, para cada propiedad y comportamiento, algún indicador que especifique si está presente, y verificarlo cada vez antes de acceder a ese comportamiento o propiedad. Solo quisiera decir que los pájaros pueden volar, simple y claramente, en la clase de Aves. Pero entonces sería bueno si se pudieran definir "excepciones" después, sin tener que usar algunos trucos horribles en todas partes. Esto sucede a menudo cuando un sistema ha sido productivo por un tiempo. De repente, encuentra una "excepción" que no cabe en absoluto en el diseño original, y no desea cambiar una gran parte de su código para acomodarlo.
Entonces, ¿hay algún lenguaje o patrón de diseño que pueda manejar este problema de manera limpia, sin requerir cambios importantes en la "superclase" y todo el código que lo utiliza? Incluso si una solución solo maneja un caso específico, varias soluciones juntas podrían formar una estrategia completa.
Después de pensar más, me doy cuenta de que olvidé el Principio de sustitución de Liskov. Por eso no puedes hacerlo. Suponiendo que defina "rasgos / interfaces" para todos los "grupos de características" principales, puede implementar libremente rasgos en diferentes ramas de la jerarquía, como el rasgo Volador podría ser implementado por Birds y algún tipo especial de ardillas y peces.
Entonces, mi pregunta podría equivaler a "¿Cómo podría desinstalar un rasgo?" Si su superclase es Java Serializable, debe ser uno también, incluso si no hay forma de serializar su estado, por ejemplo, si contiene un "Socket".
Una forma de hacerlo es definir siempre todos sus rasgos en pares desde el principio: Flying and NotFlying (que arrojaría UnsupportedOperationException, si no se compara). Not-trait no definiría ninguna interfaz nueva, y simplemente podría verificarse. Suena como una solución "barata", en particular si se usa desde el principio.
" it would be nice if one could define "exceptions" afterward, without having to use some horrible hacks everywhere"
¿consideras un método de fábrica que controla el comportamiento hacky?
NotSupportedException
de Penguin.fly()
.
class Penguin < Bird; undef fly; end;
. Si deberías, es otra pregunta.
function save_yourself_from_crashing_airplane(Bird b) { f.fly() }
sería mucho más complicado. (como dijo Peter Török, viola el LSP)