Въведение в моделите за творчески дизайн

1. Въведение

В софтуерното инженерство Design Pattern описва установено решение на най-често срещаните проблеми в софтуерния дизайн. Той представлява най-добрите практики, разработени в продължение на дълъг период от време чрез опити и грешки от опитни разработчици на софтуер.

Дизайнерските модели придобиха популярност, след като книгата „Дизайнерски модели: Елементи на многократно използвания обектно-ориентиран софтуер“ беше публикувана през 1994 г. от Ерих Гама, Джон Влисидес, Ралф Джонсън и Ричард Хелм (известен също като Gang of Four или GoF).

В тази статия ще изследваме моделите на креативния дизайн и техните типове. Също така ще разгледаме някои примерни кодове и ще обсъдим ситуациите, когато тези модели отговарят на нашия дизайн.

2. Модели на творчески дизайн

Шаблоните за творчески дизайн се занимават с начина, по който се създават обектите. Те намаляват сложността и нестабилността чрез създаване на обекти по контролиран начин.

Най- новият оператор често се смята за вреден, тъй като разпръсква обекти навсякъде по молбата. С течение на времето може да стане предизвикателство да се промени изпълнението, защото класовете стават тясно свързани.

Креативните дизайнерски модели решават този проблем, като отделят изцяло клиента от действителния процес на инициализация.

В тази статия ще обсъдим четири типа шаблон за творчески дизайн:

  1. Сингълтън - гарантира, че в приложението съществува само един екземпляр на обект
  2. Фабричен метод - Създава обекти от няколко свързани класа, без да посочва точния обект, който трябва да бъде създаден
  3. Abstract Factory - Създава семейства от свързани зависими обекти
  4. Builder - конструира сложни обекти, използвайки подход стъпка по стъпка

Нека сега обсъдим всеки от тези модели в детайли.

3. Модел на единичен дизайн

Шаблонът за дизайн на Singleton има за цел да поддържа проверка при инициализация на обекти от определен клас, като гарантира, че само един екземпляр на обекта съществува в Java Virtual Machine.

Класът Singleton също предоставя една уникална глобална точка за достъп до обекта, така че всяко следващо повикване към точката за достъп връща само този конкретен обект.

3.1. Пример за единичен модел

Въпреки че моделът Singleton е въведен от GoF, известно е, че оригиналната реализация е проблематична в многонишкови сценарии.

И така, тук ще следваме по-оптимален подход, който използва статичен вътрешен клас:

public class Singleton { private Singleton() {} private static class SingletonHolder { public static final Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } }

Тук създадохме статичен вътрешен клас, който съдържа екземпляра на класа Singleton . Той създава екземпляра само когато някой извика метода getInstance () , а не когато е зареден външният клас.

Това е широко използван подход за клас Singleton, тъй като не изисква синхронизация, безопасен е за нишки, налага мързелива инициализация и има сравнително по-малко шаблон.

Също така имайте предвид, че конструкторът има модификатор за частен достъп. Това е изискване за създаване на Singleton, тъй като публичен конструктор би означавало, че всеки може да има достъп до него и да започне да създава нови екземпляри.

Не забравяйте, че това не е оригиналната реализация на GoF. За оригиналната версия, моля, посетете тази свързана статия Baeldung за Singletons в Java.

3.2. Кога да се използва Singleton Design Pattern

  • За ресурси, които са скъпи за създаване (като обекти за свързване с база данни)
  • Добра практика е да държите всички регистратори като единични, което увеличава производителността
  • Класове, които осигуряват достъп до конфигурационни настройки за приложението
  • Класове, които съдържат ресурси, които са достъпни в споделен режим

4. Шаблон за проектиране на фабричен метод

Шаблонът за фабричен дизайн или Шаблонът за фабричен метод е един от най-използваните дизайнерски модели в Java.

Според GoF този модел „дефинира интерфейс за създаване на обект, но нека подкласовете решават кой клас да бъде създаден. Фабричният метод позволява отлагане на инстанция на клас за подкласове ”.

Този модел делегира отговорността за инициализиране на клас от клиента към определен фабричен клас чрез създаване на тип виртуален конструктор.

За да постигнем това, разчитаме на фабрика, която ни предоставя обектите, скривайки действителните подробности за изпълнението. Достъпът до създадените обекти се извършва чрез общ интерфейс.

4.1. Пример за модел на проектиране на фабричен метод

