¿Qué diseño OO usar (hay un patrón de diseño)?

11

Tengo dos objetos que representan un 'Bar / Club' (un lugar donde bebes / socializas).

En un escenario, necesito el nombre de la barra, la dirección, la distancia, el eslogan

En otro escenario, necesito el nombre de la barra, la dirección, la URL del sitio web, el logotipo

Entonces tengo dos objetos que representan la misma cosa pero con diferentes campos.

Me gusta usar objetos inmutables, por lo que todos los campos se establecen desde el constructor .

Una opción es tener dos constructores y anular los otros campos, es decir:

class Bar {
     private final String name;
     private final Distance distance;
     private final Url url;

     public Bar(String name, Distance distance){
          this.name = name;
          this.distance = distance;
          this.url = null;
     }

     public Bar(String name, Url url){
          this.name = name;
          this.distance = null;
          this.url = url;
     }

     // getters
}

No me gusta esto, ya que tendrías que verificar nulo cuando utilizas los captadores

En mi ejemplo real, el primer escenario tiene 3 campos y el segundo escenario tiene aproximadamente 10, por lo que sería un verdadero problema tener dos constructores , la cantidad de campos que tendría que declarar nulo y luego, cuando el objeto esté en uso, no No sé cuál estaba Barusando y qué campos serían nulos y cuáles no.

¿Qué otras opciones tengo?

Dos clases llamadas BarPreviewy Bar?

¿Algún tipo de herencia / interfaz?

¿Algo más que es asombroso?

Blundell
fuente
29
¡Guau, en realidad se te ocurrió un uso legítimo Barcomo identificador!
Mason Wheeler
1
Si está compartiendo algunas propiedades, una opción sería implementar una clase base.
Yusubov
1
Nunca pensé en eso. Escribir cualquier tipo de código para mi salón de perros Bar / Foo podría ser realmente confuso.
Erik Reppen
44
@gnat ¿Cómo adivinan las personas? De su cita de enlace: You should only ask practical, answerable questions based on actual problems that you face.y eso es exactamente lo que está sucediendo aquí
Blundell

Respuestas:

9

Mis pensamientos:

Una "Barra", como se representa en su dominio, tiene todas las cosas que pueden ser necesarias en cualquier lugar: nombre, dirección, URL, logotipo, eslogan y "distancia" (supongo que desde la ubicación del solicitante). Por lo tanto, en su dominio, debe haber una clase de "Barra" que sea la fuente autorizada de datos para una barra, sin importar dónde se utilizarán los datos más adelante. Esta clase debe ser mutable, de modo que los cambios en los datos de la barra se puedan realizar y guardar cuando sea necesario.

Sin embargo, tiene dos lugares en los que se necesitan los datos de este objeto Bar, y ambos solo necesitan un subconjunto (y no desea que se modifiquen esos datos). La respuesta habitual es un "objeto de transferencia de datos" o DTO; un POJO (objeto simple de Java) que contiene los captadores de propiedades inmutables. Estos DTO se pueden generar llamando a un método en el objeto de dominio de la barra principal: "toScenario1DTO ()" y "toScenario2DTO ()"; los resultados son un DTO hidratado (lo que significa que solo necesita usar el constructor largo y complicado en un solo lugar).

Si alguna vez necesita enviar datos a la clase de dominio principal (para actualizarlos; ¿cuál es el punto de datos si no puede cambiarlos según sea necesario para reflejar el estado actual del mundo real?), Podría construir uno de los DTO, o use un nuevo DTO mutable, y devuélvalo a la clase Bar usando un método "updateFromDto ()".

EDITAR: para proporcionar un ejemplo:

public class Bar {
     private String name;
     private Address address; 
     private Distance distance;
     private Url url;
     private Image logo;
     private string Slogan;

     public OnlineBarDto ToOnlineDto()
     {
         return new OnlineBarDto(name, address, url, logo);
     }

     public PhysicalBarDto ToPhysicalDto()
     {
         return new PhysicalBarDto(name, address, distance, slogan);
     }

     public void UpdateFromDto(PhysicalBarDto dto)
     {
         //validation logic here, or mixed into assignments

         name = dto.Name;
         address = dto.Address;
         distance = dto.Distance;
         slogan = dto.Slogan;
     }

     public void UpdateFromDto(OnlineBarDto dto)
     {
         //Validate DTO fields before performing assignments

         name = dto.Name;
         address = dto.Address;
         url= dto.Url;
         logo = dto.Logo;
     }

     // getters/setters - As necessary within the model and data access layers;
     // other classes can update the model using DTOs, forcing validation.
}

public class PhysicalBarDto
{
     public final String Name;
     public final Address Address;
     public final Distance Distance;
     public final String Slogan;

