Должен ли/могу ли я использовать абстрактную фабрику Java (или фабричный метод) в этой ситуации?

В упражнении по программированию я хочу создать программное обеспечение Java для гипотетического магазина: у меня есть суперкласс User и три подкласса: администратор (который может управлять каталогом продуктов и списком клиентов), клиент (который выбирает продукты из каталога и список клиентов). система создает список, который будет показан сотруднику для продолжения оплаты) и сотруднику (который помогает клиенту с оплатой и, если нет проблем, наконец, отправляет клиенту электронное письмо). Очевидно, что это функции, которые я только что придумал, чтобы оправдать наличие трех разных типов пользователей.

У пользователя есть атрибуты: идентификатор, адрес электронной почты, пароль. Атрибутами администратора являются: имя, фамилия, зарплата, номер телефона. Атрибуты сотрудника: зарплата. Атрибутами клиента являются: имя, фамилия, дата рождения, профессия, место жительства.

Очевидно, где-то в программном коде мне нужно создать новые экземпляры для каждого типа пользователей. Почему и как мне следует использовать шаблон создания (абстрактный фабричный/фабричный метод) вместо параметризованных конструкторов трех подклассов?

Думаю, в этом нет необходимости, но вот код:

public class User{

private Integer id;
private String email;
private String password;

public User(Integer id, String email, String password) {
    this.id = id;
    this.email = email;
    this.password = password;
}
//getter-setter
}
    public class Customer extends User{
    private String firstName;
    private String lastName;
    private String dateOfBirth;
    private String profession;
    private String residence;

    public Customer(Integer id, String email, String password, String firstName, String lastName, String dateOfBirth, String profession, String residence) {
        super(id, email, password);
        this.firstName= firstName;
        this.lastName= lastName;
        this.dateOfBirth= dateOfBirth;
        this.profession = profession;
        this.residence = residence;
    }
    //getter-setter
}
//same for administrator and employee, just with different attributes

«Как» и «должно», вероятно, основаны на мнениях и не подходят для SO. В реальном приложении я ожидаю, что эти классы будут считываться из постоянного хранилища/базы данных, поэтому я думаю, что оба шаблона немного чреваты. Посмотрите корпоративный шаблон DAO/DTO, он, вероятно, более «реальный».

markspace 18.05.2024 19:15

@markspace Я уже использовал классы DAO для доступа к базе данных и экземплярам импорта, но мне было интересно, смогу ли я вообще использовать эти шаблоны. Я хотел бы подчеркнуть, что для меня это упражнение, поэтому я не просто пытаюсь создать работающее программное обеспечение, но и, когда это возможно, использовать шаблоны проектирования.

Alex 18.05.2024 19:25

Если бы вы могли показать, как взаимодействуют эти шаблоны и шаблон DAO, я думаю, это помогло бы. Думаю, сейчас я недостаточно понимаю, что вы на самом деле делаете, чтобы сказать что-нибудь полезное.

markspace 18.05.2024 19:30

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

Anonymous 18.05.2024 19:33
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
4
99
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

На фундаментальном уровне между конструктором и статическим методом почти нет разницы. Оба они не требуют приемника и никоим образом не участвуют в иерархии типов. Последний аргумент, безусловно, лучшая причина использовать фабрики, но он также и самый подробный, поэтому я оставлю его напоследок.

Ресиверы

При обычном (нестатическом, неконструкторском) вызове метода вам нужен получатель: объект, для которого вы вызываете метод. Например:

String x = "hello";
toLowerCase(); // not valid java
x.toLowerCase(); // this is fine!

Вам разрешено опускать получателя, только если в текущем контексте есть какое-то значение this, которое будет работать как получатель:

void foo() {
  bar(); // no receiver?
}

void bar() {}

Это потому, что компилятор знает, что вы хотели написать this.bar(), и если вы проверите файл класса, например. с javap вы заметите, что байт-код между написанием bar() и this.bar() здесь будет полностью идентичен во всех отношениях.

Конструкторам не нужен приёмник1. статическим методам не нужен приемник.

Но и эта разница по большей части несущественна. В конце концов, в чем разница между получателем и аргументом метода? Какая значимая разница в:

class StringUtils {
  static String toLowerCase(String in) {
    ...
  }
}

и

class String  {
  String toLowerCase() {
    ...
  }
}

