Welches OO-Design soll verwendet werden (gibt es ein Designmuster)?

11

Ich habe zwei Objekte, die eine 'Bar / Club' darstellen (ein Ort, an dem Sie trinken / Kontakte knüpfen).

In einem Szenario benötige ich den Balkennamen, die Adresse, die Entfernung und den Slogon

In einem anderen Szenario benötige ich den Balkennamen, die Adresse, die Website-URL und das Logo

Ich habe also zwei Objekte, die dasselbe darstellen, aber unterschiedliche Felder haben.

Ich verwende gerne unveränderliche Objekte, daher werden alle Felder vom Konstruktor festgelegt .

Eine Möglichkeit besteht darin, zwei Konstruktoren zu haben und die anderen Felder auf Null zu setzen, dh:

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
}

Ich mag das nicht, da Sie null überprüfen müssten, wenn Sie die Getter verwenden

In meinem realen Beispiel hat das erste Szenario 3 Felder und das zweite Szenario ungefähr 10, also wäre es ein echtes Problem, zwei Konstruktoren zu haben , die Anzahl der Felder, die ich als null deklarieren müsste, und wenn das Objekt verwendet wird, würden Sie es nicht tun. Ich weiß nicht, welche BarSie wo verwenden und welche Felder null wären und welche nicht.

Welche anderen Optionen habe ich?

Zwei Klassen genannt BarPreviewund Bar?

Irgendeine Art von Vererbung / Schnittstelle?

Noch etwas, das großartig ist?

Blundell
quelle
29
Wow, Sie haben tatsächlich eine legitime Verwendung Barals Kennung gefunden!
Mason Wheeler
1
Wenn Sie einige Eigenschaften gemeinsam nutzen, besteht eine Option darin, eine Basisklasse zu implementieren.
Yusubov
1
Daran habe ich nie gedacht. Das Schreiben von Code für mein Bar / Foo-Hundesalon kann sehr verwirrend sein.
Erik Reppen
4
@gnat Wie raten die Leute. Aus Ihrem Link-Zitat: You should only ask practical, answerable questions based on actual problems that you face.Und genau das passiert hier
Blundell

Antworten:

9

Meine Gedanken:

Eine "Leiste", wie sie in Ihrer Domain dargestellt ist, enthält alle Dinge, die an beiden Orten benötigt werden: Name, Adresse, URL, Logo, Slogan und "Entfernung" (ich vermute vom Standort des Anforderers). Daher sollte es in Ihrer Domain eine "Balken" -Klasse geben, die die maßgebliche Datenquelle für einen Balken darstellt, unabhängig davon, wo die Daten später verwendet werden. Diese Klasse sollte veränderbar sein, damit Änderungen an den Daten der Leiste vorgenommen und bei Bedarf gespeichert werden können.

Sie haben jedoch zwei Stellen, an denen die Daten dieses Balkenobjekts benötigt werden, und beide benötigen nur eine Teilmenge (und Sie möchten nicht, dass diese Daten geändert werden). Die übliche Antwort ist ein "Datenübertragungsobjekt" oder DTO; ein POJO (einfaches altes Java-Objekt), das die unveränderlichen Eigenschafts-Getter enthält. Diese DTOs können durch Aufrufen einer Methode für das Hauptobjekt der Balkendomäne erstellt werden: "toScenario1DTO ()" und "toScenario2DTO ()"; Das Ergebnis ist ein hydratisiertes DTO (was bedeutet, dass Sie den langen, komplizierten Konstruktor nur an einer Stelle verwenden müssen).

Wenn Sie jemals Daten an die Hauptdomänenklasse zurücksenden mussten (um sie zu aktualisieren; wozu dienen Daten, wenn Sie sie nicht nach Bedarf ändern können, um den aktuellen Status der realen Welt widerzuspiegeln?), Können Sie eine der folgenden Methoden erstellen DTOs oder verwenden Sie ein neues veränderbares DTO und geben Sie es mit der Methode "updateFromDto ()" an die Bar-Klasse zurück.

EDIT: um ein Beispiel zu geben:

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

Die Adressen-, Entfernungs- und URL-Klassen sollten entweder selbst unveränderlich sein oder bei Verwendung in den DTOs durch unveränderliche Gegenstücke ersetzt werden.

