Який дизайн OO використовувати (чи є модель дизайну)?


11

У мене є два об'єкти, які являють собою "Бар / Клуб" (місце, де ви п'єте / спілкуєтесь).

В одному сценарії мені потрібна назва смуги, адреса, відстань, слоган

В іншому випадку мені потрібна назва смуги, адреса, URL-адреса веб-сайту, логотип

Отже, у мене є два об'єкти, що представляють одне і те ж, але з різними полями.

Мені подобається використовувати незмінні об’єкти, тому всі поля задаються від конструктора .

Один варіант - мати два конструктори та обнуляти інші поля, тобто:

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
}

Мені це не подобається, як вам доведеться скасувати нуль, коли ви користуєтеся геттерами

У моєму реальному прикладі перший сценарій має 3 поля, а другий сценарій - близько 10, тож було б справжнім болем два конструктори , кількість полів, які я мав би оголосити недійсними, і тоді, коли об’єкт використовується, ви не хочете ' не знаю, де Barти де використовуєш, а також які поля будуть нульовими, а що ні.

Які ще є варіанти?

Два класи називали BarPreviewі Bar?

Якийсь тип успадкування / інтерфейс?

Ще щось, що дивовижне?


29
Нічого собі, ви насправді придумали законне використання Barідентифікатора!
Мейсон Уілер

1
якщо ви ділитесь деякими властивостями, одним із варіантів буде реалізація базового класу.
Юсубов

1
Я ніколи про це не думав. Написання будь-якого коду для мого салону для собак Bar / Foo може стати дуже заплутаним.
Ерік Реппен


4
@gnat Як люди здогадуються. З вашої цитати посилань: You should only ask practical, answerable questions based on actual problems that you face.і саме це відбувається тут
Блонделл

Відповіді:


9

Мої думки:

"Бар", як представлено у вашому домені, містить усі речі, які можуть знадобитися в будь-якому місці: ім'я, адреса, URL-адреса, логотип, слоган та "відстань" (я здогадуюсь з місцезнаходження запитувача). Тому у вашому домені має бути один клас "Бар", який є авторитетним джерелом даних для одного рядка, незалежно від того, де дані будуть використані пізніше. Цей клас повинен бути змінним, щоб зміни в даних панелі можна було вносити та зберігати при необхідності.

Однак у вас є два місця, в яких потрібні дані цього об'єкта Bar, і обом потрібен лише підмножина (і ви не хочете, щоб ці дані були змінені). Звичайна відповідь - "об'єкт передачі даних" або DTO; POJO (звичайний ol-об’єкт Java), що містить непорушних власників властивостей. Ці DTO можуть бути створені шляхом виклику методу на основний об'єкт домену Bar: "toScenario1DTO ()" і "toScenario2DTO ()"; результати є гідратованим DTO (це означає, що вам потрібно використовувати лише довгий складний конструктор в одному місці).

Якщо вам коли-небудь потрібно було повернути дані в основний доменний клас (оновити його; який сенс даних, якщо ви не можете змінити їх у міру необхідності, щоб відобразити поточний стан реального світу?), Ви можете побудувати один із DTO, або використовувати новий змінний DTO і повернути його до класу Bar, використовуючи метод "updateFromDto ()".

EDIT: навести приклад:

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

Класи Адреса, Відстань та Url повинні бути самими незмінними, або при використанні в DTO їх слід замінити на незмінні аналоги.


що означає DTO абревіатури? Я не дуже розумію те, що ви можете сказати, чи можете ви сказати це як відпрацьований приклад. fyi Дані надходять із сервера, тож після того, як будь-яка форма цього класу буде «зволожена», поля не потрібно буде змінювати, просто використовуються для відображення в інтерфейсі користувача
Blundell

1
DTO означає "об'єкт передачі даних" і посилається на клас даних дуже простої структури, що використовується для переміщення даних з "багатого" доменного шару на верхні шари, такі як інтерфейс користувача, не піддаючи фактичний рівень домену користувальницькому інтерфейсу (дозволяє зміни повинні бути зроблені до домену, не впливаючи на інтерфейс користувача, доки DTO не потрібно змінювати).
KeithS

незмінність не має жодного стосунку до модифікації чи збереження.

4
@JarrodRoberson - ти жартуєш? Якщо клас є незмінним (його неможливо змінити на місці після інстанції), єдиний спосіб змінити дані в рівні даних - це побудувати новий екземпляр, що представляє один і той же запис (той же ПК) з різними членами. Хоча "мутація" методів, що створюють новий екземпляр, може полегшити процес, він все ще має величезний вплив на модифікацію та стійкість.
KeithS

1
@JarrodRoberson Слухайте спільноту. Ви неправі. . Насправді половина коментарів у цій цілій відповіді свідчить про те, що нам потрібна основна школа OO навколо дошки - це огидно ..
Девід Кауден

5