     public PhysicalBarDto(string Name, Address address, Distance distance, string slogan) 
     { //set instance fields using parameter fields; you know the drill }
}

public class OnlineBarDto
{
     public final String Name;
     public final Address Address;
     public final Image Logo;
     public final Url Url;

     public OnlineBarDto(string Name, Address address, Url url, Image logo) 
     { //ditto }
}

Las clases Address, Distance y Url deberían ser inmutables o, cuando se usan en los DTO, deberían reemplazarse por sus homólogos inmutables.

KeithS
fuente
¿Qué significa el acrónimo DTO? No entiendo bien lo que dices, ¿podrías ponerlo en un ejemplo trabajado, por favor? fyi Los datos provienen de un servidor, por lo que una vez que cualquier forma de esta clase esté 'hidratada', los campos no tendrán que cambiarse, solo se usarán para mostrar en la interfaz de usuario
Blundell
1
DTO significa "objeto de transferencia de datos", y se refiere a una clase de datos de estructura muy simple utilizada para mover datos de la capa de dominio "rico" a capas superiores como la interfaz de usuario, sin exponer la capa de dominio real a la interfaz de usuario (permitiendo cambios para hacerse al dominio sin afectar la interfaz de usuario siempre que el DTO no tenga que cambiar).
KeithS
la mutabilidad no tiene relación alguna con la modificación o la persistencia.
44
@JarrodRoberson, ¿estás bromeando? Si una clase es inmutable (no se puede cambiar en el lugar después de la creación de instancias), la única forma de hacer un cambio en los datos en la capa de datos es construir una nueva instancia que represente el mismo registro (mismo PK) con diferentes miembros. Si bien los métodos de "mutación" que producen una nueva instancia pueden facilitarlo, todavía tiene una gran influencia en la modificación y la persistencia.
KeithS
1
@JarrodRoberson Escucha a la comunidad. Está usted equivocado. . De hecho, la mitad de los comentarios en toda esta respuesta muestran que necesitamos un poco de educación básica de OO en todo el tablero - es asqueroso ..
David Cowden
5

Si solo le importa un subconjunto de propiedades y desea asegurarse de que no se mezclen, cree dos interfaces y úselas para hablar con su objeto base.

jmoreno
fuente
1
Dices eso, pero ¿podrías dar un ejemplo usando la Barclase
Blundell
3

El patrón de construcción (o algo parecido) podría ser útil aquí.

Tener objetos inmutables es algo admirable, pero la realidad es que con Reflection in Java, nada es realmente seguro ;-).

Martijn Verburg
fuente
Puedo ver cómo HawaiianPizzaBuilderfunciona porque los valores que necesita están codificados. Sin embargo, ¿cómo puede usar este patrón si los valores se recuperan y se pasan a un constructor? El HawaiianPizzaBuildertodavía tendría todos los captadores que SpicyPizzaBuilderes tan nulo posible. A menos que combine esto con @Jarrods Null Object Pattern. Un ejemplo de código con lo Barharía entender
Blundell el
+1 Uso el generador en casos como este, funciona de maravilla, incluido, entre otros, establecer valores predeterminados razonables en lugar de nulo cuando quiero esto
mosquito
3

El punto clave aquí es la diferencia entre qué es una "Barra" y cómo se usa en uno u otro contexto.

The Bar es una entidad única en el mundo real (o un mundo artificial, como un juego), y solo UNA instancia de objeto debería representarlo. En cualquier momento posterior, cuando no cree esa instancia desde un segmento de código, pero la cargue desde un archivo de configuración o una base de datos, esto será más evidente.

(Para ser aún más esotérico: cada instancia de Bar tiene un ciclo de vida diferente que el objeto que lo representa cuando se ejecuta su programa. Incluso si tiene un código fuente que crea esa instancia, significa que la entidad de Bar como se describe, "existe "en un estado latente en su código fuente, y" despertar "cuando ese código realmente lo crea en la memoria ...)

Perdón por el largo comienzo, pero espero que esto aclare mi punto. Tiene UNA clase de Bar que tiene todos los atributos que necesitaría y una instancia de Bar que representa cada entidad de Bar. Esto es correcto en su código e independiente de cómo desea ver la misma instancia en diferentes contextos .

Este último puede estar representado por dos interfaces diferentes , que contienen los métodos de acceso requeridos (getName (), getURL (), getDistance ()), y la clase Bar debe implementar ambos. (Y tal vez la "distancia" cambiará a "ubicación", y getDistance () se convierte en un cálculo desde otra ubicación :-))

Pero la creación es para la entidad Bar y no para la forma en que desea utilizar esa entidad: un constructor, todos los campos.

EDITADO: ¡Puedo escribir código! :-)

public interface Place {
  String getName();
  Address getAddress();
}

public interface WebPlace extends Place {
   URL getUrl();
   Image getLogo();
}