KeithS
quelle
Wofür steht das Akronym DTO? Ich verstehe nicht ganz, was Sie sagen, könnten Sie es bitte in ein funktionierendes Beispiel einfügen? fyi Die Daten stammen von einem Server. Sobald eine Form dieser Klasse "hydratisiert" ist, müssen die Felder nicht mehr geändert werden, sondern nur zur Anzeige auf der Benutzeroberfläche
Blundell
1
DTO steht für "Datenübertragungsobjekt" und bezieht sich auf eine Datenklasse mit sehr einfacher Struktur, die verwendet wird, um Daten von der "reichen" Domänenschicht in obere Schichten wie die Benutzeroberfläche zu verschieben, ohne die tatsächliche Domänenschicht der Benutzeroberfläche auszusetzen (wodurch Änderungen zulässig sind) in die Domäne aufgenommen werden, ohne die Benutzeroberfläche zu beeinträchtigen, solange sich das DTO nicht ändern muss).
KeithS
Die Veränderlichkeit hat keinerlei Einfluss auf Modifikation oder Persistenz.
4
@ JarrodRoberson - machst du Witze? Wenn eine Klasse unveränderlich ist (kann nach der Instanziierung nicht direkt geändert werden), besteht die einzige Möglichkeit, Änderungen an den Daten in der Datenschicht vorzunehmen, darin, eine neue Instanz zu erstellen, die denselben Datensatz (dieselbe PK) mit verschiedenen Mitgliedern darstellt. Während "mutierende" Methoden, die eine neue Instanz erzeugen, dies einfacher machen können, hat dies immer noch einen großen Einfluss auf Modifikation und Persistenz.
KeithS
1
@JarrodRoberson Höre der Community zu. Sie liegen falsch. . Tatsächlich zeigt die Hälfte der Kommentare in dieser ganzen Antwort, dass wir eine grundlegende OO-Schulung auf der ganzen Linie brauchen - es ist widerlich.
David Cowden
5

Wenn Sie sich nur für eine Teilmenge der Eigenschaften interessieren und sicherstellen möchten, dass sie nicht verwechselt werden, erstellen Sie zwei Schnittstellen und verwenden Sie diese, um mit Ihrem Basisobjekt zu sprechen.

jmoreno
quelle
1
Sie sagen das, aber könnten Sie ein Beispiel mit der BarKlasse geben
Blundell
3

Das Builder-Muster (oder etwas in der Nähe davon) könnte hier von Nutzen sein.

Unveränderliche Objekte zu haben ist eine bewundernswerte Sache, aber die Realität ist, dass mit Reflection in Java nichts wirklich sicher ist ;-).

Martijn Verburg
quelle
Ich kann sehen, wie das HawaiianPizzaBuilderfunktioniert, weil die benötigten Werte fest codiert sind. Wie können Sie dieses Muster jedoch verwenden, wenn die Werte abgerufen und an einen Konstruktor übergeben werden? Das HawaiianPizzaBuilderhätte noch alle Getter, die SpicyPizzaBuilderdas so null hat ist möglich. Es sei denn, Sie kombinieren dies mit @Jarrods Null Object Pattern. Ein Codebeispiel mit Barwürde Ihren Standpunkt vermitteln
Blundell
+1 Ich benutze Builder in solchen Fällen, funktioniert wie ein Zauber - einschließlich, aber nicht beschränkt auf das Setzen angemessener Standardeinstellungen anstelle von Null, wenn ich dies möchte
Mücke
3

Der entscheidende Punkt hierbei ist der Unterschied zwischen dem, was ein "Balken" ist und wie Sie ihn verwenden in dem einen oder anderen Kontext verwenden.

Die Leiste ist eine einzelne Einheit in der realen Welt (oder eine künstliche Welt wie ein Spiel), und nur EINE Objektinstanz sollte sie darstellen. Wenn Sie diese Instanz später nicht aus einem Codesegment erstellen, sondern aus einer Konfigurationsdatei oder einer Datenbank laden, wird dies deutlicher.

(Um noch esoterischer zu sein: Jede Balkeninstanz hat einen anderen Lebenszyklus als das Objekt, das sie darstellt, wenn Ihr Programm ausgeführt wird. Selbst wenn Sie einen Quellcode haben, der diese Instanz erstellt, bedeutet dies, dass die beschriebene Balkenentität "existiert" "in einem Ruhezustand in Ihrem Quellcode und" erwachen ", wenn dieser Code ihn tatsächlich im Speicher erstellt ...)

Entschuldigung für den langen Start, aber ich hoffe, das macht meinen Standpunkt klar. Sie haben eine Bar - Klasse alle Attribute mit , dass Sie jemals brauchen würde, und eine Bar Instanz jede Bar Einheit darstellt. Dies ist in Ihrem Code korrekt und unabhängig davon, wie Sie dieselbe Instanz in verschiedenen Kontexten anzeigen möchten .

Letzteres kann durch zwei verschiedene Schnittstellen dargestellt werden werden, die die erforderlichen Zugriffsmethoden enthalten (getName (), getURL (), getDistance ()), und die Bar-Klasse sollte beide implementieren. (Und vielleicht ändert sich die "Entfernung" in "Ort" und getDistance () wird zu einer Berechnung von einem anderen Ort :-))

