Jakiego projektu OO użyć (czy istnieje wzorzec projektu)?

11

Mam dwa przedmioty reprezentujące „Bar / Klub” (miejsce, w którym pijesz / spędzasz czas z przyjaciółmi).

W jednym scenariuszu potrzebuję nazwy paska, adresu, odległości, hasła

W innym scenariuszu potrzebuję nazwy paska, adresu, adresu URL strony internetowej, logo

Mam więc dwa obiekty reprezentujące to samo, ale z różnymi polami.

Lubię używać niezmiennych obiektów, więc wszystkie pola są ustawiane z konstruktora .

Jedną z opcji jest posiadanie dwóch konstruktorów i zerowanie pozostałych pól, tj .:

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
}

Nie podoba mi się to, ponieważ musiałbyś zerować sprawdzanie, gdy używasz getterów

W moim prawdziwym przykładzie pierwszy scenariusz ma 3 pola, a drugi scenariusz ma około 10, więc prawdziwym bólem byłoby posiadanie dwóch konstruktorów , ilość pól, które musiałbym zadeklarować jako zerowe, a kiedy obiekt byłby w użyciu, nie byłoby ' t wiem Bar, gdzie używasz, a więc jakie pola byłyby puste, a co nie.

Jakie inne opcje mam?

Dwie klasy nazwane BarPreviewi Bar?

Jakiś typ dziedziczenia / interfejsu?

Coś jeszcze, co jest niesamowite?

Blundell
źródło
29
Wow, właściwie wymyśliłeś uzasadnione użycie Barjako identyfikatora!
Mason Wheeler,
1
jeśli udostępniasz niektóre właściwości, jedną z opcji byłoby zaimplementowanie klasy podstawowej.
Yusubov
1
Nigdy o tym nie myślałem. Pisanie każdego rodzaju kodu dla mojego pokoju dla psów Bar / Foo może być bardzo mylące.
Erik Reppen
4
@gnat Jak zgadują ludzie. Z cytatu z linku: You should only ask practical, answerable questions based on actual problems that you face.i dokładnie to się tutaj dzieje
Blundell,

Odpowiedzi:

9

Moje myśli:

„Pasek”, reprezentowany w domenie, zawiera wszystkie rzeczy, które mogą być potrzebne w obu miejscach: imię i nazwisko, adres, adres URL, logo, hasło i „odległość” (domyślam się z lokalizacji wnioskodawcy). Dlatego w Twojej domenie powinna istnieć jedna klasa „Bar”, która jest wiarygodnym źródłem danych dla jednego paska, bez względu na to, gdzie dane zostaną wykorzystane później. Ta klasa powinna być modyfikowalna, aby zmiany danych paska mogły być wprowadzane i zapisywane w razie potrzeby.

Istnieją jednak dwa miejsca, w których potrzebne są dane tego obiektu Bar, i oba wymagają tylko podzbioru (i nie chcesz, aby te dane były zmieniane). Zwykle odpowiedzią jest „obiekt przesyłania danych” lub DTO; POJO (zwykły obiekt Java) zawierający niezmienne gettery właściwości. Te DTO można wytworzyć przez wywołanie metody na głównym obiekcie domeny Bar: „toScenario1DTO ()” i „toScenario2DTO ()”; wyniki są uwodnionym DTO (co oznacza, że ​​wystarczy użyć długiego, skomplikowanego konstruktora w jednym miejscu).

Jeśli kiedykolwiek musiałeś odesłać dane z powrotem do głównej klasy domeny (aby je zaktualizować; jaki jest sens danych, jeśli nie możesz ich zmienić w razie potrzeby w celu odzwierciedlenia obecnego stanu rzeczywistego świata?), Możesz zbudować jeden z DTO lub użyj nowego, zmiennego DTO i wróć do klasy Bar, używając metody „updateFromDto ()”.

EDYCJA: aby podać przykład:

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

Klasy Adres, Odległość i Url powinny być same w sobie niezmienne lub, jeśli są używane w DTO, należy je zastąpić niezmiennymi odpowiednikami.