Якщо ви дбаєте лише про підмножину властивостей, і хочете переконатися, що вони не змішуються, створіть два інтерфейси та використовуйте це для розмови з базовим об’єктом.


1
Ви говорите це, але чи можете ви навести приклад, використовуючи Barклас
Blundell

3

Тут може бути корисна модель Builder (або щось близьке до неї).

Наявність непорушних предметів - захоплююча річ, але реальність полягає в тому, що з відображенням на Яві нічого не є справді безпечним ;-).


Я бачу, як це HawaiianPizzaBuilderпрацює, оскільки значення, які йому потрібні, жорстко кодуються. Однак як ви можете використовувати цей шаблон, якщо значення отримані та передані конструктору? У HawaiianPizzaBuilderвсе ще залишиться все, що SpicyPizzaBuilderмає таке нульове можливо. Якщо ви не поєднаєте це з @Jarrods Null Object Pattern. Приклад коду з " BarВи
зрозумієте"

+1 Я використовую програму Builder у подібних випадках, працює як шарм - включаючи, але не обмежуючись, встановлення розумних значень за замовчуванням замість нуля, коли я цього хочу
gnat

3

Ключовим моментом тут є різниця між тим, що таке "бар", і тим, як ви його використовуєте в тому чи іншому контексті.

Штрих - це єдине ціле в реальному світі (або штучний світ, як гра), і лише ОДИН об'єкт об'єкта повинен представляти його. Пізніше, коли ви не створите цей екземпляр із сегмента коду, але завантажите його з конфігураційного файлу чи бази даних, це стане більш очевидним.

(Для того, щоб бути ще езотеричнішим: кожен екземпляр Bar має інший життєвий цикл, ніж об'єкт, який представляє його, коли програма працює. Навіть якщо у вас є вихідний код, який створює цей екземпляр, це означає, що сутність Bar як описано, "існує "у стані спокою у вихідному коді та" пробуджується ", коли цей код фактично створює його в пам'яті ...)

Вибачте за тривалий початок, але сподіваюся, що це дає зрозуміти мою думку. У вас ЄДИН бар клас, який має всі атрибути, які вам коли-небудь знадобляться, і один екземпляр Bar, який представляє кожну сутність Bar. Це правильно у вашому коді і не залежить від того, як ви хочете бачити один і той же екземпляр у різних контекстах .

Останній може бути представлений двома різними інтерфейсами , які містять необхідні методи доступу (getName (), getURL (), getDistance ()), і клас Bar повинен реалізувати обидва. (І, можливо, "відстань" зміниться на "розташування", і getDistance () стане обчисленням з іншого місця розташування :-))

Але створення створене для об'єкта Bar, а не для того, як ви хочете використовувати цю сутність: один конструктор, усі поля.

РЕДАКТИРОВАНО: Я можу написати код! :-)

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

Відповідна модель

Те, що ви шукаєте, найчастіше називають Null Object Pattern. Якщо ім'я вам не подобається, ви можете назвати його тією Undefined Value Patternж семантикою, різною міткою. Іноді така закономірність називається Poison Pill Pattern.

У всіх цих випадках Об'єкт є заміною, або Default Valueзамість null. It doesn't replace the semantic ofнульової but makes it easier to work with the data model in a more predictable way becauseнулі "тепер не повинен бути дійсним станом.

Це шаблон, де ви резервуєте спеціальний екземпляр даного класу, щоб представити інший nullваріант як Default Value. Таким чином, вам не доведеться перевіряти null, ви можете перевірити особистість у відомому NullObjectекземплярі. Ви можете сміливо називати методи на ньому тощо, не турбуючись NullPointerExceptions.

Таким чином ви замінюєте свої nullзавдання на їх представницькі NullObjectекземпляри, і ви закінчите.

Правильний об'єктно-орієнтований аналіз

Таким чином, ви можете мати спільне Interfaceдля поліморфізму і все ще мати захист від необхідності турбуватися про відсутність даних у конкретних реалізаціях інтерфейсу. Таким чином, деякі Barможуть не мати веб-сайту, а деякі можуть не мати даних про місцезнаходження на момент створення. Null Object Patterдозволяє надати значення за замовчуванням для кожного з них, що є markerдля тих даних, які говорять те саме, тут нічого не було надано, не маючи угоди про перевірку на NullPointerExceptionвсі місця.

Правильний об'єктно-орієнтований дизайн

По-перше, є abstractреалізація, яка є супер набором усіх атрибутів, якими є Barі Clubспільні.

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
}

Тоді можна реалізувати вкладені класи цього Establishmentкласу і додати тільки певні речі , які потрібні для кожного з Barі Clubкласів , які не застосовуються до інших.

Наполегливість

Ці об'єкти-заповнювачі, якщо їх правильно сконструйовано, можна прозоро зберігати в базі даних без будь-яких спеціальних операцій.

Майбутнє доказ