Die Erstellung ist jedoch für die Bar-Entität und nicht für die Art und Weise, wie Sie diese Entität verwenden möchten: ein Konstruktor, alle Felder.

EDITED: Ich kann Code schreiben! :-)

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
quelle
1

Angemessenes Muster

Was Sie suchen, wird am häufigsten als das bezeichnet Null Object Pattern. Wenn Ihnen der Name nicht gefällt, können Sie ihn als Undefined Value Patterndieselbe Bezeichnung für dieselbe Semantik bezeichnen. Manchmal wird dieses Muster genannt Poison Pill Pattern.

In all diesen Fällen ist das Objekt ein Ersatz oder steht für eine Default Valuestatt null. It doesn't replace the semantic ofnull but makes it easier to work with the data model in a more predictable way becausenull "sollte jetzt niemals ein gültiger Zustand sein.

Es ist ein Muster, bei dem Sie eine spezielle Instanz einer bestimmten Klasse reservieren, um eine andere nullOption als darzustellen Default Value. Auf diese Weise müssen Sie nicht überprüfen null, sondern können die Identität anhand Ihrer bekannten NullObjectInstanz überprüfen . Sie können Methoden und ähnliches sicher aufrufen, ohne sich Sorgen machen zu müssen NullPointerExceptions.

Auf diese Weise ersetzen Sie Ihre nullAufgaben durch ihre repräsentativen NullObjectInstanzen und Sie sind fertig.

Richtige objektorientierte Analyse

Auf diese Weise können Sie einen gemeinsamen InterfacePolymorphismus haben und trotzdem vor dem Fehlen von Daten in den spezifischen Implementierungen der Schnittstelle geschützt sein. Einige haben Barmöglicherweise keine Webpräsenz, andere verfügen zum Zeitpunkt der Erstellung möglicherweise nicht über Standortdaten. Null Object PatterMit dieser Option können Sie für jede dieser markerDaten einen Standardwert angeben, der für die Daten gilt, die dasselbe aussagen. Hier wurde nichts angegeben, ohne dass die Prüfung durchgeführt werden mussNullPointerException überall durchgeführt werden muss.

Richtiges objektorientiertes Design

Haben Sie zuerst eine abstractImplementierung, die eine Supermenge aller Attribute ist, die beide Barund Clubgemeinsam nutzen.

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
}

Dann können Sie Unterklassen dieser EstablishmentKlasse implementieren und nur die spezifischen Dinge hinzufügen, die Sie für jede der Klassen Barund benötigen Club, die für die andere nicht gelten.

Beharrlichkeit

Diese Platzhalterobjekte können bei korrekter Konstruktion auch ohne besondere Behandlung transparent in einer Datenbank gespeichert werden.

Zukunftssicher

Wenn Sie sich dazu entschlossen haben, später auf den Zug Inversion of Control / Dependency Injection zu springen, können Sie mit diesem Muster auch diese Markierungsobjekte einfach injizieren.


quelle
0

Ich denke, das Problem ist, dass Sie in keinem dieser Szenarien einen Balken modellieren (und Sie modellieren zwei verschiedene Probleme, Objekte usw.). Wenn ich eine Klasse-Bar sehe, würde ich einige Funktionen erwarten, die sich auf Getränke, die Menüs, die verfügbaren Plätze beziehen, und Ihr Objekt hat nichts davon. Wenn ich das Verhalten Ihrer Objekte sehe, modellieren Sie Informationen über eine Einrichtung. Bar ist das, wofür Sie diese in diesem Moment verwenden, aber es ist nicht das eigentliche Verhalten, das sie implementieren. (In einem anderen Kontext: Wenn Sie eine Ehe modellieren, haben Sie zwei Instanzvariablen: Person Ehefrau; Person Ehemann; eine Frau ist die aktuelle Rolle, die Sie diesem Objekt in diesem Moment geben, aber das Objekt ist immer noch eine Person). Ich würde so etwas machen:

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;
     }
}
user1494736
quelle
-1

Es ist wirklich nicht nötig, dies zu komplizieren. Sie benötigen zwei verschiedene Arten von Objekten? Bilden Sie zwei Klassen.

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

Wenn Sie sie gleichermaßen bearbeiten müssen, sollten Sie eine Schnittstelle hinzufügen oder Reflektion verwenden.