public interface PhysicalPlace extends Place {
  Double getDistance();
  Slogon getSlogon();
}

public class Bar implements WebPlace, PhysicalPlace {
  private final String name;
  private final Address address;
  private final URL url;
  private final Image logo;
  private final Double distance;
  private final Slogon slogon;

  public Bar(String name, Address address, URL url, Image logo, Double distance, Slogon slogon) {
    this.name = name;
    this.address = address;
    this.url = url;
    this.logo = logo;
    this.distance = distance;
    this.slogon = slogon;
  }

  public String getName() { return name; }
  public Address getAddress() { return address; }
  public Double getDistance() { return distance; }
  public Slogon getSlogon() { return slogon; }
  public URL getUrl() { return url; }
  public Image getLogo() { return logo; } 
}
Lorand Kedves
fuente
1

Patrón apropiado

Lo que estás buscando se conoce comúnmente como el Null Object Pattern. Si no le gusta el nombre, puede llamarlo, la Undefined Value Patternmisma semántica etiqueta diferente. A veces se llama este patrón Poison Pill Pattern.

En todos estos casos, el Objeto es un reemplazo o un sustituto Default Valuede null. It doesn't replace the semantic ofnulo but makes it easier to work with the data model in a more predictable way becausenulo` ahora nunca debería ser un estado válido.

Es un patrón en el que reserva una instancia especial de una clase determinada para representar una nullopción como a Default Value. De esta manera, no tiene que verificar null, puede verificar la identidad con su NullObjectinstancia conocida . Puede invocar métodos seguros y similares sin preocuparse NullPointerExceptions.

De esta manera, reemplaza sus nulltareas con sus NullObjectinstancias representativas y listo.

Análisis orientado a objetos adecuado

De esta manera, puede tener un común Interfacepara el polimorfismo y aún así tener la protección de tener que preocuparse por la ausencia de datos en las implementaciones específicas de la interfaz. Por lo tanto, algunos Barpueden no tener presencia en la web y otros pueden no tener datos de ubicación en el momento de la construcción. Null Object Patterle permite proporcionar un valor predeterminado para cada uno de estos markerdatos que dice lo mismo, no se ha proporcionado nada aquí, sin tener que lidiar con la verificación de NullPointerExceptiontodo el lugar.

Diseño orientado a objetos adecuado

Primero tenga una abstractimplementación que sea un súper conjunto de todos los atributos que comparten Bary Clubcomparten.

class abstract Establishment 
{
     private final String name;
     private final Distance distance;
     private final Url url;

     public Bar(final String name, final Distance distance, final Url url)
     {
          this.name = name;
          this.distance = distance;
          this.url = url;
     }

     public Bar(final String name, final Distance distance)
     {
          this(name, distance, Url.UNKOWN_VALUE);
     }

     public Bar(final String name, final Url url)
     {
          this(name, Distance.UNKNOWN_VALUE, url);
     }

     // other code
}

Luego puede implementar subclases de esta Establishmentclase y agregar solo las cosas específicas que necesita para cada una de las clases Bary Clubque no se aplican a la otra.

Persistencia

Estos objetos de marcador de posición si se construyen correctamente se pueden almacenar de forma transparente en una base de datos sin ningún manejo especial también.

Prueba del futuro

Si decidiste subirte al carro de la Inversión de Control / Inyección de Dependencia más adelante, este patrón hace que también sea fácil inyectar estos objetos marcadores.


fuente
0

Creo que el problema es que no estás modelando una barra en ninguno de esos escenarios (y estás modelando dos problemas diferentes, objetos, etc.). Si veo una barra de clase, esperaría alguna funcionalidad relacionada con las bebidas, los menús, los asientos disponibles y su objeto no tiene nada de eso. Si veo el comportamiento de tus objetos, estás modelando información sobre un establecimiento. Bar es para lo que los estás usando en este momento, pero no es el comportamiento intrínseco que están implementando. (En otro contexto: si está modelando un matrimonio, tendrá dos variables de instancia Persona esposa; Persona esposo; una esposa es el rol actual que le está dando a ese objeto en ese momento, pero el objeto sigue siendo una Persona). Haría algo como esto:

class EstablishmentInformation {
     private final String name;

     public EstablishmentInformation(String name){
          this.name = name;
     }

     // getters
}

class EstablishmentLocationInformation {
    EstablishmentInformation establishmentInformation;
     private final Distance distance;

     public EstablishmentLocationInformation (String name, Distance distance){
          this.establishmentInformation = new EstablishmentInformation(name)
          this.distance = distance;
     }
}

class EstablishmentWebSiteInformation {
    EstablishmentInformation establishmentInformation;
     private final Url url;

     public EstablishmentWebSiteInformation(String name, Url url){
          this.establishmentInformation = new EstablishmentInformation(name)
          this.url = url;
     }
}
usuario1494736
fuente
-1

