Автор оригинала: David Landup.
Обзор
Это первая статья в короткой серии, посвященной шаблонам проектирования на Java .
Модели создания
Шаблоны создания в Java, которые рассматриваются в этой статье, являются:
- Заводской метод/Шаблон
- Абстрактная фабрика
- Строитель
- Прототип
- Синглтон
Заводской способ
Фабричный метод, также часто называемый Фабричным шаблоном, является широко используемым шаблоном проектирования, который управляет созданием объектов.
В этом шаблоне класс Factory создается как родительский класс всех подклассов, принадлежащих определенному логическому сегменту связанных классов.
Точно так же, как SessionFactory
используется для создания, обновления, удаления и управления всеми объектами Session
, так и любая другая фабрика отвечает за свой набор дочерних классов.
Важно отметить, что подклассы не могут быть доступны без использования их соответствующей фабрики. Таким образом, их создание скрыто от клиента и зависит от фабрики.
Реализация:
Давайте построим небольшой, простой проект, чтобы продемонстрировать это.
Мы собираемся определить несколько классов, принадлежащих логическому сегменту, каждый из которых реализует один и тот же интерфейс. Затем мы собираемся создать фабрику для этих объектов.
public interface Animal { void eat(); }
Интерфейс имеет только один метод для удобства представления точки зрения.
Теперь давайте определим несколько классов, реализующих этот интерфейс, каждый по-своему:
public class Dog implements Animal { @Override public void eat() { System.out.println("Dog is eating, woof!"); } } public class Cat implements Animal { @Override public void eat() { System.out.println("Cat is eating, meow!"); } } public class Rabbit implements Animal { @Override public void eat() { System.out.println("Rabbit is eating, squeak!"); } }
Примечание : Эти классы являются отдельными файлами .java , они сгруппированы таким образом для удобства чтения.
Теперь, когда у нас есть группа классов, мы можем назначить для них фабрику:
public class AnimalFactory { public Animal getAnimal(String animal) { if(animal.equals(null)) return null; if(animal.equalsIgnoreCase("Dog")) { return new Dog(); } else if(animal.equalsIgnoreCase("Cat")) { return new Cat(); } else if(animal.equalsIgnoreCase("Rabbit")) { return new Rabbit(); } return null; } }
Таким образом, у нас есть фабрика для создания экземпляров наших объектов заданным фабрикой способом, без прямого контакта с самими объектами.
Теперь давайте понаблюдаем за результатом.
public class Main { public static void main(String[] args) { AnimalFactory animalFactory = new AnimalFactory(); Animal animal = animalFactory.getAnimal("dOg"); animal.eat(); Animal animal2 = animalFactory.getAnimal("CAT"); animal2.eat(); Animal animal3 = animalFactory.getAnimal("raBbIt"); animal3.eat(); } }
Запуск этого фрагмента кода приведет к:
Dog is eating, woof! Cat is eating, meow! Rabbit is eating, squeak!
Если вы хотите прочитать отдельную подробную статью о Шаблоне проектирования заводского метода , мы вам поможем!
Абстрактная фабрика
Шаблон Абстрактная фабрика дизайн основывается на шаблоне Фабрика и действует как высшая фабрика в иерархии. Это представляет собой практику создания фабрики фабрик .
Этот шаблон отвечает за создание всех других фабрик в качестве своих подклассов, точно так же, как фабрики отвечают за создание всех своих собственных подклассов.
Реализация:
Предыдущий пример может быть использован в качестве хорошей основы для этой реализации.
Интерфейс Animal
переименовывается в интерфейс Pet
, и каждая реализация изменяется:
public class Dog implements Pet { @Override public void eat() { System.out.println("Dog is eating, woof!"); } } public class Cat implements Pet { @Override public void eat() { System.out.println("Cat is eating, meow!"); } } public class Rabbit implements Pet { @Override public void eat() { System.out.println("Rabbit is eating, squeak!"); } }
Определен новый интерфейс:
public interface Human { public void feedPet(); }
И, как обычно, несколько конкретных классов реализуют этот интерфейс:
public class Child implements Human { @Override public void feedPet() { System.out.println("Child is feeding pet irresponsibly."); } } public class Adult implements Human { @Override public void feedPet() { System.out.println("Adult is feeding pet responsibly."); } } public class Elder implements Human { @Override public void feedPet() { System.out.println("Elder is overfeeding the pet."); } }
На данный момент у нас есть достаточные классы для создания Абстрактной фабрики
, а также соответствующие Фабрики классы для этих двух групп: Фабрика домашних животных
и Фабрика людей
.
Абстрактная фабрика
заботится о том , чтобы предоставить эти объекты производителю Фабрики
, а не создавать их экземпляры:
public abstract class AbstractFactory { public abstract Pet getPet(String pet); public abstract Human getHuman(String human); }
Прежде чем мы определим класс , который создает экземпляры этих объектов с помощью Абстрактной фабрики
, нам нужно создать две наши фабрики.
public class HumanFactory extends AbstractFactory { @Override Human getHuman(String human) { if(human.equals(null)) return null; if(human.equalsIgnoreCase("chILd")) { return new Child(); } else if(human.equalsIgnoreCase("adult")) { return new Adult(); } else if(human.equalsIgnoreCase("elDeR")) { return new Elder(); } return null; } @Override Pet getPet(String pet) { // don't implement return null; }
public class PetFactory extends AbstractFactory { @Override public Pet getPet(String pet) { if(pet.equals(null)) return null; if(pet.equalsIgnoreCase("Dog")) { return new Dog(); } else if(pet.equalsIgnoreCase("Cat")) { return new Cat(); } else if(pet.equalsIgnoreCase("Rabbit")) { return new Rabbit(); } return null; } @Override Human getHuman(String human) { //don't implement return null; } }
И теперь, с их помощью, мы можем создать Производителя фабрики
, на которого возложена ответственность за создание соответствующих фабрик, с помощью AbstractFactory
:
public class FactoryProducer { public static AbstractFactory getFactory(String factory) { if(factory.equalsIgnoreCase("Human")) { return new HumanFactory(); } else if(factory.equalsIgnoreCase("Pet")) { return new PetFactory(); } return null; } }
Передавая Строку
, производитель Фабрики
возвращает Абстрактную фабрику
с запрошенной дочерней фабрикой.
Теперь давайте понаблюдаем за результатом:
public class Main { public static void main(String[] args) { AbstractFactory humanFactory = FactoryProducer.getFactory("Human"); AbstractFactory petFactory = FactoryProducer.getFactory("Pet"); Human human = humanFactory.getHuman("Child"); human.feedPet(); Pet pet = petFactory.getPet("Dog"); pet.eat(); Human human2 = humanFactory.getHuman("Elder"); human2.feedPet(); Pet pet2 = petFactory.getPet("Rabbit"); pet2.eat(); } }
Git Essentials
Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!
Запустив этот фрагмент кода, мы получаем:
Child is feeding pet irresponsibly. Dog is eating, woof! Elder is overfeeding the pet. Rabbit is eating, squeak!
Строитель
Шаблон компоновщика используется для пошагового создания конечных объектов для классов с огромным количеством полей или параметров. Это не очень полезно в небольших простых классах, в которых не так много полей, но сложные объекты трудно читать и поддерживать сами по себе.
Инициализация объекта с более чем несколькими полями с помощью конструктора является беспорядочной и подвержена человеческим ошибкам.
Реализация:
Давайте определим класс с несколькими полями:
public class Computer { private String computerCase; private String CPU; private String motherboard; private String GPU; private String HDD; private String operatingSystem; private int powerSupply; private int amountOfRAM; public Computer(String computerCase, String CPU, String motherboard, String GPU, String HDD, String operatingSystem, int powerSupply, int amountOfRAM) { this.computerCase = computerCase; this.CPU = CPU; this.motherboard = motherboard; this.GPU = GPU; this.HDD = HDD; this.operatingSystem = operatingSystem; this.powerSupply = powerSupply; this.amountOfRAM = amountOfRAM; } //getters and setters }
Проблема очевидна – даже для такого маленького, простого класса, как этот, требуется большой и грязный конструктор.
Классы легко могут иметь значительно больше полей, чем это, что породило шаблон проектирования Builder.
Чтобы применить его, мы установим статический конструктор
класс в классе Компьютер
.
Этот конструктор будет использоваться для создания наших объектов в чистом и удобочитаемом виде, в отличие от приведенного выше примера:
public class Computer { public static class Builder { private String computerCase; private String CPU; private String motherboard; private String GPU; private String HDD; private String operatingSystem; private int powerSupply; private int amountOfRAM; public Builder withCase(String computerCase) { this.computerCase = computerCase; return this; } public Builder withCPU(String CPU) { this.CPU = CPU; return this; } public Builder withMotherboard(String motherboard) { this.motherboard = motherboard; return this; } public Builder withGPU(String GPU) { this.GPU = GPU; return this; } public Builder withHDD(String HDD) { this.HDD = HDD; return this; } public Builder withOperatingSystem(String operatingSystem) { this.operatingSystem = operatingSystem; return this; } public Builder withPowerSupply(int powerSupply) { this.powerSupply = powerSupply; return this; } public Builder withAmountOfRam(int amountOfRAM) { this.amountOfRAM = amountOfRAM; return this; } public Computer build() { Computer computer = new Computer(); computer.computerCase = this.computerCase; computer.CPU = this.CPU; computer.motherboard = this.motherboard; computer.GPU = this.GPU; computer.HDD = this.HDD; computer.operatingSystem = this.operatingSystem; computer.powerSupply = this.powerSupply; computer.amountOfRAM = this.amountOfRAM; return computer; } } private Computer() { //nothing here } //fields //getters and setters }
Этот вложенный класс имеет те же поля, что и класс Computer
, и использует их для создания самого объекта.
Конструктор Computer
сделан закрытым, так что единственный способ инициализировать его-это использовать класс Builder
.
С помощью Builder
все настройки, мы можем инициализировать Компьютер
объекты:
public class Main { public static void main(String[] args) { Computer computer = new Computer.Builder() .withCase("Tower") .withCPU("Intel i5") .withMotherboard("MSI B360M-MORTAR") .withGPU("nVidia Geforce GTX 750ti") .withHDD("Toshiba 1TB") .withOperatingSystem("Windows 10") .withPowerSupply(500) .withAmountOfRam(8) .build(); } }
Это гораздо более чистый и подробный способ, чем писать:
public class Main { public static void main(String[] args) { Computer computer = new Computer("Tower", "Intel i5", "MSI B360M-MORTAR", "nVidia GeForce GTX 750ti, "Toshiba 1TB", "Windows 10", 500, 8); } }
Если вы хотите прочитать отдельную, подробную статью о шаблоне проектирования Builder , мы вам поможем!
Прототип
Шаблон прототипа используется в основном для минимизации затрат на создание объектов, обычно когда крупномасштабные приложения создают, обновляют или извлекают объекты, которые требуют больших ресурсов.
Это делается путем копирования объекта после его создания и повторного использования копии объекта в последующих запросах, чтобы избежать выполнения другой операции, требующей больших ресурсов. Это зависит от решения разработчика, будет ли это полная или неглубокая копия объекта, хотя цель одна и та же.
Реализация:
Поскольку этот шаблон клонирует объекты, было бы уместно определить для них класс:
// to clone the object, the class needs to implement Cloneable public abstract class Employee implements Cloneable { private String id; protected String position; private String name; private String address; private double wage; abstract void work(); public Object clone() { Object clone = null; try { clone = super.clone(); } catch(CloneNotSupportedException ex) { ex.printStackTrace(); } return clone; } //getters and setters }
Теперь, как обычно, давайте определим несколько классов, которые расширяют Сотрудник
:
public class Programmer extends Employee { public Programmer() { position = "Senior"; } @Override void work() { System.out.println("Writing code!"); } } public class Janitor extends Employee { public Janitor() { position = "Part-time"; } @Override void work() { System.out.println("Cleaning the hallway!"); } } public class Manager extends Employee { public Manager() { position = "Intern"; } @Override void work() { System.out.println("Writing a schedule for the project!"); } }
На данный момент у нас есть все необходимое для класса на уровне данных, чтобы сохранять, обновлять и извлекать этих сотрудников для нас.
Хэш-таблица
будет использоваться для имитации базы данных, а предопределенные объекты будут имитировать объекты, полученные с помощью запросов:
public class EmployeesHashtable { private static HashtableemployeeMap = new Hashtable (); public static Employee getEmployee(String id) { Employee cacheEmployee = employeeMap.get(id); // a cast is needed because the clone() method returns an Object return (Employee) cacheEmployee.clone(); } public static void loadCache() { // predefined objects to simulate retrieved objects from the database Programmer programmer = new Programmer(); programmer.setId("ETPN1"); employeeMap.put(programmer.getId(), programmer); Janitor janitor = new Janitor(); janitor.setId("ETJN1"); employeeMap.put(janitor.getId(), janitor); Manager manager = new Manager(); manager.setId("ETMN1"); employeeMap.put(manager.getId(), manager); } }
Чтобы понаблюдать за результатом:
public class Main { public static void main(String[] args) { EmployeesHashtable.loadCache(); Employee cloned1 = (Employee) EmployeesHashtable.getEmployee("ETPN1"); Employee cloned2 = (Employee) EmployeesHashtable.getEmployee("ETJN1"); Employee cloned3 = (Employee) EmployeesHashtable.getEmployee("ETMN1"); System.out.println("Employee: " + cloned1.getPosition() + " ID:" + cloned1.getId()); System.out.println("Employee: " + cloned2.getPosition() + " ID:" + cloned2.getId()); System.out.println("Employee: " + cloned3.getPosition() + " ID:" + cloned3.getId()); } }
Запуск этого фрагмента кода приведет к:
Employee: Senior ID:ETPN1 Employee: Part-time ID:ETJN1 Employee: Intern ID:ETMN1
Синглтон
Одноэлементный шаблон обеспечивает существование только одного экземпляра объекта во всей JVM.
Это довольно простой шаблон, и он обеспечивает возможность доступа к этому объекту даже без его создания. Другие шаблоны проектирования используют этот шаблон, например шаблоны Абстрактной фабрики, конструктора и прототипа, которые мы уже рассмотрели.
Реализация:
Это довольно простая реализация одноэлементного класса:
public class SingletonClass { private static SingletonClass instance = new SingletonClass(); private SingletonClass() {} public static SingletonClass getInstance() { return instance; } public void showMessage() { System.out.println("I'm a singleton object!"); } }
Этот класс создает статический объект сам по себе, который представляет глобальный экземпляр.
Предоставляя частный конструктор, класс не может быть создан.
Статический метод getInstance()
используется в качестве глобальной точки доступа для остальной части приложения.
В этот класс можно добавить любое количество общедоступных методов, но в этом учебнике нет необходимости.
Таким образом, наш класс отвечает всем требованиям, чтобы стать Синглетоном .
Давайте определим некоторый код, который извлекает этот объект и запускает метод:
public class Main { public static void main(String[] args) { SingletonClass singletonClass = SingletonClass.getInstance(); singletonClass.showMessage(); } }
Выполнение этого кода приведет к:
I'm a singleton object!
Вывод
При этом все Шаблоны творческого проектирования в Java полностью описаны с рабочими примерами.
Если вы хотите продолжить чтение о шаблонах проектирования в Java, в следующей статье рассматриваются Структурные шаблоны проектирования .