Arquitectura correcta/Patrón de diseño

Buenas tardes compañeros de javamexico, tengo la siguiente duda y acudo a ustedes para poder resolverla:

Me encuentro desarrollando un proyecto donde estoy modelando distintos tipos de casas, cada una de ellas cuenta con distintas propiedades y métodos. En este proyecto cada usuario es capaz de exportar estos tipos de casas a distintos formatos (PDF,EXCEL,CSV,etc):

Mi duda es sobre la forma en la que tengo organizada mi arquitectura (quisiera saber qué opinan de ello y si hay una mejor forma de hacerlo). La forma en la que yo resuelvo esto es la siguiente: Tengo una clase abstracta llamada Casa y en ella tengo métodos abstractos que serán sobreescritos por cada tipo de casa para indicar cómo se pintarán en un archivo excel,pdf, csv, etc.

public abstract class Casa {

   properties

   getters/setters

   abstract CSVFormater formatCSV();

   abstract ExcelFormater formatExcel();

   abstract PDFFormater formatPDF();

}

public class CasaCarton {
    CSVFormater formatCSV() {
        return new CasaCartonCSVFormater();
    }

    ExcelFormater formatExcel() {
        return new CasaCartonExcelFormater();
    }

   PDFFormater formatPDF() {
        return new CasaCartonPDFFormater();
    }
}

Me surge la duda si esto es lo correcto o si existe algún patrón de diseño para resolverlo, deseo tener una mejor arquitectura en este proyecto y al mismo tiempo seguir aprendiendo... Será que necesito algún patrón de diseño de tipo factory? o como lo estoy haciendo es correcto?
De antemano, muchas gracias por su tiempo y conocimiento aportados

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de The man

A mi me parece

Yo lo dejaria asi,con un clase abstracta,lo que si agregaria es una clase por cada formato,Ejemplo la clase excel que tenga todos los metodos para exportar a excel para en el metodo llamar a la respectiva clase de cadar formato y exportar,esto por cuestiones de reutilizar codigo

Imagen de ezamudio

completamente distinto

Yo no le pondría nada de eso de formatear a la casa, y por lo tanto no necesita ser abstracta. En vez de eso tendría una interfaz o clase abstracta Exportador y luego implementaciones concretas ExportadorPdf, ExportadorCsv, etc.

En la interfaz defines exportar(Casa casa).

De ese modo cuando quieres exportar a un formato, obtienes el exportador correspondiente. La casa no tiene por qué saber si la van a exportar a un formato, eso es cosa del exportador.

instanceof?

ezamudio:

A eso se refieren cuándo hablan de pojos ? En bean sólo debe conocer sus propiedades, getters y setters? Y la lógica de todo lo que se hará en ese pojo va por aparte? Si es así, cuáles son las ventajas de hacerlo de esa manera ?

Siguiendo lo que me aconseja, quedaría algo así:

public class Casa {}

public class CasaCarton extends Casa {}

public class CasaPlaya extends Casa {}

public interface Exportador {
    void exportar(Casa casa);
}

public class ExportadorPDF implements Exportador {
    void exportar(Casa casa) {...}
}

public class ExportadorExcel implements Exportador {
    void exportar(Casa casa) {...}
}

public class ExportadorCSV implements Exportador {
    void exportar(Casa casa) {...}
}

El problema que a mi me surge es que para exportar cada tipo de casa, necesitaría operadores instanceof, ejemplo:

public class ExportadorPDF implements Exportador {
    void exportar(Casa casa) {
        if (casa instanceof CasaPlaya) {
            // formato pdf casa de playa
        } else if (casa instanceof CasaCarton) {
           // formato pdf casa de cartón
        }
    }
}

Mi pregunta es, cómo puedo evitar utilizar operadores instanceof en un exportador concreto para todos los tipos de casa? El problema está en que cuando agrego una nueva casa, podría olvidar poner la nueva casa en el if y eso me llevaría a no tener por ejemplo el exportador pdf de esa casa.... me gustaría que cuando agregue la nueva casa el código me fuera pidiendo qué métodos debo implementar (esa era la única ventaja de tener una clase abstracta, que cuando extendía de ella, ya sabía qué era todo lo que tenía que implementar y ya no tenía que estar escribiendo sentencias if para cada exportador concreto)

Gracias de antemano por su tiempo, gracias también por tus comentarios #The man

Ventajas de los patrones de diseño

