Обзор
Это первая статья из короткой серии, посвященной шаблонам проектирования в 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.");
}
}
На данный момент у нас есть соответствующие классы для создания
AbstractFactory
а также соответствующие классы Factory для этих двух
групп: PetFactory
и HumanFactory
.
Задача AbstractFactory
- это возможность предоставить эти объекты
FactoryProducer
, а не создавать их экземпляры:
public abstract class AbstractFactory {
public abstract Pet getPet(String pet);
public abstract Human getHuman(String human);
}
Прежде чем мы определим класс, который создает экземпляры этих объектов
с помощью AbstractFactory
, нам нужно создать две наши фабрики.
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;
}
}
И теперь с их помощью мы можем создать FactoryProducer
который
возложена ответственность за создание экземпляров соответствующих фабрик
с помощью 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;
}
}
FactoryProducer
String
, FactoryProducer возвращает
AbstractFactory
с запрошенной дочерней фабрикой.
Теперь посмотрим на результат:
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();
}
}
Запустив этот фрагмент кода, нас встречают:
Child is feeding pet irresponsibly.
Dog is eating, woof!
Elder is overfeeding the pet.
Rabbit is eating, squeak!
Строитель
Шаблон Builder используется для пошагового построения конечных объектов для классов с огромным количеством полей или параметров. Это не очень полезно в небольших, простых классах, у которых не так много полей, но сложные объекты трудно читать и поддерживать сами по себе.
Инициализация объекта с более чем несколькими полями с использованием конструктора беспорядочная и подвержена человеческой ошибке.
Выполнение:
Определим класс с несколькими полями:
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.
Чтобы применить его, мы вложим статический класс static Builder
Computer
.
Этот конструктор будет использоваться для создания наших объектов в чистом и удобочитаемом виде, в отличие от приведенного выше примера:
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
мы можем инициализировать объекты Computer
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
}
Теперь, как обычно, давайте определим несколько классов, расширяющих
Employee
:
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!");
}
}
На данный момент у нас есть все необходимое для того, чтобы класс из уровня данных сохранял, обновлял и извлекал этих сотрудников для нас.
Hashtable
будет использоваться для моделирования базы данных, а
предопределенные объекты будут моделировать объекты, полученные с
помощью запросов:
public class EmployeesHashtable {
private static Hashtable<String, Employee> employeeMap = new Hashtable<String, Employee>();
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
Синглтон
Шаблон Singleton обеспечивает существование только одного экземпляра объекта во всей JVM.
Это довольно простой шаблон, и он обеспечивает возможность доступа к этому объекту даже без его создания. Другие шаблоны проектирования используют этот шаблон, такие как шаблоны Abstract Factory, Builder и Prototype, которые мы уже рассмотрели.
Выполнение:
Это довольно простая реализация класса Singleton:
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!
Заключение
При этом все шаблоны Creational Design Patterns в Java полностью покрыты с рабочими примерами.
Если вы хотите продолжить чтение о шаблонах проектирования в Java, следующая статья посвященашаблонам структурного проектирования .