Quale OO Design usare (esiste un Design Pattern)?


11

Ho due oggetti che rappresentano un "Bar / Club" (un posto dove bevi / socializzi).

In uno scenario ho bisogno del nome della barra, dell'indirizzo, della distanza, dello slogan

In un altro scenario ho bisogno del nome della barra, dell'indirizzo, dell'URL del sito web, del logo

Quindi ho due oggetti che rappresentano la stessa cosa ma con campi diversi.

Mi piace usare oggetti immutabili, quindi tutti i campi sono impostati dal costruttore .

Un'opzione è avere due costruttori e annullare gli altri campi, ovvero:

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
}

Non mi piace perché dovresti controllare nulla quando usi i getter

Nel mio esempio reale il primo scenario ha 3 campi e il secondo scenario ha circa 10, quindi sarebbe una vera seccatura avere due costruttori , la quantità di campi che dovrei dichiarare nulla e quindi quando l'oggetto è in uso non lo faresti ' non so quale Bardove stavi usando e quindi quali campi sarebbero nulli e cosa no.

Quali altre opzioni ho?

Due classi chiamate BarPreviewe Bar?

Qualche tipo di eredità / interfaccia?

Qualcos'altro che è fantastico?


29
Wow, in realtà hai trovato un uso legittimo Barcome identificatore!
Mason Wheeler,

1
se si condividono alcune proprietà, un'opzione sarebbe quella di implementare una classe di base.
Yusubov,

1
Non ci avevo mai pensato. Scrivere qualsiasi tipo di codice per il mio bar / foo dog potrebbe diventare davvero confuso.
Erik Reppen,


4
@gnat Come indovinano le persone. Dalla citazione del tuo link: You should only ask practical, answerable questions based on actual problems that you face.ed è esattamente quello che sta succedendo qui
Blundell,

Risposte:


9

I miei pensieri:

Una "barra", come rappresentata nel tuo dominio, contiene tutte le cose che potrebbero essere necessarie in entrambi i luoghi: nome, indirizzo, URL, logo, slogan e "distanza" (sto indovinando dalla posizione del richiedente). Pertanto, nel tuo dominio, dovrebbe esserci una classe "Bar" che è la fonte autorevole di dati per una barra, indipendentemente da dove i dati verranno utilizzati in seguito. Questa classe dovrebbe essere modificabile, in modo che le modifiche ai dati della barra possano essere apportate e salvate quando necessario.

Tuttavia, ci sono due posizioni in cui sono necessari i dati di questo oggetto Bar ed entrambi necessitano solo di un sottoinsieme (e non si desidera che tali dati vengano modificati). La solita risposta è un "oggetto di trasferimento dati" o DTO; un POJO (semplice oggetto Java) contenente i getter di proprietà immutabili. Questi DTO possono essere prodotti chiamando un metodo sull'oggetto dominio Bar principale: "toScenario1DTO ()" e "toScenario2DTO ()"; i risultati sono un DTO idratato (il che significa che devi usare solo il costruttore lungo e complicato in un unico posto).

Se hai mai avuto bisogno di rispedire i dati alla classe di dominio principale (per aggiornarli; qual è il punto dei dati se non puoi cambiarli secondo necessità per riflettere lo stato attuale del mondo reale?), Potresti costruire uno dei DTO o utilizzare un nuovo DTO mutabile e restituirlo alla classe Bar utilizzando un metodo "updateFromDto ()".

EDIT: per fornire un esempio:

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 }
}

Le classi Address, Distance e Url devono essere immutabili o, se utilizzate nei DTO, devono essere sostituite con controparti immutabili.


cosa significa l'acronimo DTO? Non capisco bene cosa dici per favore, potresti metterlo in un esempio funzionante. fyi I dati provengono da un server, quindi una volta "idratato" uno dei due moduli di questa classe, i campi non dovranno essere modificati, usati solo per essere visualizzati sull'interfaccia utente
Blundell,

