Мобильная
версия

Абстрактная фабрика

Дата: Категория: Паттерны проектирования

Этой статьей я положу начало серии статей "Паттерны проектирования".

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

"... порождающий шаблон проектирования, позволяющий изменять поведение системы, варьируя создаваемыми объектами, при этом сохраняя интерфейсы... "
(c) Wikipedia

Поведения системы? Создаваемые объекты? Интерфейсы? Что?

Постараемся разобраться на пальцах.

К примеру у нас есть кот и пес. У них много различий, но и много общего - например они умеют издавать звуки, и ходить (пока ограничимся только 2-мя общими свойствами)
Опишем все это дело в виде классов

class cat{
    function voice(){} // подавать голос
    function walk(){} // ходить
}
class dog{
    function voice(){} // подавать голос
    function walk(){} // ходить
}

Тут думаю ничего пояснений не требует - все общие свойства мы реализовали в классах.

Теперь нам нужно сделать так чтобы кот и пес подали голос

$cat = new cat;
$cat->voice();
$dog = new dog;
$dog->voice();

Тут выполняются методы voice классов cat и dog. Тоже все просто, поэтому дополним тем, что после того как они подали голос им нужно куда-то уйти (метод walk)

$cat = new cat;
$cat->voice();
$cat->walk();
$dog = new dog;
$dog->voice();
$dog->walk();

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

Строки 2,3 и 5,6 различаются только классами из которых их вызывают, не смотря на то, что вывод этих функций может быть совершенно разным.

Интересно, можно ли как то это объединить?

Конечно можно. Для начала приведем строки 2,3 и 5,6 к полному соответствию друг с другом

$animal = new cat;
$animal->voice();
$animal->walk(); 
$animal = new dog;
$animal->voice();
$animal->walk();

Теперь, как видите, они полностью идентичны, потому что мы переопределяем одну и туже переменную.

Теперь, с помощью ввода еще одной переменной, в которой будет указываться имя класса, мы сможем упростить код примерно до такого состояния
Введем переменную $animal, в которой будет содержаться имя класса

$animal = 'cat'; // или dog $animal = new $animal;
$animal->voice();
$animal->walk();

Вот так, меняя эту переменную, мы можем изменять вывод наших функций.

Например если весь этот код обернуть функцией примерно такой - 

function voice_and_walk($animal){
    $animal = new $animal;
    $animal->voice();
    $animal->walk();
}

То мы сможем вызывать действия у наших животных простым методом 

voice_and_walk('cat'); // создаем класс кота
// или
voice_and_walk('dog'); // создаем класс пса

Ну вот - перед вами самая простейшая реализация Абстрактной фабрики.

Но почему тогда она названа "абстрактной"?

Что будет если в функцию мы передадим название класса, например cow (корова)?

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

function voice_and_walk($animal){
    if(!class_exist($animal)){ // проверяем, был ли объявлен класс
        return false;
    }
    $animal = new $animal;
    $animal->voice();
    $animal->walk();
}

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

Но это все же не решает нашего вопроса - почему "абстрактная"?

Что будет если мы создадим класс cow (корова) вот таким (без функции walk)

class cow{
    function voice(){} // подавать голос
    // как видите функции walk нет
}

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

Call to undefined method cow::walk()

В таком случае вспомним что такое абстрактные классы - Если простым языком то это класс в котором есть хотя бы один абстрактный метод и экземпляр которого нельзя создать.
То есть у нас будет класс от которого мы можем только унаследовать другие классы, которые будут обязаны реализовывать абстрактный метод.
Запутано? Попробую объяснить на пальцах - 
Мы создаем абстрактный класс - animal и создаем там 2 абстрактных метода - voice и walk

abstract class animal{
    abstract function voice();
    abstract function walk();
}

Мы не можем создать его экземпляр (код $animal = new animal; выдаст ошибку), мы можем только наследовать от него другие классы, которые будут обязаны реализовать методы voice и walk
Перепишем наш старый код в соответствии с новым классом.

class cat extends animal{
    function voice(){} // подавать голос
    function walk(){} // ходить
}
class dog extends animal{
    function voice(){} // подавать голос
    function walk(){} // ходить
}

С этими животными у нас все отлично, но если мы попробуем унаследовать от класса animal класс cow (в котором не реализован метод walk) мы получим ошибку

class cow{
    function voice(){}
}
//Class cow contains 1 abstract method and must therefore be declared
//abstract or implement the remaining methods (animal::walk)

Теперь нам нужно заставить передавать в функцию только классы которые наследуются от класса animal чтобы в неожиданный момент мы не поймали ошибку.
Делается это просто - после проверки на наличие класса и создания экземпляра мы проверяем наследуется ли созданный класс от класса animal

function voice_and_walk($animal){
    if(!class_exist($animal)){ // проверяем, был ли объявлен класс
        return false;
    }
    $animal = new $animal;
    if(!($a instanceof animal)){ // проверяем наследуется ли класс от абстрактного класса animal
        return false;
    }
    $animal->voice();
    $animal->walk();
    return true;
}
В случае если что то пошло не так мы получаем false, если же все прошло успешно то код выполняется и в ответ мы получаем true.

Мы только что реализовали самый просто пример паттерна "абстрактная фабрика".
Применяется он тогда когда нужно создать объект и произвести с ним какие либо действия.

Реальный пример:

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

И напоследок - весь код приведенный в примере с небольшими коментариями.

// абстрактный класс
abstract class animal{
    abstract function voice();
    abstract function walk();
}
// классы наследуемые от абстрактного класса
class cat extends animal{
    function voice(){} // подавать голос
    function walk(){} // ходить
}
class dog extends animal{
    function voice(){} // подавать голос
    function walk(){} // ходить
}
// класс НЕ наследуемый от абстрактного класса
class cow{
    function voice(){}
}
// функция реализующая создание класса и вызов методов
function voice_and_walk($animal){
    if(!class_exist($animal)){ // проверяем, был ли объявлен класс
        return false;
    }
    $animal = new $animal;
    if(!($a instanceof animal)){ // проверяем наследуется ли класс от абстрактного класса animal
        return false;
    }
    $animal->voice();
    $animal->walk();
    return true;
}
// проверка работоспособности
voice_and_walk('dog'); // return true
voice_and_walk('cat'); // return true

voice_and_walk('pig'); // return false
// данный класс не реализован, поэтому код не выполняется
voice_and_walk('cow'); // return false
// данный класс не наследуется от animal, поэтому мы не можем быть уверены
// что необходимые нам методы (voice, walk) в нем реализованы

Теги: #Паттерны проектирования, #ООП, # абстрактная фабрика

Ваша оценка:

Рейтинг: 10.0 (Оценок: 4)

Комментарий:

Copyright © DOC_tr 2015-2017 г. Все права защищены
Яндекс.Метрика
Перейти к мобильной версии