Якщо ви вирішили перейти на смугу інверсії управління / залежність вприскування пізніше, ця модель дозволяє легко вводити ці об’єкти маркерів також.


0

Я думаю, проблема полягає в тому, що ви не моделюєте штангу в жодному з цих сценаріїв (а ви моделюєте дві різні проблеми, об'єкти тощо). Якщо я бачу бар класу, я очікую певної функціональності, пов’язаної з напоями, меню, наявними місцями, а ваш об’єкт нічого з цього не має. Якщо я бачу вашу поведінку об'єктів, ви моделюєте інформацію про заклад. Бар - це те, чим ви зараз користуєтесь, але це не властива поведінка, яку вони реалізують. (В іншому контексті: якщо ви моделюєте шлюб, у вас будуть дві змінні екземпляра Person žena; Person чоловік; дружина - це поточна роль, яку ви надаєте цьому об'єкту в цей момент, але об'єкт все ще є Person). Я б зробив щось подібне:

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

Справді не потрібно цього ускладнювати. Вам потрібні два різні види об’єкта? Складіть два класи.

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

Якщо вам потрібно працювати однаково, подумайте про додавання інтерфейсу або використання рефлексії.


@David: О, ні. Вони мають, як, один цілий член даних. ВИМОГА!
DeadMG

без спільного інтерфейсу в цьому рішенні немає поліморфізму , жоден з цих класів не міг би замінити інший цим поганим дизайнерським рішенням. Справа не в тому, що вони не мають однакового набору атрибутів, це те, що за замовчуванням деякі з них є null. Пам'ятати nullозначає відсутність даних , а не відсутність атрибута.

1
@DeadMG Це цілком можливо вправа саме в ідеї згрупування спільних значень в батьківські об'єкти .. Ваше рішення не отримає повного кредиту, якби ви запропонували це в цьому контексті.
Девід Кауден

OP не визначає який - або необхідності взаємозамінності. І як я вже сказав, ви можете просто додати інтерфейс або використовувати відображення, якщо хочете.
DeadMG

@DeadMG Але рефлексія - це як спроба написати мобільний додаток для різних платформ за допомогою одного середовища - це може спрацювати, але це не правильно . Штраф за виклик методу за допомогою відображення десь у 2–50 разів повільніше, ніж звичайний виклик методу. Роздуми - це не все, що
лечить

-1

Моя відповідь будь-кому з таким типом проблеми - розбити її на керовані кроки .

  1. Спочатку просто створіть два класи BarOneта BarTwo(або називайте їх обома, Barале в різних пакетах)
  2. Почніть використовувати свої об’єкти як окремі класи, поки не турбуйтеся про дублювання коду. Ви помітите, коли перейдете (дублюючі методи) від одного до іншого
  3. Ви можете дізнатись, що вони жодним чином не пов’язані між собою, і тому ви повинні запитати себе , чи справді вони обидва,bar якщо не перейменують клас правопорушника на те, що він представляє
  4. Якщо ваш пошук загальних полів або поведінку , яке ви можете отримати interfaceабо superclassз загальним поведінкою
  5. Після того, як у вас є interfaceабо superclassви можете створити builderабо factoryстворити / отримати об'єкти реалізації

(4 і 5 - про що відповідають інші відповіді на це запитання)


-2

Вам потрібен базовий клас, наприклад, Місцезнаходження, яке має ім’я та адресу . Тепер у вас є два класи Bar і BarPreview, які розширюють базовий клас Location . У кожному класі ви ініціалізуєте загальні змінні суперкласу, а потім свої унікальні змінні:

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

І подібне для класу BarPreview ..

Якщо вам краще спати вночі, замініть всі випадки розташування в моєму коді на AnythingYouThinkWouldBeAnAppro primernoNameForAThingThatABarExtendsSuchAsFoodServicePlace - ffs.


ОП хоче, щоб примірник був непорушним , це означає, що все має бути final.

1
Це може спрацювати, вам потрібно finalв поля @David. Barне повинно, extend Locationхоча це не має сенсу. Можливо, Bar extends BaseBarі BarPreview extends BaseBarці імена не дуже добре звучать, хоча я сподівався на щось більш елегантне
Blundell

@JarrodRoberson Я просто замальовую це для нього .. просто додай остаточний, щоб він був непорушним. Це не без розуму. Основна проблема питання ОП полягає в тому, що він не знає, як мати базовий клас і два окремі класи, що розширюють базовий клас. Я просто деталізую це.
Девід Кауден

@Blundell, про що у світі ти говориш? Bar є Місцезнаходження . Просто замініть моє місцезнаходження своїм BaseBar, і це точно те саме. Ви знаєте, що коли один клас розширює інший, клас, який він розширює, не повинен бути названий Base [ClassThatWillExtend] так?
Девід Кауден

1
@DavidCowden, чи можете ви припинити рекламувати свою відповідь як коментар нижче будь-якої іншої відповіді?
marktani
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.