DeadMG
quelle
@ David: Oh nein. Sie haben wie ein ganzes Datenelement gemeinsam. NOTFALL!
DeadMG
Ohne eine gemeinsame Schnittstelle gibt es in dieser Lösung keinen Polymorphismus. Keine dieser Klassen könnte durch diese schlechte Entwurfsentscheidung durch die andere ersetzt werden. Es ist nicht so, dass sie nicht die gleiche Obermenge von Attributen haben, es ist so, dass einige dieser Attribute standardmäßig sind null. Denken Sie daran , nullMittel , um das Fehlen von Daten , nicht das Fehlen des Attributs.
1
@DeadMG Dies ist möglicherweise eine Übung in genau der Idee, gemeinsam genutzte Werte in übergeordnete Objekte zu gruppieren. Ihre Lösung würde nicht die volle Punktzahl erhalten, wenn Sie sie in diesem Kontext vorschlagen würden.
David Cowden
Das OP legt keinen Bedarf an Substituierbarkeit fest. Und wie gesagt, Sie können einfach eine Schnittstelle hinzufügen oder Reflection verwenden, wenn Sie möchten.
DeadMG
@DeadMG Aber Reflexion ist wie der Versuch, eine plattformübergreifende mobile App in einer Umgebung zu schreiben - es mag funktionieren, aber es ist nicht korrekt . Die Strafe für das Aufrufen einer Methode mit Reflektion ist zwischen 2 und 50 Mal langsamer als bei einem normalen Methodenaufruf. Reflexion ist kein Allheilmittel.
David Cowden
-1

Meine Antwort an alle, die Probleme dieser Art haben, besteht darin , sie in überschaubare Schritte zu unterteilen .

  1. Erstellen Sie zunächst einfach zwei Klassen BarOneund BarTwo(oder rufen Sie beide auf, Barjedoch in unterschiedlichen Paketen).
  2. Verwenden Sie Ihre Objekte als separate Klassen. Machen Sie sich vorerst keine Gedanken über die Codeduplizierung. Sie werden feststellen, wenn Sie von einer zur anderen wechseln (doppelte Methoden)
  3. Möglicherweise stellen Sie fest, dass sie in keiner Weise miteinander verwandt sind, und Sie sollten sich fragen, ob beide wirklich einebar beleidigende Klasse sind, wenn nicht umbenannt in das, was sie jetzt darstellt
  4. Wenn Sie gemeinsame Felder oder Verhaltensweisen finden, können Sie ein interfaceoder superclassmit dem gemeinsamen Verhalten extrahieren
  5. Sobald Sie ein interfaceoder haben, können superclassSie ein oder erstellen builderoder factoryIhre Implementierungsobjekte erstellen / abrufen

(4 und 5 sind die anderen Antworten auf diese Frage)

Blundell
quelle
-2

Sie benötigen eine Basisklasse, z. B. Location, die einen Namen und eine Adresse hat . Nun haben Sie zwei Klassen Bar und BarPreview die Basisklasse erweitern Lage . In jeder Klasse initialisieren Sie die allgemeinen Variablen der Superklasse und dann Ihre eindeutigen Variablen:

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

Und ähnlich für die BarPreview-Klasse.

Wenn Sie nachts besser schlafen können, ersetzen Sie alle Instanzen von Location in meinem Code durch AnythingYouThinkWouldBeAnAppropriateNameForAThingThatABarExtendsSuchAsFoodServicePlace - ffs.

David Cowden
quelle
Das OP möchte , dass die Instanz unveränderlich ist , das heißt, alles muss sein final.
1
Dies könnte funktionieren, Sie müssen finaldie Felder @David. Barsollte aber nicht, extend Locationdass das keinen Sinn macht. Vielleicht Bar extends BaseBarund BarPreview extends BaseBardiese Namen klingen auch nicht wirklich gut, ich hoffte auf etwas eleganteres
Blundell
@JarrodRoberson Ich skizziere es nur für ihn. Füge einfach final hinzu, damit es unveränderlich ist. Es ist ein Kinderspiel. Das grundlegende Problem mit der Frage des OP ist, dass er nicht weiß, wie man eine Basisklasse und zwei separate Klassen hat, die eine Basisklasse erweitern. Ich beschreibe das einfach.
David Cowden
@Blundell wovon in aller Welt redest du? Eine Bar ist ein Ort . Ersetzen Sie einfach meinen Speicherort durch Ihre BaseBar und es ist genau das Gleiche. Sie wissen, dass, wenn eine Klasse eine andere erweitert, die Klasse, die sie erweitert, nicht Base [ClassThatWillExtend] heißen muss, oder?
David Cowden
1
@DavidCowden, können Sie bitte aufhören, Ihre Antwort als Kommentar unter jeder anderen Antwort zu bewerben?
März