1
DTO sta per "oggetto di trasferimento dati" e si riferisce a una classe di dati di struttura molto semplice utilizzata per spostare i dati dal livello di dominio "ricco" verso i livelli superiori come l'interfaccia utente, senza esporre il livello di dominio effettivo all'interfaccia utente (consentendo modifiche da rendere al dominio senza influire sull'interfaccia utente, purché il DTO non debba cambiare).
KeithS

la mutabilità non ha nulla a che vedere con la modifica o la persistenza.

4
@JarrodRoberson - stai scherzando? Se una classe è immutabile (non può essere modificata sul posto dopo l'istanza) l'unico modo per apportare una modifica ai dati nel livello dati è costruire una nuova istanza che rappresenti lo stesso record (stesso PK) con membri diversi. Sebbene i metodi "mutanti" che producono una nuova istanza possano renderlo più semplice, ha comunque un grande impatto sulla modifica e sulla persistenza.
KeithS

1
@JarrodRoberson Ascolta la community. Tui hai torto. . In effetti metà dei commenti in questa intera risposta mostra che abbiamo bisogno di un po 'di istruzione OO di base intorno alla lavagna - è disgustoso ..
David Cowden,

5

Se ti interessa solo un sottoinsieme delle proprietà e vuoi assicurarti che non si confondano, crea due interfacce e usale per parlare con l'oggetto base.


1
Lo dici ma potresti fare un esempio usando la Barclasse
Blundell il

3

Il Builder Pattern (o qualcosa di simile ad esso) potrebbe essere utile qui.

Avere oggetti immutabili è una cosa ammirevole, ma la realtà è che con Reflection in Java, nulla è veramente sicuro ;-).


Vedo come HawaiianPizzaBuilderfunziona perché i valori di cui ha bisogno sono codificati. Tuttavia, come è possibile utilizzare questo modello se i valori vengono recuperati e passati a un costruttore? L' HawaiianPizzaBuilderavrebbe ancora tutti i getter che SpicyPizzaBuilderha così nulla è possibile. A meno che non lo abbini a @Jarrods Null Object Pattern. Un esempio di codice Barpotrebbe farti capire
Blundell il

+1 Uso il builder in casi come questo, funziona come un incantesimo - incluso, ma non limitato a impostare impostazioni predefinite ragionevoli invece di null quando lo voglio
moscerino

3

Il punto chiave qui è la differenza tra cos'è una "barra" e come la usi in uno o in un altro contesto.

Il Bar è una singola entità nel mondo reale (o un mondo artificiale, come un gioco), e solo UNA istanza di oggetto dovrebbe rappresentarlo. In qualsiasi momento successivo, quando non crei quell'istanza da un segmento di codice, ma lo carichi da un file di configurazione o da un database, questo sarà più evidente.

(Per essere ancora più esoterici: ogni istanza di Bar ha un ciclo di vita diverso rispetto all'oggetto che lo rappresenta durante l'esecuzione del programma. Anche se si dispone di un codice sorgente che crea tale istanza, significa che l'entità Bar come viene descritta, "esiste "in uno stato dormiente nel codice sorgente e" risvegliarsi "quando quel codice lo crea effettivamente nella memoria ...)

Ci scusiamo per il lungo inizio, ma spero che questo chiarisca il mio punto. Hai UNA classe Bar con tutti gli attributi di cui potresti mai aver bisogno e un'istanza Bar che rappresenta ciascuna entità Bar. Questo è corretto nel tuo codice e indipendente da come vuoi vedere la stessa istanza in contesti diversi .

Quest'ultimo può essere rappresentato da due diverse interfacce , che contengono i metodi di accesso richiesti (getName (), getURL (), getDistance ()) e la classe Bar dovrebbe implementare entrambi. (E forse la "distanza" cambierà in "posizione" e getDistance () diventa un calcolo da un'altra posizione :-))

Ma la creazione è per l'entità Bar e non per il modo in cui vuoi usare quell'entità: un costruttore, tutti i campi.

EDITED: posso scrivere il codice! :-)

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; } 
}

1

Modello appropriato

Quello che stai cercando è più comunemente indicato come Null Object Pattern. Se non ti piace il nome, puoi chiamarlo come Undefined Value Patternetichetta semantica diversa. A volte questo modello viene chiamato Poison Pill Pattern.