KeithS
źródło
co oznacza skrót DTO? Nie do końca rozumiem, co powiedziałeś, czy mógłbyś podać to w sprawdzony przykład. fyi Dane pochodzą z serwera, więc gdy jakakolwiek forma tej klasy zostanie „uwodniona”, pola nie będą musiały być zmieniane, tylko wyświetlane w interfejsie użytkownika
Blundell,
1
DTO oznacza „obiekt transferu danych” i odnosi się do klasy danych o bardzo prostej strukturze służącej do przenoszenia danych z „bogatej” warstwy domeny na wyższe warstwy, takie jak interfejs użytkownika, bez narażania faktycznej warstwy domeny na interfejs użytkownika (umożliwiając zmiany do domeny bez wpływu na interfejs użytkownika, o ile DTO nie musi się zmieniać).
KeithS,
zmienność nie ma żadnego wpływu na modyfikację lub trwałość.
4
@JarrodRoberson - żartujesz? Jeśli klasa jest niezmienna (nie można jej zmienić po utworzeniu instancji), jedynym sposobem na dokonanie zmiany danych w warstwie danych jest zbudowanie nowej instancji reprezentującej ten sam rekord (ten sam PK) z różnymi elementami. Chociaż metody „mutowania”, które powodują powstanie nowej instancji, mogą to ułatwić, nadal mają ogromny wpływ na modyfikację i trwałość.
KeithS,
1
@JarrodRoberson Słuchaj społeczności. Mylisz się. . W rzeczywistości połowa komentarzy w tej całej odpowiedzi pokazuje, że potrzebujemy podstawowego szkolenia OO wokół tablicy - to obrzydliwe ..
David Cowden
5

Jeśli zależy Ci tylko na podzbiorze właściwości i chcesz się upewnić, że się nie pomieszają, utwórz dwa interfejsy i użyj go do rozmowy z obiektem podstawowym.

jmoreno
źródło
1
Mówisz tak, ale czy możesz podać przykład, używając Barklasy
Blundell,
3

Budowniczy (lub coś podobnego do niego) mogą być wykorzystane tutaj.

Posiadanie niezmiennych obiektów jest godną podziwu rzeczą, ale w rzeczywistości z Reflection in Java nic nie jest naprawdę bezpieczne ;-).

Martijn Verburg
źródło
Widzę, jak to HawaiianPizzaBuilderdziała, ponieważ wartości, których potrzebuje, są zakodowane na stałe. Jak jednak użyć tego wzorca, jeśli wartości zostaną pobrane i przekazane do konstruktora? HawaiianPizzaBuilderNadal mają wszystkie pobierające że SpicyPizzaBuilderma tak null jest możliwe. Chyba że połączysz to z @Jarrods Null Object Pattern. Przykład kodu z Barpoprowadziłby twój punkt
Blundell
+1 Używam konstruktora w takich przypadkach, działa jak urok - w tym między innymi ustawianie rozsądnych wartości domyślnych zamiast zerowego, gdy tego chcę
gnat
3

Kluczową kwestią jest tutaj różnica między tym, czym jest „słupek”, a tym, jak go używasz w takim czy innym kontekście.

Pasek jest pojedynczym bytem w świecie rzeczywistym (lub sztucznym świecie, takim jak gra) i powinna go reprezentować tylko JEDNA instancja obiektu. Kiedykolwiek później nie utworzysz tego wystąpienia z segmentu kodu, ale załadujesz go z pliku konfiguracyjnego lub bazy danych, będzie to bardziej widoczne.

(Aby być jeszcze bardziej ezoterycznym: każda instancja Bar ma inny cykl życia niż obiekt, który ją reprezentuje podczas działania programu. Nawet jeśli masz kod źródłowy, który tworzy tę instancję, oznacza to, że encja Bar zgodnie z opisem „istnieje” ”w stanie uśpienia w kodzie źródłowym i„ przebudź się ”, gdy ten kod faktycznie utworzy go w pamięci ...)

Przepraszam za długi początek, ale mam nadzieję, że to wyjaśnia mój punkt widzenia. Masz JEDNĄ klasę Bar posiadającą wszystkie atrybuty, których kiedykolwiek potrzebujesz, i jedną instancję Bar reprezentującą każdą jednostkę Bar. Jest to poprawne w kodzie i niezależne od tego, jak chcesz zobaczyć to samo wystąpienie w różnych kontekstach .

