Какой ОО дизайн использовать (есть ли шаблон дизайна)?


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», который является официальным источником данных для одного бара, независимо от того, где эти данные будут использоваться позже. Этот класс должен быть изменяемым, чтобы при необходимости можно было вносить изменения в данные панели и сохранять их.

Однако у вас есть два места, в которых нужны данные этого объекта Bar, и им обоим нужно только подмножество (и вы не хотите, чтобы эти данные были изменены). Обычный ответ - «объект передачи данных» или DTO; POJO (обычный старый Java-объект), содержащий неизменяемые свойства get. Эти DTO могут быть получены путем вызова метода основного объекта домена Bar: "toScenario1DTO ()" и "toScenario2DTO ()"; результаты - гидратированный DTO (это означает, что вам нужно использовать только длинный и сложный конструктор в одном месте).

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

РЕДАКТИРОВАТЬ: предоставить пример:

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

Классы Address, Distance и Url должны быть либо неизменяемыми, либо при использовании в DTO их следует заменять неизменяемыми аналогами.


что означает аббревиатура DTO? Я не совсем понимаю, что вы говорите, пожалуйста, не могли бы вы привести это в рабочий пример К сведению, данные поступают с сервера, поэтому после того, как любая из форм этого класса будет «увлажнена», поля не нужно будет менять, просто они используются для отображения в пользовательском интерфейсе
Blundell,

1
DTO означает «объект передачи данных» и относится к классу данных с очень простой структурой, используемой для перемещения данных из «обогащенного» уровня домена на верхние уровни, такие как пользовательский интерфейс, без предоставления действительного уровня домена пользовательскому интерфейсу (допуская изменения быть внесенным в домен, не влияя на пользовательский интерфейс, если DTO не нужно менять).
KeithS

изменчивость не имеет никакого отношения к изменению или постоянству.

4
@JarrodRoberson - ты шутишь? Если класс является неизменным (его нельзя изменить на месте после создания экземпляра), единственный способ внести изменения в данные на уровне данных - это создать новый экземпляр, представляющий одну и ту же запись (одну и ту же PK) с разными членами. Хотя «мутирующие» методы, создающие новый экземпляр, могут упростить его, он по-прежнему имеет огромное значение для модификации и постоянства.
KeithS

1
@JarrodRoberson Слушайте сообщество. Вы неправы. , Фактически половина комментариев во всем этом ответе показывает, что нам нужно какое-то базовое обучение ОО вокруг доски - это отвратительно ..
Дэвид Кауден

5

Если вы заботитесь только о подмножестве свойств и хотите убедиться, что они не перепутаны, создайте два интерфейса и используйте их для связи с вашим базовым объектом.


1
Вы говорите это, но не могли бы вы привести пример с использованием Barкласса
Бланделл

3

Здесь может быть полезен Образец Строителя (или что-то близкое к нему).

Наличие неизменных объектов - замечательная вещь, но реальность такова, что с Reflection в Java нет ничего действительно безопасного ;-).


Я могу видеть, как HawaiianPizzaBuilderработает, потому что значения, которые ему нужны, жестко закодированы. Однако как вы можете использовать этот шаблон, если значения извлекаются и передаются в конструктор? У HawaiianPizzaBuilderвсе еще были бы все методы получения, которые SpicyPizzaBuilderимеют настолько нулевое, возможно. Если вы не объедините это с @Jarrods Null Object Pattern. Пример кода, с помощью Barкоторого вы получите свою точку зрения
Бланделл,

+1 Я использую строитель в подобных случаях, работает как шарм - включая, но не ограничиваясь, установление разумных значений по умолчанию вместо нуля, когда я этого хочу
комнат

3

Ключевым моментом здесь является разница между тем, что такое «Бар», и тем, как вы используете его в том или ином контексте.

Бар - это единое целое в реальном мире (или искусственном мире, таком как игра), и только ОДИН экземпляр объекта должен представлять его. В любое время позже, когда вы не создадите этот экземпляр из сегмента кода, а загрузите его из файла конфигурации или базы данных, это станет более очевидным.

(Чтобы быть еще более эзотерическим: каждый экземпляр Bar имеет свой жизненный цикл, отличный от объекта, который представляет его при запуске вашей программы. Даже если у вас есть исходный код, который создает этот экземпляр, это означает, что сущность 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 becausenull`, теперь никогда не должно быть допустимым состоянием.

Это шаблон, в котором вы резервируете специальный экземпляр данного класса для представления другой 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классов , которые не применяются к другим.

Упорство

Эти объекты-заполнители, если они построены правильно, могут прозрачно храниться в базе данных без какой-либо специальной обработки.

На будущее

Если вы решили перейти к победе в Inversion of Control / Dependency Injection позже, этот шаблон также позволяет легко вводить эти маркерные объекты.


0

Я думаю, проблема в том, что вы не моделируете Бар в любом из этих сценариев (и вы моделируете две разные проблемы, объекты и т. Д.). Если я увижу бар класса, я бы ожидал, что некоторые функции, связанные с напитками, меню, доступными местами, и ваш объект не имеют ничего подобного. Если я вижу поведение ваших объектов, вы моделируете информацию об учреждении. Бар - это то, для чего вы используете их в данный момент, но это не внутреннее поведение, которое они реализуют. (В другом контексте: если вы моделируете Брак, у вас будет две переменные экземпляра «Личность»; «Личность»; «Жена» - это текущая роль, которую вы отводите этому объекту в данный момент, но объект по-прежнему «Личность»). Я бы сделал что-то вроде этого:

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

Если вам нужно работать с ними одинаково, рассмотрите возможность добавления интерфейса или использования отражения.


@ Дэвид: О нет. У них есть один общий элемент данных. КРАЙНЯЯ НЕОБХОДИМОСТЬ!
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

Вам нужен базовый класс, например, Location, который имеет имя и адрес . Теперь у вас есть два класса 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 ..

Если это заставляет вас спать лучше ночью, замените все экземпляры Location в моем коде на AnythingYouThinkWouldBeAnAppresponNameForAThingThatABarExtendsSuchAsFoodServicePlace - ffs.


ОП хочет, чтобы экземпляр был неизменным , то есть все должно быть final.

1
Это может сработать, вам нужно finalв поля @David. Barне должно, extend Locationхотя это не имеет смысла. Возможно, Bar extends BaseBarи BarPreview extends BaseBarэти названия не очень хорошо звучат, хотя, я надеялся на что-то более элегантное
Бланделл,

@JarrodRoberson Я просто делаю набросок для него ... просто добавлю финал, чтобы он был неизменным. Это не просто. Основная проблема с вопросом ОП заключается в том, что он не знает, как иметь базовый класс и два отдельных класса, расширяющих базовый класс. Я просто детализирую это.
Дэвид Коуден,

@ Бланделл, о чем ты вообще говоришь? Bar является Местоположение . Просто замените мое Location вашим BaseBar, и это точно то же самое. Вы знаете, что когда один класс расширяет другой, класс, который он расширяет, не обязательно должен называться Base [ClassThatWillExtend], верно?
Дэвид Коуден

1
@DavidCowden, можете ли вы прекратить рекламировать свой ответ в виде комментария под каждым другим ответом, пожалуйста?
Марктани
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.