Patterns for Writing Simple Code

О шаблонах

В этой статье речь пойдет о наборе шаблонов ООП, использующих простую композицию, а не наследование.

Большинство шаблонов взяты из книги Gang of Four: Design Patterns. Мы рассмотрим лишь краткое введение в каждый из них, для более подробного ознакомления перейдите по ссылке.

Шаблон 1: Абстрактная Фабрика

Фабрика — это объект, единственной задачей которого является создание других объектов. Их можно создавать разными способами, однако шаблон абстрактной фабрики — наиболее эффективный вариант.

С помощью этого шаблона можно не только изменять сгенерированный или созданный объект во время выполнения, но и саму фабрику. Он также отлично работает с фреймворками, использующими inversion of control, такими как Spring или Unity.

Код выглядит следующим образом:

interface Factory<T> {

T build(Metadata d)

}

class ClientFactory implements Factory<Client> {

Client build(Metadata d) {
        // Build actual object and return      
    }

}

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

Шаблон 2: Делегат

Многие разработчики при работе над определенным проектом (кодом) делегируют работу другим членам команды вместо того, чтобы выполнять ее самостоятельно.

Шаблон делегирования работает по тому же принципу: классы высшего порядка просят классы низшего порядка выполнить работу за них, оставаясь при этом более простыми и зная меньше информации о находящихся ниже структурах.

Код выглядит следующим образом:

interface Validator {
    
    bool validate(Object o)

}

class ValidatorHelper implements Validator {

Set<Validator> delegates;
    
    bool validate(Object o) { 
        for (Validator v : delegates) { 
            if (!v.validate(o)) return false
        }
        return true
    }

}
class RestController {

ValidationHelper helper;

Response addObject(Object o) {
        if (helper.validate(o)) return ErrorResponse

// Normal processing
    }

}

Использование делегатов полезно при работе с валидацией, сортировкой, нормализацией и т. д.

Шаблон 3: Строитель / Именованные параметры

С помощью шаблона строитель можно создать гибкий и расширяемый код без особых усилий и при желании использовать возможности неизменяемости!

Другие языки могут не иметь шаблон строитель (или даже не нуждаться в нем), благодаря наличию именованных параметров в конструкторах со стандартными значениями по умолчанию. Они предоставляют те же возможности: объявление только тех элементов, для которых нужно установить определенное значение.

Код выглядит следующим образом:

class Dto {

String s
    int i

private Dto(String s, int i) {
        this.s = s
        this.i = i
    }

public DtoBuilder builder() {
        return new DtoBuilder()
    }

public static class DtoBuilder {
        
        private String s = "some string"
        private int i = 0

public DtoBuilder withString(String s) {
            this.s = s
            return this
        }

public DtoBuilder withInt(int it) {
            this.i = i
            return this
        }

public Dto build() {
            return new Dto(s, i)
        }
    }

}

Примечание: в Java также используется Lombok для выполнения утомительного процесса написания кода.

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

Шаблон 4: Обогатитель

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

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

interface Enricher<T> {

T enrich(T thing);

}

class HeadersEnricher implements Enricher<Headers> {
 
    Headers enrich(Headers headers) {
        headers.add("x-header", "something")
        return headers
    }

}

Этот шаблон отлично подходит для наполнения объекта новым состоянием. Например, если у вас есть объект, поступающий из потока Kafka, к которому необходимо добавить данные, прежде чем сохранить в хранилище данных.

Читайте также:


Перевод статьи Dan Goslen: My Top 4 Patterns for Writing Simple Code