Te ostatnie mogą być reprezentowane przez dwa różne interfejsy , które zawierają wymagane metody dostępu (getName (), getURL (), getDistance ()), a klasa Bar powinna zaimplementować oba. (I być może „odległość” zmieni się na „lokalizacja”, a getDistance () stanie się obliczeniem z innej lokalizacji :-))

Ale stworzenie jest dla encji Bar, a nie dla sposobu, w jaki chcesz jej używać: jeden konstruktor, wszystkie pola.

ZMIENIONO: Mogę pisać kod! :-)

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
źródło
1

Odpowiedni wzór

To, czego szukasz, jest najczęściej określane jako Null Object Pattern. Jeśli nie podoba ci się nazwa, możesz nazwać ją tą Undefined Value Patternsamą semantyką inną etykietą. Czasami ten wzorzec jest nazywany Poison Pill Pattern.

We wszystkich tych przypadkach Obiekt jest zamiennikiem lub zastępuje go wartością zerową Default Valuezamiast wartości null. It doesn't replace the semantic ofzerowej, but makes it easier to work with the data model in a more predictable way becausektóra nigdy nie powinna być poprawnym stanem.

Jest to wzorzec, w którym rezerwujesz specjalne wystąpienie danej klasy w celu reprezentowania innej nullopcji jako Default Value. W ten sposób nie musisz sprawdzać w stosunku do null, możesz sprawdzić tożsamość w stosunku do znanego NullObjectwystąpienia. Możesz bezpiecznie wywoływać metody na nim i tym podobne, nie martwiąc się o to NullPointerExceptions.

W ten sposób zastępujesz swoje nullzadania ich reprezentatywnymi NullObjectinstancjami i gotowe.

Właściwa analiza obiektowa

W ten sposób możesz mieć wspólne Interfacedla polimorfizmu i nadal mieć ochronę przed martwieniem się o brak danych w konkretnych implementacjach interfejsu. Dlatego niektóre Barmogą nie mieć obecności w sieci, a niektóre mogą nie mieć danych o lokalizacji w czasie budowy. Null Object Patterpozwala podać wartość domyślną dla każdego z nich, czyli markerdla danych, które mówią to samo, nic tu nie podano, bez zajmowania się sprawdzaniem NullPointerExceptioncałego miejsca.

Właściwe projektowanie obiektowe

Najpierw abstractimplementacja, która jest super zestawem wszystkich atrybutów, które jednocześnie Bari Clubwspółużytkują.

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
}

Następnie możesz zaimplementować podklasy tej Establishmentklasy i dodać tylko określone rzeczy, których potrzebujesz dla każdej z klas Bari Clubklas, które nie dotyczą drugiej.

Trwałość

Te obiekty zastępcze, jeśli są poprawnie zbudowane, mogą być przezroczyście przechowywane w bazie danych bez specjalnej obsługi.

Dowód na przyszłość

Jeśli zdecydujesz się wskoczyć na modę Inversion of Control / Dependency Injection później, ten wzór ułatwia wstrzyknięcie również tych obiektów znaczników.


źródło
0

Myślę, że problem polega na tym, że nie modelujesz paska w żadnym z tych scenariuszy (i modelujesz dwa różne problemy, obiekty itp.). Jeśli zobaczę bar klasy, oczekiwałbym pewnej funkcjonalności związanej z napojami, menu, dostępnymi siedzeniami, a twój obiekt nie ma tego. Jeśli widzę zachowanie twoich obiektów, modelujesz informacje o zakładzie. Bar jest tym, czego używasz w tej chwili, ale nie jest to samo zachowanie, które wdrażają. (W innym kontekście: jeśli modelujesz małżeństwo, będziesz mieć dwie zmienne instancji Osoba żona; Osoba mąż; żona jest bieżącą rolą, jaką w tym momencie nadajesz temu obiektowi, ale obiekt jest nadal Osobą). Zrobiłbym coś takiego:

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;
     }
}
użytkownik1494736
źródło
-1

Naprawdę nie trzeba tego komplikować. Potrzebujesz dwóch różnych rodzajów obiektów? Zrób dwie klasy.

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

Jeśli chcesz na nich działać równo, rozważ dodanie interfejsu lub użycie refleksji.

