Рубрики
Uncategorized

Шаблоны создания объектов

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

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

Старый добрый новое

Весь экземпляр объекта в PHP выполняется путем вызова new |/и тип объекта, который мы хотим получить:

$object = new MyClass();

С этого момента переменная $object может использоваться как объект этого типа ( MyClass ) и мы можем попросить Вас выполнить свое собственное поведение или передать его в качестве параметра другим объектам, которые в этом нуждаются. Например:

$line = new InvoiceLine();

$invoice = new Invoice();
$invoice->addLine($line);

$payment = new Payment($invoice);

Во время создания объекта мы можем передать ему различное количество параметров, необходимых для его строительства.

Некоторые объекты запускаются без каких-либо параметров:

$builder = new Builder();

В то время как другим может понадобиться несколько:

$customer = new Customer($name, $surname, $email);

И в некоторых случаях нам понадобится много параметров, но много:

$student = new Student(
    $id,
    $name,
    $surname,
    $mail,
    $parent1,
    $parent2,
    $birthday,
    $street,
    $street_number,
    $location,
    //...
);

Неважно. Во всех случаях мы будем вызывать метод конструктора с помощью |/new .

Метод __construct

В PHP метод __construct//вызывается автоматически, когда мы делаем//new//.

То, что происходит, заключается в том, что объект создается с использованием определения класса как “шаблон”. В остальном метод __construct |/- это еще один метод, в котором у нас есть ссылка на вновь созданный экземпляр в символе $this .

Мы можем получить любое количество параметров в этой функции, которые мы обычно назначаем ее свойствам либо напрямую, либо путем некоторой проверки или преобразования.

Функция __construct |/является правильным моментом, чтобы убедиться, что объект хорошо построен|/. Мы уже говорили в других случаях о том, что это значит, но подводя итог, мы можем сказать, что хорошо построенный объект:

  • Вам назначены все свойства, которые вам обязательно нужны для работы.
  • Значения этих свойств верны в вашем домене.

Например, объект, представляющий электронную почту:

class Email
{
    private $email;

    public function __construct(string $email)
    {
        if (! filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                sprintf('%s is not valid email', $email);
            );
        }
        $this->email = $email;
    }
}

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

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

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

Решение для многих случаев

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

Эти конкретные случаи могут быть сгруппированы в несколько категорий. Или иными словами , эти ситуации вписываются в ряд шаблонов, которые мы называем творческими шаблонами , и что у них есть хорошо проверенные и действительно полезные решения.

Подер создает объект различных форм: именованные конструкторы

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

Первоначально мы могли бы подготовить эту информацию в соответствии с нашим конструктором, но у этого есть некоторые недостатки:

  • Мы должны добавить эту подготовку, которая добавляет шум к Кодексу.
  • Повторите это в разных точках кода.

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

Для этого мы будем использовать статический метод. Статический метод “принадлежит” классу, хотя и не объекту, потому что на самом деле при выполнении статического метода экземпляр объекта не существует.

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

Во – первых, это стандартная конструкция этого типа объекта:

class Coordinates
{
    private $longitude;
    private $latitude;

    public function __construct(float $longitude, float $latitude)
    {
        $this->longitude = $longitude;
        $this->latitude = $latitude;
    }
}

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

$coordinatesFromAPI = [45.23, -12.43];

Мы могли бы сделать следующее:

$coordinatesFromAPI = [45.23, -12.43];

$coordinates = new Coordinates($coordinatesFromAPI[0], $coordinatesFromAPI[1]);

Это неплохо, и в этом случае так просто на самом деле не так уродливо, но давайте посмотрим сейчас с named конструктором:

class Coordinates
{
    private $longitude;
    private $latitude;

    private function __construct(float $longitude, float $latitude)
    {
        $this->longitude = $longitude;
        $this->latitude = $latitude;
    }

    public static function fromAPIArray(array $coordinates): Coordinates
    {
        return new self($coordinates[0], $coordinates[1]);
    }
}

