superclase metodos ejemplo clases java oop inheritance anti-patterns

metodos - superclase java



Evitar jerarquías de herencia paralelas (6)

¿Por qué no hacer de IXMLFormatter una interfaz con los métodos toXML (), toSoap (), YAML () y hacer que el vehículo, el automóvil y el camión implementen todo eso? ¿Qué está mal con ese enfoque?

Tengo dos cadenas de herencia paralelas:

Vehicle <- Car <- Truck <- etc. VehicleXMLFormatter <- CarXMLFormatter <- TruckXMLFormatter <- etc.

Mi experiencia ha sido que las jerarquías de herencia paralelas pueden convertirse en un dolor de cabeza de mantenimiento a medida que crecen.

es decir, NO agregando toXML(), toSoap(), toYAML() a mis clases principales.

¿Cómo evito una jerarquía de herencia paralela sin romper el concepto de separación de preocupaciones?


Estoy pensando en usar el patrón Visitor.

public class Car : Vehicle { public void Accept( IVehicleFormatter v ) { v.Visit (this); } } public class Truck : Vehicle { public void Accept( IVehicleFormatter v ) { v.Visit (this); } } public interface IVehicleFormatter { public void Visit( Car c ); public void Visit( Truck t ); } public class VehicleXmlFormatter : IVehicleFormatter { } public class VehicleSoapFormatter : IVehicleFormatter { }

Con esto, evita un árbol de herencia adicional y mantiene la lógica de formateo separada de sus clases de vehículo. Por supuesto, cuando creas un vehículo nuevo, tendrás que agregar otro método a la interfaz del formateador (e implementar este nuevo método en todas las implementaciones de la interfaz del formateador).
Pero, creo que esto es mejor que crear una nueva clase de Vehículo, y por cada IVehicleFormatter que tengas, crea una nueva clase que pueda manejar este nuevo tipo de vehículo.


Otro enfoque es adoptar un modelo push en lugar de un modelo pull. Normalmente necesita diferentes formateadores porque está rompiendo la encapsulación y tiene algo como:

class TruckXMLFormatter implements VehicleXMLFormatter { public void format (XMLStream xml, Vehicle vehicle) { Truck truck = (Truck)vehicle; xml.beginElement("truck", NS). attribute("name", truck.getName()). attribute("cost", truck.getCost()). endElement(); ...

donde está extrayendo datos del tipo específico en el formateador.

En su lugar, cree un receptor de datos independiente del formato e invierta el flujo para que el tipo específico empuje los datos al receptor

class Truck implements Vehicle { public DataSink inspect ( DataSink out ) { if ( out.begin("truck", this) ) { // begin returns boolean to let the sink ignore this object // allowing for cyclic graphs. out.property("name", name). property("cost", cost). end(this); } return out; } ...

Eso significa que aún tiene los datos encapsulados y solo está alimentando datos etiquetados al receptor. Un sumidero de XML podría ignorar ciertas partes de los datos, quizás reordenar parte de él y escribir el XML. Incluso podría delegar en una estrategia de receptor diferente internamente. Pero el receptor no necesariamente tiene que preocuparse por el tipo de vehículo, solo cómo representar los datos en algún formato. Usar identificadores globales internados en lugar de cadenas en línea ayuda a mantener bajos los costos de cómputo (solo importa si está escribiendo ASN.1 u otros formatos ajustados).


Podría tratar de evitar la herencia de sus formateadores. Simplemente haga un VehicleXmlFormatter que pueda tratar con Car s, Truck s, ... La reutilización debe ser fácil de lograr cortando las responsabilidades entre los métodos y descubriendo una buena estrategia de despacho. Evita sobrecargar la magia; sea ​​tan específico como sea posible en los métodos de denominación en su formateador (por ejemplo, formatTruck(Truck ...) lugar de format(Truck ...) ).

Solo use Visitor si necesita el doble despacho: cuando tenga objetos de tipo Vehicle y quiera formatearlos en XML sin conocer el tipo de concreto real. El visitante en sí mismo no resuelve el problema básico de lograr la reutilización en su formateador, y puede introducir una complejidad adicional que tal vez no necesite. Las reglas anteriores para la reutilización por métodos (recorte y despacho) también se aplicarían a su implementación de Visitante.


Puedes usar Bridge_pattern

El patrón de puente desacopla una abstracción de su implementación para que ambos puedan variar de forma independiente .

Dos jerarquías de clases ortogonales (jerarquía de abstracción y jerarquía de implementación ) se vinculan usando composición (y no herencia). Esta composición ayuda a ambas jerarquías a variar de forma independiente.

La implementación nunca se refiere a la abstracción . Abstracción contiene la interfaz de Implementación como miembro (a través de la composición).

Volviendo a su ejemplo:

Vehicle es abstracción

Car y el Truck son refinadosAbstracción

Formatter es implementador

XMLFormatter , POJOFormatter son ConcreteImplementor

Pseudo código:

Formatter formatter = new XMLFormatter(); Vehicle vehicle = new Car(formatter); vehicle.applyFormat(); formatter = new XMLFormatter(); vehicle = new Truck(formatter); vehicle.applyFormat(); formatter = new POJOFormatter(); vehicle = new Truck(formatter); vehicle.applyFormat();

Publicación relacionada:

¿Cuándo usas el patrón de puente? ¿Cómo es diferente del patrón del Adaptador?


Quiero agregar genéricos a la respuesta de Frederiks.

public class Car extends Vehicle { public void Accept( VehicleFormatter v ) { v.Visit (this); } } public class Truck extends Vehicle { public void Accept( VehicleFormatter v ) { v.Visit (this); } } public interface VehicleFormatter<T extends Vehicle> { public void Visit( T v ); } public class CarXmlFormatter implements VehicleFormatter<Car> { //TODO: implementation } public class TruckXmlFormatter implements VehicleFormatter<Truck> { //TODO: implementation }