В принципе никакого. На уровне JVM/класса его нет — по крайней мере, в этом отношении. На уровне JVM/класса получатели являются параметрами метода. Java компилируется x.someInstanceMethod() так же, как и someStaticMethod(x): помещая x в стек и затем добавляя байт-код для вызова метода. За исключением одной детали: задействованный байт-код другой; например, методы («обычные» - нет static) это INVOKEVIRTUAL - для статического метода это INVOKESTATIC (и для конструкторов это INVOKESPECIAL, но фактически нет разницы между статическим и специальным для всех намерений и целей). Это важно и будет рассмотрено в последнюю очередь, поскольку дело доходит до иерархии типов.

Так что эта разница (приемники) нам не поможет. На самом деле мы не можем сделать с фабриками ничего такого, чего мы не могли бы сделать с конструкторами или наоборот, за исключением тривиально очевидного момента, поэтому давайте сначала разберемся с этим.

Тип принудительного возврата

Конструкторы:

  • ДОЛЖЕН возвращать точный тип класса, в котором вы пишете конструктор - конструктор в классе Apple обязательно должен возвращать объект типа Apple - он не может возвращать Fruit (ну, все яблоки - это фрукты, но если вы вызываете .getClass() на вещь, которую вы сделаете, это будет яблоко. Вы не можете вернуть какой-либо другой фрукт, не являющийся яблоком), и он также не может вернуть GoldenDelicious.
  • Это будет новый объект.

Окончательно! Мы нашли то, что фабрики могут предложить существенное улучшение по сравнению с конструкторами!

Фабрики способны возвращать разные типы вещей, в которых они находятся, и могут возвращать существующие вещи вместо того, чтобы заставлять их создавать новые. Таким образом:

  • ИСПОЛЬЗУЙТЕ ФАБРИКУ, КОГДА: вам нужен метод, который возвращает разные типы в зависимости от контекста. Например, если вам нужен единственный метод, который может возвращать, в зависимости от необходимости, Customer, Admin или Employee, то фабрика может это сделать, а конструктор — нет. Это не так полезно, как кажется: тип возвращаемого значения этого фабричного метода должен быть User, что означает, что вы не можете вызывать какие-либо методы, специфичные для типа (например, getPhoneNumber(), который есть только у администраторов).

  • КОГДА ИСПОЛЬЗУЙТЕ ФАБРИКУ: Вы хотите вернуть вещи, которые не обязательно являются свежеприготовленными. Например, если создание стоит довольно дорого, но вы можете легко кэшировать значения. Скажем, у вас есть объект, который представляет все телефонные номера в заданном коде города, который создается путем доступа к некоторому API телефонной книги. Для этого требуется много времени и много сетевых ресурсов; возможно, вы захотите внедрить систему кэширования (если система только что проверила все номера телефонов для кода города 345, и кто-то хочет создать еще один объект AreaCodeLookupTable для 345, возможно, просто верните тот, который у нас уже есть, вместо того, чтобы снова пинговать этот API) .

Стиль

В целях тестирования и в качестве принципа стиля, который довольно распространен в экосистеме Java, Java-программисты, как правило, не любят делать много вещей в конструкторах. Однако объективно плохо иметь исходный код, в котором можно получить ссылку на объект, находящийся в «недействительном» состоянии: потому что теперь весь код должен учитывать, что ему следует делать, столкнувшись с таким объектом. Гораздо проще, если наличие ссылки подразумевает, что она находится в хорошо документированном известном состоянии.

Чтобы решить эту коллизию (конструкторы не должны делать многого, но также не должно быть возможности наблюдать объекты в недопустимом состоянии - это взаимоисключающие требования!), вы делаете конструктор простым, но private (или пакет приватным), и добавьте статический метод, который создает объект и делает его действительным и возвращает его только в том случае, если это возможно (в противном случае выдается исключение). Теперь у нас есть пирог, и мы можем его съесть: наш конструктор прост, но никакой код за пределами этого файла/этого пакета не может использовать его для создания объекта в состоянии, которое мы не хотим раскрывать или даже документировать.

ИСПОЛЬЗУЙТЕ ФАБРИКУ, КОГДА: Конструктор сложен, т.е. состоит из нескольких этапов вещей, которые вы предпочитаете тестировать отдельно, и вы чувствуете, что стиль «конструкторы должны оставаться простыми» важен. Рассказ о том, хорошая ли это идея с точки зрения стиля, по большей части выходит за рамки задачи Stack Overflow. Просто знайте, что об этом упоминают многие руководства по стилю.

Иерархия типов

