Рубрики
Без рубрики

Шаблоны творческого проектирования на Java

Автор оригинала: 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 Hashtable employeeMap = 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, в следующей статье рассматриваются Структурные шаблоны проектирования .