В този пример ще създадем интерфейс Polygon, който ще бъде реализиран от няколко конкретни класа. За извличане на обекти от това семейство ще се използва PolygonFactory :

Нека първо създадем интерфейса Polygon :

public interface Polygon { String getType(); }

След това ще създадем няколко реализации като Square , Triangle и др. , Които реализират този интерфейс и връщат обект от тип Polygon .

Сега можем да създадем фабрика, която приема броя на страните като аргумент и връща подходящото изпълнение на този интерфейс:

public class PolygonFactory { public Polygon getPolygon(int numberOfSides) { if(numberOfSides == 3) { return new Triangle(); } if(numberOfSides == 4) { return new Square(); } if(numberOfSides == 5) { return new Pentagon(); } if(numberOfSides == 7) { return new Heptagon(); } else if(numberOfSides == 8) { return new Octagon(); } return null; } }

Забележете как клиентът може да разчита на тази фабрика, за да ни даде подходящ многоъгълник , без да се налага да инициализира обекта директно.

4.2. Кога да се използва шаблон за проектиране на фабричен метод

  • Когато се очаква внедряването на интерфейс или абстрактен клас да се променя често
  • Когато текущата реализация не може удобно да побере нова промяна
  • Когато процесът на инициализация е сравнително прост и конструкторът изисква само няколко параметри

5. Abstract Factory Design Pattern

In the previous section, we saw how the Factory Method design pattern could be used to create objects related to a single family.

By contrast, the Abstract Factory Design Pattern is used to create families of related or dependent objects. It's also sometimes called a factory of factories.

For a detailed explanation, check out our Abstract Factory tutorial.

6. Builder Design Pattern

The Builder Design Pattern is another creational pattern designed to deal with the construction of comparatively complex objects.

When the complexity of creating object increases, the Builder pattern can separate out the instantiation process by using another object (a builder) to construct the object.

This builder can then be used to create many other similar representations using a simple step-by-step approach.

6.1. Builder Pattern Example

The original Builder Design Pattern introduced by GoF focuses on abstraction and is very good when dealing with complex objects, however, the design is a little complicated.

Joshua Bloch, in his book Effective Java, introduced an improved version of the builder pattern which is clean, highly readable (because it makes use of fluent design) and easy to use from client's perspective. In this example, we'll discuss that version.

This example has only one class, BankAccount which contains a builder as a static inner class:

public class BankAccount { private String name; private String accountNumber; private String email; private boolean newsletter; // constructors/getters public static class BankAccountBuilder { // builder code } } 

Note that all the access modifiers on the fields are declared private since we don't want outer objects to access them directly.

The constructor is also private so that only the Builder assigned to this class can access it. All of the properties set in the constructor are extracted from the builder object which we supply as an argument.

We've defined BankAccountBuilder in a static inner class:

public static class BankAccountBuilder { private String name; private String accountNumber; private String email; private boolean newsletter; public BankAccountBuilder(String name, String accountNumber) { this.name = name; this.accountNumber = accountNumber; } public BankAccountBuilder withEmail(String email) { this.email = email; return this; } public BankAccountBuilder wantNewsletter(boolean newsletter) { this.newsletter = newsletter; return this; } public BankAccount build() { return new BankAccount(this); } } 

Notice we've declared the same set of fields that the outer class contains. Any mandatory fields are required as arguments to the inner class's constructor while the remaining optional fields can be specified using the setter methods.

This implementation also supports the fluent design approach by having the setter methods return the builder object.

Finally, the build method calls the private constructor of the outer class and passes itself as the argument. The returned BankAccount will be instantiated with the parameters set by the BankAccountBuilder.

Let's see a quick example of the builder pattern in action:

BankAccount newAccount = new BankAccount .BankAccountBuilder("Jon", "22738022275") .withEmail("[email protected]") .wantNewsletter(true) .build();

6.2. When to Use Builder Pattern

  1. When the process involved in creating an object is extremely complex, with lots of mandatory and optional parameters
  2. When an increase in the number of constructor parameters leads to a large list of constructors
  3. When client expects different representations for the object that's constructed

7. Conclusion

В тази статия научихме за моделите на креативен дизайн в Java. Също така обсъдихме техните четири различни типа, т.е. Singleton, Factory Method, Abstract Factory и Builder Pattern, техните предимства, примери и кога трябва да ги използваме.

Както винаги, пълните кодови фрагменти са достъпни в GitHub.