Окончательно. Мы добираемся до настоящей сути фабрик.

статические методы просто не «играют» в игру с иерархией. Совсем. Конструкторы тоже. Нет смысла говорить о том, что «этот статический метод переопределяет тот». Это работает не так: тип, для которого вы вызываете статический метод, решает, какой из них вызывается, во время компиляции. Напротив, если вы вызываете метод экземпляра объекта, вы получаете наиболее конкретную версию:

List<String> x = new ArrayList<>();
x.get(5);

Это вызывает метод get ArrayList. Тот факт, что x принадлежит к типу List, не имеет значения. Речь идет не о типе переменной, а о типе объекта, на который указывает переменная (или, на языке Java, «ссылка». Potayto, Potahto: «Указывает на» и «ссылки» — это одна и та же идея).

статические методы и конструкторы просто не делают этого и не могут быть созданы для этого.

Следовательно, если вы хотите применить понятие иерархии типов к проблемам, влияющим на весь класс, потребуются фабрики. В этом смысле фабрики — не совсем правильное название: одна чрезвычайно распространенная «общеклассовая проблема», которую вы, возможно, захотите использовать в иерархии типов, — это «создавать новые экземпляры», откуда и происходит название «фабрика». Но это не единственное, что можно применить к классу в целом.

Во многих языках программирования у вас есть один сопутствующий объект, который может реализовывать интерфейсы, расширять другие классы и т. д. Java не такая — поэтому вы сами изобретаете это колесо:

interface Fruit {}

interface FruitFactory<F extends Fruit> {
  public F create();

  public String getFruitType();
}

class AvocadoFactory implements FruitFactory<Avocado> {
  public static final AvocadoFactory INSTANCE = new AvocadoFactory();

  public Avocado create() { return new Avocado(); }
  public String getFruitType() { return "berry"; }
}

class Avocado extends Fruit {}

class GroceryStore {
  FruitFactory<?> specialOfTheDay = ...;
}

Ключевой момент здесь: тип авокадо высечен на камне на основе всего «типа»: ВСЕ авокадо — это ягоды. По определению. Следовательно, «странно» помещать методы экземпляра в класс Avocado для его типа — все авокадо имеют один и тот же тип, но методы экземпляра подразумевают, что это свойство может меняться в зависимости от того, какое авокадо у вас есть. В отличие от «зрелости» или «веса» — одно авокадо не будет весить так же, как другое, так что это будет свойство экземпляра.

Я мог бы просто сделать:

class Avocado {
  public static String getFruitType() { "return berry"; }
}

(Ключевой момент: static. РЕДАКТИРОВАНИЕ: я забыл static — ключ к этому фрагменту, упс).

Но проблема в том, что это не играет с иерархией. Я не могу написать код, который принимает любой фрукт в качестве концепции (не отдельный фрукт, готовый к употреблению, нет, категорию фруктов. Ни «яблоко», ни «концепцию всех яблок») и делает что-то интересное на основе свойств этой концепции. в целом, например, тип плода.

Фабрики делают это возможным.

ИСПОЛЬЗУЙТЕ ФАБРИКУ, КОГДА: вам нужно, чтобы вся категория сама была частью иерархии типов; например, вам нужен интерфейс для описания этой категории.

Обратите внимание, что в этой фабрике нет никаких static, кроме поля INSTANCE. Потому что мы не хотим static: Нам нужна иерархия типов, и ни конструкторы, ни статические методы не связаны с иерархией типов. У вас есть отдельный класс, который следует своей собственной иерархии «фабрики», и, как правило, это очень простой способ получить единственный существующий экземпляр этой фабрики.

Так нужен ли вам здесь завод?

Кому ты рассказываешь. Я предоставил все 4 причины, по которым вам следует использовать одну. Если ни одна из этих причин не применима, вам почти наверняка следует просто написать конструктор. Похоже, что ни один из четырех вариантов не подходит, но вам виднее.


[1] Конструкторам нестатических внутренних классов он действительно нужен. нестатические внутренние элементы довольно странные, и это сделало бы этот ответ значительно более длинным. Когда речь идет о таких классах, это еще не все, но суть этого ответа существенно не изменится.

Очень обстоятельный ответ. Большая часть этого обсуждения, похоже, подкрепляет и расширяет описание Джоша Блоха статических фабричных методов.

jaco0646 18.05.2024 20:05

Это очень глубокий и полезный ответ, большое спасибо.

Alex 18.05.2024 20:05

Другие вопросы по теме