Pues una de las principales ventajas de un patron de diseño es que genera bajo acoplamiento. Como bien dice ezamudio, es mejor que uses interfaces a una implementacion, eso se le conoce como composicion.

Supon que tienes una interface y esa interface define tu generalmente lo que hace: Exportador... ¿Que exporta? un arreglo de bytes por ejemplo. ¿En que formato? eso si aun no lo sabemos

Interface Exportador {
    byte[] performExportar(Informacion informacion);
}

ahora tenemos la clase que implementa:

public class ExportadorArchivos implements Exportador {

    ExportadorBehaivor exportadorBehaivor;

    byte[] performExportar(Informacion informacion) {
        return exportadorBehaivor.exportar(Informacion informacion);
    }
}

Oooops pero ahora tenemos un ExportadorBehaivor. Chispas, es hora de generar otra interface porque podemos exportar a diferentes tipos, CSV, Excel, etc

Ahora lo que vamos a hacer son los comportamientos.

Interface ExportadorBehaivor {
    byte[] exportar(Informacion informacion);
}

y finalmente la implementacion que hará la talacha de exportar a digamos CSV

public class ExportadorCsv implements ExportadorBehaivor {
    public  byte[] exportar(Informacion informacion) {
         System.out...(informacion.getSaludo());
    }
}

Ahora que tenemos nuestra arquitectura ahora si a utilizar (por cierto, le faltan los setter y getters). Mi implementacion para el CSV seria

Exportador exportador = new ExportadorArchivos ();

exportador.setExportadorBehaivor(new ExportadorCsv ());

//enviando el objeto Informacion, que tenga el saludo por ejemplo
exportador.performExportar(informacion);

Ese patron segun recuerdo es Startegy, para desacoplar

Imagen de ezamudio

abstracciones

Tal vez estoy viendo el ejemplo de manera muy simplista, tal vez escogiste una mala analogía... si lo de las casas es real, pues creo que la abstracción que estás haciendo no es la apropiada. CasaCarton no debería ser una subclase de Casa, sino que Casa debería tener una propiedad que indique el material del cual está hecha. Lo mismo va para CasaPlaya; hay ciertos atributos que dicha casa va a tener, cuyos valores serán muy distintos de una casa de ciudad, pero esto que ilustras es como decir que CasaAmarilla y CasaRoja son dos subclases distintas de Casa, cuando en realidad deben ser instancias de una Casa y simplemente cuando les pides su color una te da amarillo y otra te da rojo.

Suponiendo que por alguna otra razón más compleja necesitas realmente que Casa sea abstracta y vas a tener subclases (y esa razón no tiene nada que ver con lo de exportar), entonces otra opción es que Casa tenga UN método abstracto exportar que recibe un Exportador como parámetro. En este caso la interfaz Exportador es bastante más complicada y puede ser digamos así (ejemplo sobresimplificado):

interface Exportador {
  public void exportaTecho(Techo techo);
  public void exportaPiso(Piso piso);
  public void exportaPared(Pared pared);
  public void exportaPuerta(Puerta puerta);
  public void exportaVentana(Ventana ventana);
}

Entonces, cuando creas tu exportador a Excel debes implementar todos esos métodos, igual cuando haces tu exportador a CSV o a PDF etc. Si luego resulta que le agregas algo a la clase Casa, como por ejemplo Garage, entonces le agregas a la interfaz Exportador un método exportaGarage(Garage garage) y el compilador te recordará que debes implementarlo en todas las implementaciones concretas de Exportador.

Del lado de la Casa, puedes incluso implementar el método exportar(Exportador exp) y pasarle a dicho exportador todo lo que sepas de la Casa abstracta; las subclases concretas pueden sobreescribir dicho método, llamando la implementación de la clase abstracta (con super.exportar(exp)). Habrá subclases que ni siquiera necesiten sobreescribir el método y otras tal vez quieran hacer algo distinto y por eso lo sobreescriben y tal vez llaman a super o ni siquiera.

Imagen de bferro

Casa, expórtate! o exportador,exporta la casa!

That is the question y al decidirlo, se decidirá si las casas deben tener un método para exportarse, con ayuda de un exportador o el exportador tendrá métodos para exportar la casa que le ordenan. ¿Cuál solución es mejor? Habria que añadir más cosas que aparecerán en ese contexto para dar la respuesta.
Una aclaración pertinente a algo que comentó java.daba.doo: Programar contra interfaces NO es composición, aunque para usar correctamente la composición programamos contra interfaces. Son dos principios de diseño:
Programming to an interface, not an implementation
Favor composition over inheritance.
El artículo Design Principles from Design Patterns donde se entrevista a Gamma vale la pena.