In tutti questi casi, l'Oggetto è un rimpiazzo o sostituire uno Default Valueinvece di null. It doesn't replace the semantic ofnull but makes it easier to work with the data model in a more predictable way becausenull` ora non dovrebbe mai essere uno stato valido.

È un modello in cui si riserva un'istanza speciale di una determinata classe per rappresentare nullun'opzione altrimenti come a Default Value. In questo modo non è necessario verificare null, è possibile verificare l'identità rispetto NullObjectall'istanza nota . Puoi tranquillamente chiamare metodi su di esso e simili senza preoccuparti NullPointerExceptions.

In questo modo sostituisci i tuoi nullincarichi con le loro NullObjectistanze rappresentative e il gioco è fatto.

Analisi orientata agli oggetti corretta

In questo modo puoi avere un punto in comune Interfacecon il polimorfismo e avere comunque protezione dal doverti preoccupare dell'assenza di dati nelle specifiche implementazioni dell'interfaccia. Quindi alcuni Barpotrebbero non avere presenza sul web e alcuni potrebbero non avere dati sulla posizione al momento della costruzione. Null Object Patterti consente di fornire un valore predefinito per ciascuno di essi che è un dato markerper quei dati che dicono la stessa cosa, nulla è stato fornito qui, senza avere l'accordo con il controllo NullPointerExceptiondappertutto.

Design orientato agli oggetti

In primo luogo hanno abstractun'implementazione che è un super set di tutti gli attributi che sia Bare Clubcondivisione.

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
}

Quindi è possibile implementare sottoclassi di questa Establishmentclasse e aggiungere solo le cose specifiche necessarie per ciascuna delle classi Bare Clubche non si applicano all'altra.

Persistenza

Questi oggetti segnaposto se costruiti correttamente possono essere archiviati in modo trasparente in un database senza alcuna gestione speciale.

Prova futura

Se hai deciso di saltare sul carrozzone Inversion of Control / Dependency Injection successivamente, questo modello rende facile iniettare anche questi oggetti marker.


0

Penso che il problema sia che non stai modellando una barra in nessuno di questi scenari (e stai modellando due diversi problemi, oggetti, ecc.). Se vedo un bar di classe, mi aspetto alcune funzionalità relative alle bevande, ai menu, ai posti disponibili e il tuo oggetto non ha nulla di tutto ciò. Se vedo il comportamento dei tuoi oggetti, stai modellando informazioni su uno stabilimento. La barra è ciò per cui stai usando quelli in questo momento, ma non è il comportamento intrinseco che stanno implementando. (In un altro contesto: se stai modellando un matrimonio, avrai due variabili di istanza Persona moglie; Persona marito; una moglie è il ruolo attuale che stai assegnando a quell'oggetto in quel momento, ma l'oggetto è ancora una Persona). Farei qualcosa del genere:

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;
     }
}

-1

Non c'è davvero bisogno di complicare troppo questo. Hai bisogno di due diversi tipi di oggetto? Crea due lezioni.

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;
     }
     //...
}

Se è necessario operare allo stesso modo su di essi, prendere in considerazione l'aggiunta di un'interfaccia o l'utilizzo di reflection.


@ David: Oh no. Hanno in comune un intero membro dei dati. EMERGENZA!
DeadMG

senza un'interfaccia comune non c'è polimorfismo in questa soluzione, nessuna di queste classi potrebbe essere sostituita con l'altra con questa scarsa decisione progettuale. Non è che non abbiano lo stesso superset di attributi, è che di default sono alcuni di questi attributi null. Ricorda nullsignifica l' assenza di dati , non l'assenza dell'attributo.

1
@DeadMG Questo è probabilmente un esercizio esattamente nell'idea di raggruppare i valori condivisi in oggetti padre .. la tua soluzione non otterrebbe pieno credito se la proponessi in quel contesto.
David Cowden,

Il PO non specifica alcuna necessità di sostituibilità. E come ho detto, puoi semplicemente aggiungere un'interfaccia o usare la riflessione se vuoi.
DeadMG

@DeadMG Ma la riflessione è come provare a scrivere un'app mobile multipiattaforma usando un ambiente: potrebbe funzionare, ma non è corretto . La penalità per aver chiamato un metodo usando la riflessione è ovunque tra 2 e 50 volte più lenta di una normale chiamata di metodo. La riflessione non è una cura per tutti ...
David Cowden,

-1

La mia risposta a chiunque abbia questo tipo di problema è di scomporla in passaggi gestibili .

  1. Per prima cosa basta creare due classi BarOnee BarTwo(o chiamarle entrambe Barma in pacchetti diversi)
  2. Inizia ad usare i tuoi oggetti come classi separate, per ora non preoccuparti della duplicazione del codice. Noterai quando attraversi (metodi duplicati) dall'uno all'altro
  3. Potresti scoprire che non sono in alcun modo correlati, e quindi dovresti chiederti se entrambi sono davvero unbar se non rinominare la classe offensiva in ciò che ora rappresenta
  4. Se trovi campi o comportamenti comuni, puoi estrarre un interfaceo superclasscon il comportamento comune
  5. Una volta che hai un interfaceo superclasspotresti creare un buildero factoryper creare / recuperare i tuoi oggetti di implementazione

(4 e 5 sono le risposte dell'altra a questa domanda)


-2

È necessaria una classe di base, ad esempio Posizione che ha un nome e un indirizzo . Ora, hai due classi Bar e BarPreview che estendono la posizione della classe base . In ogni classe inizializzi le variabili comuni della superclasse e quindi le tue variabili uniche:

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;
    }
}

E simile per la classe BarPreview ..

Se ti fa dormire meglio di notte, sostituisci tutte le istanze di Posizione nel mio codice con AnythingYouThinkWouldBeAnAppropriateNameForAThingThatABarExtendsSuchAsFoodServicePlace - ffs.


l'OP vuole che l'istanza sia immutabile , ciò significa che tutto deve essere final.

1
Questo potrebbe funzionare, è necessario finalnei campi @David. Barnon dovrebbe extend Locationperò avere senso. Forse Bar extends BaseBare BarPreview extends BaseBarquesti nomi non suonano davvero troppo bene, però, speravo in qualcosa di più elegante
Blundell,

@JarrodRoberson Lo sto solo disegnando per lui .. basta aggiungere il finale perché sia ​​immutabile. È un gioco da ragazzi. Il problema fondamentale con la domanda del PO è che non sa come avere una classe base e due classi separate che estendono una classe base. Lo sto semplicemente spiegando in dettaglio.
David Cowden,

@Blundell di che diavolo stai parlando? Un bar è una posizione . Basta sostituire la mia posizione con la tua BaseBar ed è esattamente la stessa cosa. Sai che quando una classe si estende ad un'altra la classe che si estende non deve essere chiamata Base [ClassThatWillExtend] giusto?
David Cowden,

1
@DavidCowden, puoi smettere di pubblicizzare la tua risposta come commento sotto ogni altra risposta, per favore?
marktani,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.