Realmente no hay necesidad de complicar demasiado esto. ¿Necesitas dos tipos diferentes de objeto? Haz dos clases.

class OnlineBar {
     private final String name;
     private final Url url;
     public OnlineBar(String name, Url url){
          this.name = name;
          this.url = url;
     }

     // ...
}
class PhysicalBar {
     private final String name;
     private final Distance distance;
     public PhysicalBar(String name, Distance distance){
          this.name = name;
          this.distance = distance;
     }
     //...
}

Si necesita operar con ellos por igual, considere agregar una interfaz o usar la reflexión.

DeadMG
fuente
@David: Oh, no. Tienen como, un miembro de datos completo en común. ¡EMERGENCIA!
DeadMG
sin una interfaz común, no hay polimorfismo en esta solución, ninguna de estas clases podría sustituirse por la otra con esta mala decisión de diseño. No es que no tengan el mismo superconjunto de atributos, es que por defecto algunos de esos atributos sí lo están null. Recordar nullsignifica la ausencia de datos , no la ausencia del atributo.
1
@DeadMG Este es posiblemente un ejercicio sobre la idea exacta de agrupar valores compartidos en objetos primarios ... su solución no obtendría crédito completo si la propusiera en ese contexto.
David Cowden
El OP no especifica ninguna necesidad de sustituibilidad. Y como dije, solo puede agregar una interfaz o usar la reflexión si lo desea.
DeadMG
@DeadMG Pero la reflexión es como tratar de escribir una aplicación móvil multiplataforma utilizando un entorno: puede funcionar, pero no es correcto . La penalización por llamar a un método usando reflexión es entre 2 y 50 veces más lenta que una llamada a método normal. La reflexión no es una panacea ...
David Cowden
-1

Mi respuesta para cualquiera con este tipo de problema es dividirlo en pasos manejables .

  1. Primero solo crea dos clases BarOney BarTwo(o llámalos a ambos Barpero en paquetes diferentes)
  2. Comience a usar sus objetos como clases separadas, no se preocupe por la duplicación de código por ahora. Notará cuando se cruza (métodos duplicados) de uno a otro
  3. Puede descubrir que no están relacionados de ninguna manera, por lo que debe preguntarse si ambos realmentebar cambian el nombre de la clase infractora a lo que ahora representa
  4. Si encuentra campos o comportamientos comunes, puede extraer uno interfaceo superclasscon el comportamiento común
  5. Una vez que tenga un interfaceo superclasspodría crear un buildero factorypara crear / recuperar sus objetos de implementación

(4 y 5 son de lo que tratan las otras respuestas a esta pregunta)

Blundell
fuente
-2

Necesita una clase base, por ejemplo, Ubicación que tenga un nombre y una dirección . Ahora, tiene dos clases, Bar y BarPreview, extienden la ubicación de la clase base . En cada clase, inicializa las variables comunes de superclase y luego sus variables únicas:

public class Location {
    protected final String name;
    protected final String address:

    public Location (String locName, String locAddress) {
    name = locName;
    address = locAddress
    }

}

public class Bar extends Location {
    private final int dist;
    private final String slogan;

    public Bar(String barName, String barAddress,
               int distance, String barSlogan) {
    super(locName, locAddress);
    dist = distance;
    slogan = barSlogan;
    }
}

Y similar para la clase BarPreview.

Si lo hace dormir mejor por la noche, reemplace todas las instancias de Ubicación en mi código con AnythingYouThinkWouldBeAnAppropriateNameForAThingThatABarExtendsSuchAsFoodServicePlace - ffs.

David Cowden
fuente
el OP quiere que la instancia sea inmutable , eso significa que todo tiene que serlo final.
1
Esto podría funcionar, necesita finallos campos @David. aunque Barno debería extend Locationtener sentido. Tal vez Bar extends BaseBary BarPreview extends BaseBarestos nombres tampoco suenan demasiado bien, esperaba algo más elegante
Blundell
@JarrodRoberson Solo lo estoy dibujando para él ... solo agregue el final para que sea inmutable. Es obvio. El problema fundamental con la pregunta del OP es que no sabe cómo tener una clase base y dos clases separadas que extiendan una clase base. Simplemente estoy detallando eso.
David Cowden
@Blundell, ¿de qué demonios estás hablando? Un bar es una ubicación . Simplemente reemplace mi ubicación con su BaseBar y es exactamente lo mismo. Usted sabe que cuando una clase extiende a otra, la clase que extiende no tiene que llamarse Base [ClassThatWillExtend] ¿verdad?
David Cowden
1
@DavidCowden, ¿puede dejar de anunciar su respuesta como un comentario debajo de cualquier otra respuesta, por favor?
marktani