ColdFusion: понимание интерфейсов и ООП (часть первая)

Написано 27/09/2009

Пришло время рассказать об высоких материях – использовании в ColdFusion интерфейсов ((Интерфейс — это семантическая и синтаксическая конструкция в коде программы, используемая для специфицирования услуг, предоставляемых классом или компонентом. Интерфейс определяет границу взаимодействия между классами или компонентами, специфицируя определенную абстракцию, которую осуществляет реализующая сторона. В отличие от многих других видов интерфейсов, интерфейс в ООП является строго формализованным элементом объектно-ориентированного языка и, в качестве семантической конструкции, широко используется кодом программы.)), так хорошо знакомых java-программистам.

Зачем они и с какой стати их использовать? У меня есть несколько объяснений, но воспользуюсь самым простым — сначала вам будут казаться конструкции кода  избыточными, однако при некоторой практике вы поймёте, что использовать интерфейсы выгодно – во-первых, вы сможете значительно улучшить структуру вашего приложения, а во-вторых, если вы работаете в команде, то вы получите огромные преимущества по скорости разработки и дальнейшей отладке cf-кода.

Отлично, хватит предисловий. Рассмотрим пример — попробуем создать миниатюрную реализацию личного блога, с использованием различных техник.

Начнём с мастер-компонента, описывающего объект “информация”. Вот так он выглядит:

/**
* @displayname information
* @hint Master information component / Мастер-компонент для объекта информация
* @output false
*
* @project inj_sample_miniblog
* @coderevision 1
* @author Alexei Yakovenko
**/
component
{
    pageencoding "utf-8";
    
    property name="information_act" type="any" displayname="information_act" hint="Information actions / Действия с объектом информация";

    /**
    * @displayname setInformation_act
    * @hint Set Information_act / Сеттер для свойства Information_act
    * @output false
    **/
    public void function setInformation_act(required any Information_act)
    {
        information_act=arguments.Information_act;
    }
    
    /**
    * @displayname handler
    * @hint Master handler / Главный обработчик
    * @output false
    **/
    public any function handler(required string event, attributecollection="idata")
    {
        //Check argument "event" / Проверяем аргумент "event"
        if(!arguments.event=="")
        {
            switch(arguments.event)
            {
                //Publish new information / Публикуем информацию
                case "publish": return variables.inf.publish(idata);
                //Update information / Обновляем информацию
                case "update": return variables.inf.update(idata);
                //Delete information / Удаляем информацию
                case "delete": return variables.inf.delete(idata);
                //Get all information (with filters) / Выборка информации (с фильтрами)
                case "getall": return variables.inf.getall();
                //Get information by ID / Выборка информации по идентификатору ID
                case "getbyid": return variables.inf.getbyid(idata);
                //Search information / Поиск информации
                case "search": return variables.inf.search(idata);
            }
        } else
        {
            //Get an error handler / Передаём управление обработчику ошибок
            new errorshandler().err("information_handlerevent");
        }
    }
}

Как видите, это абстрактный компонент, имеющий одно свойство — information_act — и одну функцию — handler(), у которой два параметра — event и коллекция атрибутов idata.

Через первый параметр мы передаём действие, через второй — данные.

Важно обратить внимание на свойство information_act, которое в данном случае важно тем, что именно так будет называться наш интерфейс, а также обратите внимание на то, что указано в в блоке switch():

case "publish": return variables.inf.publish(idata);

Хм, чтобы это значило? Что будет если вызвать напрямую только что описанный компонент и его функцию handler и передать туда необходимые параметры? Ответ прост – вы получите ошибку такого содержания:

Element INF is undefined in a Java object of type class [Ljava.lang.String;.

То есть системе ничего не известно об элементе inf, да и самому cf-компоненту ничего не известно об этом элементе.

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

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

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

А теперь посмотрим, что из себя представляет интерфейс:

interface  displayname="information_act" hint="Interface for information / Интерфейс для объекта информация"
{
    public boolean function publish(attributecollection="idata")
     displayname="publish" hint="Publish information / Публикуем информацию" output="false";

    public boolean function update(attributecollection="idata")
     displayname="update" hint="Update information / Обновляем информацию" output="false";

    public boolean function delete(attributecollection="mbrdata")
     displayname="delete" hint="Delete information / Удаляем информацию" output="false";

    public any function getall()
     displayname="getall" hint="Get all information (with filters) / Выборка информации (с фильтрами)" output="false";

    public boolean function getbyid(attributecollection="idata")
     displayname="getbyid" hint="Get information by ID / Выборка информации по идентификатору ID" output="false";

    public any function search(attributecollection="idata")
     displayname="search" hint="Search information / Поиск информации" output="false";
}

Удивлены? 🙂

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

К чему мы и переходим.

Если вы пользуетесь CFBuilder’ом (а я советую вам им пользоваться — он очень удобен), то всё вам сгенерируется автоматически — то есть как только вы укажете при создании нового компонента (назовём его information_handlers) использование интерфейса, и кликните на Finish, то вот что вы увидите:

component  implements="information_act" displayname="information_handlers" hint="Information handlers" output="false"
{
    public boolean function update( attributecollection="idata")
    hint="Update information / Обновляем информацию" output="false" displayname="update"
    {

    }
    public boolean function getbyid( attributecollection="idata")
    hint="Get information by ID / Выборка информации по идентификатору" ID output="false" displayname="getbyid"
    {

    }
    public any function search( attributecollection="idata")
    hint="Search information / Поиск информации" output="false" displayname="search"
    {

    }
    public boolean function delete( attributecollection="idata")
    hint="Delete information / Удаляем информацию" output="false" displayname="delete"
    {

    }
    public boolean function publish( attributecollection="idata")
    hint="Publish information / Публикуем информацию" output="false" displayname="publish"
    {

    }
    public any function getall()
    hint="Get all information (with filters) / Выборка информации (с фильтрами)" output="false" displayname="getall"
    {

    }
}

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

Поскольку я пользуюсь ColdFusion 9, то и прибегнем у другой замечательной новинке в этой версии — поддержке ORM.

Это ещё больше упростит разработку, поскольку нам будет необходимо лишь описать данные, которые будем хранить в БД, и написать несколько функций для CRUD, а всё остальное сделает Hibernate.

В следующей части мы допишем компонент information_handlers и приступим к использованию интерфейсов.

To be continued…