Gracias por sus comentarios

Gracias por sus comentarios ezamudio y java.daba.doo

ezamudio:

Si, seguramente escogí una mala analogía pues no es un ejemplo real y la intención de explicarlo así no era entrar en detalles de lógica sino hacerlo simple. Pongamos otro ejemplo:

SerVivo (clase base), implementaciones (Perico, Dinosaurio, Hormiga, ...)

Entiendo sus comentarios y los de java.daba.doo, pero sigo teniendo la misma duda (la cual no he sabido explicar):

Tomando este ejemplo de los seres vivos: Supongamos que deseo exportar los detalles de la hormiga, el perico y el dinosaurio (tal vez su historia de cada uno) a pdf,excel y csv, también necesito indicar cómo se va a "clonar" cada uno, también necesito indicar la manera en la que cada uno comerá cierto alimento, y N comportamientos más... Pongo en código mi idea para ser más claro:

public abstract class SerVivo {
    propiedades base;
    getters/setters;
     
    abstract void comer(Alimento);
    abstract SerVivo clonar();
    abstract void exportar(Exportador exp);
    ...
    más comportamientos
}

public class Hormiga extends SerVivo {
    public void exportar(Exportador exp) { // puede ser csv, excel, pdf
                   
    }
    public SerVivo clonar() {return this;}
 
    public void comer(Alimento a) {...}
}

interface Exportador {
    void exporta(outputstream os);
}

public class HormigaExportadorCSV implements Exportador {
    void exporta(outputstream os){}
}

public class HormigaExportadorPDF implements Exportador {
    void exporta(outputstream os){}
}

public class HormigaExportadorExcel implements Exportador {
    void exporta(outputstream os){}
}
...
Y así para todos los seres vivos y todos los formatos
,
luego acá defino cómo va a comer cada animal cierto alimento
,
luego acá defino cómo se va a clonar cada animal.

Tengo las siguientes dudas sobre mi arquitectura:

De acuerdo al código anterior, es correcto tener todos los comportamientos que tendrán los seres vivos en la clase abstracta SerVivo ? No hay algún patrón de diseño donde le diga algo así como FabricaDeComportamientos() ?

Mi segunda pregunta es: Es correcto mezclar esto así? He leido que los pojos deben tener solo las propiedades, setters/getters y de acuerdo a lo que les estoy explicando, yo estoy revolviendo propiedades con comportamientos abstractos (en las clases base regresaré una instancia que realice el comportamiento, por eso les llamo abstractos). Siento que estoy revolviendo propiedades con comportamientos, mi pregunta es si la práctica que estoy utilizando es correcta.

Muchas gracias por su tiempo

Imagen de bferro

¿Es correcto tener todos los comportamientos en la clase base?

Tu pregunta, hobo_75 de si es correcto tener todos los comportamientos en la clase base abstracta es una pregunta frecuente, y la única respuesta correcta es: It depends.
¿De qué depende?, de muchas cosas, inclusive del lenguaje en que vas a implementar la solución, sobre todo en el tipado dinámico o estático.
El asunto tiene que ver con la decisión de utilizar la herencia exclusivamente para especialización, o también para extender el comportamiento expresado en la clase base por las clases derivadas. Un ejemplo muy adecuado para discutir esto es la clase java.io.InputStream, que es una clase abstracta con implementaciones por default para algunos de sus comportamientos.
Puedes echarle un ojo al código fuente y a la documentación de esa clase para discutir sobre las decisiones de diseño que tuvieron en cuenta para decidir una herencia por especialización, o una herencia por extensión, o una mezcla de ellas que es lo que sucede realmente.
Cuando haces uso de la herencia por especialización, incluyes en la clase base todos los comportamientos posibles que tendrán las posibles clase derivadas. Es un gran dilema porque es imposible decidir a priori cuando diseñas un API, las posibles extensiones a ese API mediante la herencia.
Lo que haces es una operación de Unión de todos los comportamientos de las clases derivadas y los incluyes en la clase base, y tendrás entonces que decidir cuales de esos comportamientos tendrán que ser abstractos, y cuales necesitan una implementación por default, para evitar que aquellas clases que no tienen ese comportamiento tengan la necesidad de anularlos (overriding).
Al usar ese tipo de solución te salvas de los castings a las referencias que usas del tipo de la clase base. Por ejemplo, si los que diseñaron la clase InputStream hubieran decidido escribir una implementación por default para el método getFD, tendrían que haber dado una implementación por default, indicando el disparo de una excepción, para que así las clases derivadas de InputStream que no tienen asociado el concepto de un file descriptor no tengan que escribir una implementación, y que la clase FileInputStream sí lo haga pues ella si maneja un file descriptor.
Al hacerlo de esa manera la programación con interfaces se facilita y podemos entonces escribir algo así:

InputStream in = new FileInputStream("foo.obj");
FileDescriptor fd =in.getFD();

Al no incluir ese método, que es como está realmente, entonces tienes que escribir:

InputStream in = new FileInputStream("foo.obj");
FileDescriptor fd =((FileInputStream)in).getFD();

¿Cuál es la mejor opción: herencia por especialización o por extensión? Ambas son útiles y necesarias y su uso depende fuertemente del modelo de objetos del dominio del problema y depende mucho menos de si tienes que usar castings o no.

Re: Casa, expórtate! o exportador,exporta la casa!

Gracias Sr. BFerro
Exacto, ahí se encuentra la respuesta: "Casa, expórtate! o exportador,exporta la casa!" Gracias por su punto de vista en el primer post.

Sobre éste último comentario, yo siempre evito utilizar CAST pero con el ejemplo que me pone del InputStream se me aclara el panorama porque me doy cuenta que tiene más sentido hacer el cast en getFD, pues solo el FileInputStream tiene un descriptor de archivo y no tendría sentido pedir getFD a otro tipo de InputStream.
Con esta gran explicación ahora entiendo cómo hacer mejor mis API's, le agradezco mucho, sin embargo la duda que me queda es la siguiente:

En muchos artículos he leido que la práctica correcta es programar pojos, osea beans planos con sus propiedades, getters/setters. Yo pienso que si introduzco métodos abstractos y la especialización de ellos en clases hijas, estoy rompiendo con aquello de que sean "planos" !
No se si estoy en lo correcto o si exista alguna otra manera de hacer las cosas, por ejemplo yo he visto que en proyectos que utilizan hibernate, sólo se tienen las propiedades anotadas hacia las tablas de la bd a la que mapean, pero no contienen métodos donde definan comportamiento.
Hablando del ejemplo anterior, existe alguna práctica que indique que los objetos de negocio deben ser beans "planos" y para exportarlos se deben crear "exportadores" externos ?

Gracias de antemano a todos por sus valiosos comentarios y aportaciones

Imagen de bferro

¿Qué es un POJO?

Tienes una idea errónea del concepto de POJO. Al rato comento sobre eso, pero por lo pronto un POJO puede tener todos los métodos de negocio que sean necesarios, puede ser abstracto, concreto, o lo que prefieras. En esencia un POJO es un objeto regular de Java, "lo más alejado posible" del marco de trabajo, del contenedor que lo hospeda, etc., etc.,

Re: ¿Qué es un POJO?

Gracias por su gran ayuda, espero el comentario para profundizar

Imagen de ezamudio

POJO

Plain Old Java Object - un objeto de Java que no tiene dependencias con un framework en particular. Por ejemplo no tiene que heredar de alguna clase de EJB o implementar alguna interfaz particular de un framework o llenarlo de anotaciones etc.

Imagen de beto.bateria

Aunque no se habla mucho de

Aunque no se habla mucho de los requerimientos, y con lo que mencionas, utilizaria el patron strategy, y asi quedaria:

public class Casa {
    //Todos los metodos que quieras
   
    OutputStream exportar(Exportador exportador ){
         return exportador.exportar(this);
    }
}

public class ExportadorPDF implements Exportador {
    OutputStream exportar(Casa casa) {...}
}

public class ExportadorExcel implements Exportador {
    OutputStream exportar(Casa casa) {...}
}

public class ExportadorCSV implements Exportador {
    OutputStream exportar(Casa casa) {...}
}

Le puse que regresa la clase OutputStream debido a que vas a manejar archivos, pero tal ves estoy equivocado, podria regresar un Object. Agregando, no puedes hacer una clase de casa de carton, u otra de madera, eso se debe de especificar en uno de los atributos (material), tal vez si debas hacer una clase edificio, bodega, o departamento.

Considera el patron strategy, el lenguaje java lo utiliza mucho, y yo tambien, es muy... ¿adaptable?, ¿sencillo?, no tengo la palabra exacta, pero ayuda mucho.