Теперь мы бы написали это так:

$coordinatesFromAPI = [45.23, -12.43];

$coordinates = Coordinates::fromAPIArray($coordinatesFromAPI);

Таким образом, логика создания объекта из формы получения данных управляется код самого класса, и нам не нужно знать, как вы должны подготовить информацию, просто передать ее.

Когда мы используем named constructors, часто делают конструктор частным, чтобы заставить его использовать эти методы. Это не обязательно, хотя это считается хорошей практикой. В случае, если использование стандартного метода конструктора имеет смысл, мы всегда сможем создать named конструктор, который заменит его.

class Coordinates
{
    private $longitude;
    private $latitude;

    private function __construct(float $longitude, float $latitude)
    {
        $this->longitude = $longitude;
        $this->latitude = $latitude;
    }

    public static function fromCoordinates(float $longitude, float $latitude): Coordinates
    {
        return new self($longitude, $latitude);
    }

    public static function fromAPIArray(array $coordinates): Coordinates
    {
        return new self($coordinates[0], $coordinates[1]);
    }
}

Возможность создать экземпляр подкласса иерархии, не зная, какой из них априори: factory method

Часто у нас будет иерархия classes, особенно набор сестринских классов, и вы можете даже время создания экземпляра объекта, во время выполнения, давайте не будем знать, какой из них мы хотим.

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

Представьте себе систему планов платежей за услугу с типичными траншами Бесплатно, Семейный профессионал. Это может быть представлено примерно так:

abstract class PaymentPlan
{
}

class FreePaymentPlan extends PaymentPlan
{
}

class FamilyPaymentPlan extends PaymentPlan
{
}

class ProPaymentPlan extends PaymentPlan
{
}

Наш первый подход, получив ответ от пользователя, состоял бы в том, чтобы использовать структуру if или switch, чтобы решить, какой подкласс создать экземпляр.

switch ($userSelection) {
    case 'free':
        $plan = new FreePaymentPlan();
        break;
    case 'family':
        $plan = new FamilyPaymentPlan();
        break;
    case 'pro';
        $plan = new ProPayment();
        break;
    default:
}

Альтернативой является наличие factory method (метод Factory), который инкапсулирует эту логику. Так же, как named конструктор, это будет статический метод, но есть несколько отличий:

  • Обычно это будет базовый или абстрактный класс.
  • Он возвращает не экземпляр себя, а запрошенный подкласс.
abstract class PaymentPlan
{
    public static function create($userSelection): PaymentPlan
    {
         switch ($userSelection) {
            case 'free':
                return new FreePaymentPlan();
                break;
            case 'family':
                return new FamilyPaymentPlan();
                break;
            case 'pro';
                return new ProPayment();
                break;
            default:
        }   
    }
}

Таким образом, каждый раз, когда нам нужно создать экземпляр объекта PaymentPlan нам больше нечего делать.:

$plan = PaymentPlan::create($userSelection);

Объекты, которые трудно построить: builder

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

Первое приближение пытается создать Parameter objects или value objects, когда это возможно, и облегчить таким образом, работа строителя.

Однако иногда трудность определяется не только количеством параметров, но и отношениями, которые могут существовать между ними.

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

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

И для этого мы будем использовать Builder.

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

Мы говорили о builders в другое время , но не помешает поднять и развить некоторые моменты:

Builder может иметь или не иметь свой собственный конструктор на случай, если ему нужна какая-либо зависимость для выполнения своей работы, хотя часто ему ничего не нужно. Эль-строитель не должен толковать информацию, необходимую для создания конкретного объекта, в частности, для определения параметров, необходимых для того, чтобы выполнить установку медиантов.

$builder = new ComplexObjectBuilder();

$builder->forCustomer($customer);
$builder->bySeller($seller);
$builder->addProduct($product1);
$builder->addProduct($product2);

$complexObject = $builder->build();

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

class ComplexObjectBuilder
{
    private $customer;
    private $seller;
    private $products;

    public function __construct()
    {
        $this->products = [];
    }