DeadMG
źródło
@David: O nie. Mają podobny, jeden wspólny członek danych. NAGŁY WYPADEK!
DeadMG,
bez jakiegoś wspólnego interfejsu nie ma polimorfizmu w tym rozwiązaniu, żadna z tych klas nie mogłaby zostać zastąpiona drugą z powodu tej złej decyzji projektowej. Nie chodzi o to, że nie mają tego samego nadrzędnego atrybutu, lecz o to, że domyślnie niektóre z nich są null. Zapamiętaj nulloznacza brak danych , a nie brak atrybutu.
1
@DeadMG Prawdopodobnie jest to ćwiczenie polegające na pogrupowaniu wspólnych wartości w obiekty nadrzędne. Twoje rozwiązanie nie uzyskałoby pełnego uznania, gdybyś zaproponował je w tym kontekście.
David Cowden,
PO nie określa żadnej potrzeby zastępowalności. I jak powiedziałem, możesz po prostu dodać interfejs lub użyć odbicia, jeśli chcesz.
DeadMG,
@DeadMG Ale refleksja jest jak próba napisania wieloplatformowej aplikacji mobilnej przy użyciu jednego środowiska - może działać, ale nie jest poprawna . Kara za wywołanie metody za pomocą odbicia jest gdziekolwiek od 2 do 50 razy wolniejsza niż normalne wywołanie metody. Refleksja nie jest lekarstwem…
David Cowden,
-1

Moja odpowiedź dla każdego, kto ma tego typu problem, polega na podzieleniu go na możliwe do opanowania kroki .

  1. Najpierw po prostu utwórz dwie klasy BarOnei BarTwo(lub wywołaj je obie, Barale w różnych pakietach)
  2. Zacznij używać swoich obiektów jako osobnych klas, nie martw się na razie duplikacją kodu. Zauważysz, kiedy przechodzisz (metody duplikatów) z jednej do drugiej
  3. Możesz dowiedzieć się, że nie są one w żaden sposób powiązane, dlatego powinieneś zadać sobie pytanie, czy naprawdę tak naprawdębar zmieniają nazwę obrażającej klasy na to, co teraz reprezentuje
  4. Jeśli znajdujesz wspólne pola lub zachowanie, możesz następnie wyodrębnić interfacelub superclassze wspólnym zachowaniem
  5. Gdy masz już interfacelub superclassmożesz utworzyć builderlub, factoryaby utworzyć / pobrać obiekty implementacji

(4 i 5 to inne odpowiedzi na to pytanie)

Blundell
źródło
-2

Potrzebujesz klasy podstawowej, na przykład Lokalizacja, która ma nazwę i adres . Teraz masz dwie klasy Bar i BarPreview rozszerzają klasę bazową Lokalizacja . W każdej klasie inicjujesz zmienne wspólne superklasy, a następnie zmienne unikalne:

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

Podobnie jest w przypadku klasy BarPreview.

Jeśli to sprawia, że ​​śpisz lepiej w nocy, zamień wszystkie wystąpienia lokalizacji w moim kodzie na AnythingYouThinkWouldBeAnAp odpowiedniNameForAThingThatABarExtendsSuchAsFoodServicePlace - ffs.

David Cowden
źródło
OP chce, aby instancja była niezmienna , co oznacza, że ​​wszystko musi być final.
1
To może zadziałać, musisz finalw polach @David. Barnie powinno to extend Locationjednak mieć sensu. Być może Bar extends BaseBari BarPreview extends BaseBarte nazwy też nie brzmią zbyt dobrze, liczyłem na coś bardziej eleganckiego
Blundell,
@JarrodRoberson Po prostu szkicuję go dla niego .. po prostu dodaj finał, aby był niezmienny. To oczywiste ... Podstawowym problemem z pytaniem OP jest to, że nie wie on, jak mieć klasę podstawową i dwie oddzielne klasy, które rozszerzają klasę podstawową. Po prostu opisuję to.
David Cowden,
@Blundell o czym ty mówisz na świecie? Bar jest Lokalizacja . Po prostu zamień moją lokalizację na BaseBar, a to dokładnie to samo. Wiesz, że kiedy jedna klasa rozszerza inną, klasa, którą rozszerza, nie musi mieć nazwy Base [ClassThatWillExtend] prawda?
David Cowden,
1
@DavidCowden, czy możesz przestać reklamować swoją odpowiedź jako komentarz pod każdą inną odpowiedzią?
marktani