    public function forCustomer(Customer $customer)
    {
        $this->customer = $customer;
    }

    public function bySeller(Seller $seller)
    {
        $this->seller = $seller;
    }

    public function addProduct(Product $product)
    {
        array_push($this->product, $product);
    }
}

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

class ComplexObjectBuilder
{
    private $customer;
    private $seller;
    private $products;

    public function __construct()
    {
        $this->products = [];
    }

    public function forCustomer(Customer $customer)
    {
        $this->customer = $customer;
    }

    public function bySeller(Seller $seller)
    {
        $this->seller = $seller;
    }

    public function addProduct(Product $product)
    {
        array_push($this->products, $product);
    }

    public function build()
    {
        $object = new ComplexObject(
            $this->customer,
            $this->seller,
            $this->products
        );

        return $object;
    }
}

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

class ComplexObjectBuilder
{
    private $customer;
    private $seller;
    private $products;
    private $discount;

    public function __construct()
    {
        $this->products = [];
    }

    public function forCustomer(Customer $customer)
    {
        $this->customer = $customer;
    }

    public function bySeller(Seller $seller)
    {
        $this->seller = $seller;
    }

    public function addProduct(Product $product)
    {
        array_push($this->products, $product);
    }

    public function withSpecialDiscount(Discount $discount)
    {
        $this->discount = $discount;
    }

    public function build()
    {
        $object = new ComplexObject(
            $this->customer,
            $this->seller,
            $this->products
        );

        if ($this->discount) {
            $object->withDiscount($this->discount);
        }

        return $object;
    }
}

Сеттеры builder часто предлагают плавный интерфейс, что упрощает управление и позволяет нам выполнять сборку за один шаг.

class ComplexObjectBuilder
{
    private $customer;
    private $seller;
    private $products;
    private $discount;

    public function __construct()
    {
        $this->products = [];
    }

    public function forCustomer(Customer $customer): self
    {
        $this->customer = $customer;

        return $this;
    }

    public function bySeller(Seller $seller): self
    {
        $this->seller = $seller;

        return $this;
    }

    public function addProduct(Product $product): self
    {
        array_push($this->products, $product);

        return $this;
    }

    public function withSpecialDiscount(Discount $discount): self
    {
        $this->discount = $discount;

        return $this;
    }

    public function build()
    {
        $object = new ComplexObject(
            $this->customer,
            $this->seller,
            $this->products
        );

        if ($this->discount) {
            $object->withDiscount($this->discount);
        }

        return $object;
    }
}

Как этот пример:

$complexObject = (new ComplexObjectBuilder())
    ->forCustomer($customer)
    ->bySeller($seller)
    ->addProduct($product1)
    ->addProduct($product2)
    ->withSpecialDiscount($discount)
    ->build()
;

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

Готовые семейства объектов: factory

Named constructors и Builders инкапсулируют создание отдельных объектов. В первом случае, позволяя нам различные способы построения объекта. Во-вторых, перемещение логики построения из класса, когда это особенно сложно.

Кроме того, Factory method позволяет нам создавать экземпляры объектов типа, который мы не знаем заранее. Если логика создания этих различных типов сложна, или даже если диапазон типов объектов относительно широк, предпочтительнее иметь объект, который позаботится об этой задаче. Этот объект является фабрикой.

Фабрика предоставляет методы, которые возвращают объекты, которые мы хотим. Мы можем спроектировать их двумя основными способами:

Методы, которые возвращают объекты указанного типа в качестве параметра.

class UserFactory
{
    public function create(string $userType, $other, $parameters, $needed): User
    {
    }
}

Методы, которые возвращают определенные типы объектов и которые мы можем настроить, передав им параметры.

class UserFactory
{
    public function createAdmin(): Admin
    {
    }

    public function createAuthor(): Student
    {
    }

    public function createEditor(): Teacher
    {
    }
}

Оригинал: “https://dev.to/franiglesias/patrones-de-creacin-de-objetos-1gd4”