Как стать автором
Обновить

Отличие каркаса от библиотеки

Время на прочтение9 мин
Количество просмотров6.5K

Предисловие


Не секрет, что современный разработчик старается повысить эффективность и призывает себе на помощь библиотеки и каркасы.


Слово framework(каракас) настолько вошло в обиход, что стала встречаться путаница — что можно назвать каркасом, а что таковым не является?


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


Библиотека


С библиотекой все просто. Кто-то написал код, выставил наружу открытые методы/свойства(API) и этим можно пользоваться. В некотором смысле библиотека сервис, а ваш код клиент.


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


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


В качестве примера приведем фрагмент воображаемой JavaScript библиотеки работы с именами и фамилиями:


var nameUtil = {
        correctFullName:function(fullname){
            var f = fullname.replace(/^ +| +$| {2}/g, "")
            f =  f.substring(0,1).toUpperCase()+f.substring(1,f.length)
            f = f.substring(0, f.indexOf(" ")+1)
                + (f.substring(f.indexOf(" ")+1, f.indexOf(" ")+2)).toUpperCase()
                + f.substring(f.indexOf(" ")+2, f.length)
            return f
        }
}

Метод “nameUtil.correctFullName” исправляет написание полного имени человека, то есть “ John leaf” исправит в “John Leaf”. Мы помещаем эту библиотеку в проект и просто начинаем пользоваться ее сервисами — вызываем ее методы, то есть:


nameUtil.correctFullName(“some Name”)

Каркас


С каркасом другая история, каркас накладывает ограничения на уровне архитектуры, этапе проектирования приложения. Более того, его влияние распространяется и на уровень реализации.


Статья на на Wikipedia, ссылаясь на труд Вольфганга Прии, упоминает о замороженных и горячих точках. В самом источнике “Meta Patterns—A Means For Capturing the Essentials of Reusable Object-Oriented Design” говорится о горячих точках, серых и белых.


Попробуем проинтерпретировать это следующей иллюстрацией:



Рис. 1 — Каркас и остальные части системы


Голубые точки показывают части каркаса, они неизменны. Оранжевые точки это те части, которые было созданы в рамках какого-либо проекта.


Чаще всего упоминают следующие отличия между библиотекой и каркасом:


  • вы вызываете код библиотеки
  • каркас вызывает ваш код

Практический пример каркаса


Создадим каркас и посмотрим, как нам удалось реализовать свойства каркаса. Каркас реализует архитектурный шаблон, в нашем случае это MVC. В части обработки событий применен шаблон проектирования “Издатель-подпсчик”.


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


Напишем для нашей системы следующие компоненты на JavaScript:


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


/*
 * Объект-экземпляр, шина событий, позволяет подписываться на события и публиковать события.
 */
EventBus = {
    subscribers:[],
    /*
     * регистрирует подписчика события
     * @param subscriberObject объект-экземпляр подписчика события
     * @param subscriberMethod метод  подписчика события для обработки сообщения
     */ 
    subscribe:function(subscriberObject, subscriberMethod, event) {
        var subscriber = {}
        subscriber.object = subscriberObject
        subscriber.method = subscriberMethod
        subscriber.event = event 
        this.subscribers[this.subscribers.length]=subscriber
    },
    /*
     * публикация события
     * @param eventId строка-идетификатор события
     * @param params параметры
     * @param callback метод, который будет вызван для возвращения результата отработки подписчика события
     * @return возвращает результат отработки по событию 
     */
    publish:function(eventId, params, callback){
        for(var i=0; i<this.subscribers.length;  i++){          
            var event = this.subscribers[i].event
            //console.log("eventId=",eventId, " callback=", typeof callback)
            if(event===eventId){
                if(typeof callback=='function'){
                    callback(this.subscribers[i].object[this.subscribers[i].method](params))
                } else {
                    this.subscribers[i].object[this.subscribers[i].method](params)
                }
            }           
        }
    }
}

Table — компонент визуального отображения информации в виде таблицы. Для него нужна структура данных определенного вида. Реализован как объект-конструктор, можно создавать экземпляры.


/*
 * Объект-конструктор, визуальный элемент показывающий таблицу
 * @param nameParam уникальное имя таблицы
 * @param eBus шина событий
 * @param domId идентификатор узла DOM-дерева для размещения таблицы
 */
function Table(nameParam, eBus, domId){
    var name = nameParam
    var targetDomId = domId
    var eventBus = eBus
    /*
     * Показывает таблицу
     * @param params  параметры отображения, передаются источнику данных для таблицы
     */
    this.show = function(params){
        var data={}
        data.headers=[]
        data.rows=[]
        this.remove()
        // get data from a data source              
        eventBus.publish(name+ ".getDataSet", params,  function(d){ data = d})
        var targetDom = document.getElementById(targetDomId)
        var tableElement = document.createElement("TABLE")
        var tbodyElement = document.createElement("TBODY")
        var trElement = document.createElement("TR")
        trElement.style.background="#eaeaea"
        for(i=0;i<data.headers.length;i++){
            var tdElement = document.createElement("TD")            
            tdElement.appendChild(document.createTextNode(data.headers[i]))
            trElement.appendChild(tdElement)
        }
        tbodyElement.appendChild(trElement)

        for(i=0;i<data.rows.length;i++){
            var trElement = document.createElement("TR")
            for(j=0;j<data.rows[i].length; j++){
                var tdElement = document.createElement("TD")            
                tdElement.appendChild(document.createTextNode(data.rows[i][j]))
                trElement.appendChild(tdElement)
            }
            tbodyElement.appendChild(trElement)
        }

        tableElement.appendChild(tbodyElement)
        targetDom.appendChild(tableElement)     
        tableElement.border=1
    }
    /*
     * удаляет таблицу из DOM 
     */
    this.remove = function(){
        var targetDom = document.getElementById(targetDomId)
        try{
            while (targetDom.firstChild) {
                targetDom.removeChild(targetDom.firstChild);
            }
        }catch(e){}
    }
}

DataSource — источник данных, объект-экземпляр. Он умеет отдавать определенную структуру данных. Внутри содержит тестовый набор самих данных (переменная data).


/*
 * Объект-экземпляр, источник данных
 */
DataSource = {
    /*
     * регистрирует подписчика события
     * @return  возвращает данные
     */
    loadDepartments:function(){
        return data        
    }
}
// структура данных
var data = [
            {name:"IT",
            employees:[
                       {name:"Federico", surname:"Gonsales", position:"Engineer", hirenDate:"2013-01-02"},
                       {name:"Mike", surname:"Saldan", position:"Tester", hirenDate:"2011-11-22"},
                       {name:"Leo", surname:"Sigh", position:"Architect", hirenDate:"2001-12-12"}
                       ]
            },
            {name:"Sales",
                employees:[
                           {name:"Sarah", surname:"Connor", position:"Manager", hirenDate:"2010-04-14"},
                           {name:"Richard", surname:"Senom", position:"Specialist", hirenDate:"2014-05-07"}                                 
                           ]
                }
       ]

Employee — объект-конструктор модели, представляющий объект предметной области — Сотрудника.


function Employee(nameParam, surnameParam, positionParam, hirenDateParam){
    var name      = nameParam
    var surname = surnameParam  
    var position   = positionParam
    var hirenDate = hirenDateParam
    this.setName = function(n){
        name=n
    }
    this.setSurname = function(s){
        surname=s
    }
    this.setPosition = function(p){
        position=p
    }
    this.setHirenDate = function(d){
        hirenDate=d
    }
    this.getName = function(){
        return name
    }
    this.getSurname = function(){
        return surname
    }
    this.getPosition = function(){
        return position
    }
    this.getHirenDate = function(){
        return hirenDate
    }
    this.getFullName = function(){
        return name+" "+surname
    }
    this.getExperience = function(){
        var oneDay = 24*60*60*1000;
        var now = new Date();
        //console.log(hirenDate.getTime() +"-"+ now.getTime())
        var diffDays = Math.round(Math.abs((hirenDate.getTime() - now.getTime())/(oneDay)))
        return (diffDays/365).toFixed(0)
    }
}

Department — объект-конструктор модели, представляющий объект предметной области — Отдел.


/*
 * Объект-конструктор отдел, содержит сведения о отделе
 * @param nameParam  название отдела
 */
function Department(nameParam){
    var name = nameParam
    var employees = []
    this.setName = function(n){
        name=n
    }
    this.addEmployee = function(e){
        employees[employees.length] = e
    }
    this.getName = function(){
        return name
    }    
    this.getEmployees = function(){        
        return employees
    }
}

Main — главный объект приложения, соединяет все вместе. В части использования каркас предъявляет свои правила. Для размещения на странице нашей таблицы (Table) нужно выполнить следующие шаги:


Создать экземпляр Table указав обработчик событий, порождаемых визуальным компонентом.
Обработчик событий должен в ответ на вызов с параметром “getDataSet” вернуть JSON-структуру вида:
[headers:[“first”, “second”], rows:[ [value1, value2], [value3, value4] ] ]


Посмотрев объект “Main” может показаться, что нам не нужно создавать экземпляры модели для того, чтоб отправить набор данных компоненту “Table”, но это не так. Объекты модели содержат бизнес-логику, которая не должна быть в Контроллере, например, вычисление стажа работника(метод “getExpirience”)



/*
 * Объект-экземпляр, производит первичную инициализацию, получает данные от источника данных,
 * создает объекты модели, на основе опроса модели формирует набор данных для визуального компонета Table
 */
var Main  = {
    /*
     * создает экземпляр визуального компонента, производит его настройку
     * связывает таблицу, себя и источник данных через шину событий для обмена сообщениями
     */
    init:function(){
        var myTable = new Table("myTable", EventBus, "employeeTable")
        EventBus.subscribe(myTable, "show", 'employee.showTable')
        EventBus.subscribe(this, "getEmployeesDataSet", "myTable.getDataSet")
        EventBus.subscribe(DataSource, "loadDepartments", "loadDepartments")
    },
    /*
     * получает данные от источника, воссоздает модель предметной области, формирует набор данных
     * @param params параметры для источника данных
     */
    getEmployeesDataSet:function(params){
        var data
        EventBus.publish("loadDepartments", null,  function(d){ data = d })
        var departments = []
        for(i=0; i<data.length; i++){
            department = new Department(data[i].name)
                for(j=0;j<data[i].employees.length; j++){
                    var h = data[i].employees[j].hirenDate
                    var hirenDate = new Date(h.substring(0,4), h.substring(5,7), h.substring(8,10))
                    employee = new Employee(data[i].employees[j].name,
                            data[i].employees[j].surname,
                            data[i].employees[j].position,
                            hirenDate)
                    department.addEmployee(employee)
                }
            departments[i] = department 
        }
        records = []
        var c=0
        for(i=0;i<departments.length;i++){          
            department = departments[i]
            employees = department.getEmployees()
            for(j=0;j<employees.length;j++){
                if(params){                 
                    if(params.expirience){
                        if(employees[j].getExperience()*1 >= params.expirience*1){
                            records[c] = [employees[j].getFullName(), employees[j].getPosition(), employees[j].getExperience(), department.getName()]
                            c++
                        }
                    }                   
                }else{
                    records[c] = [employees[j].getFullName(), employees[j].getPosition(), employees[j].getExperience(),  department.getName()]
                    c++
                }
            }
        }
        var dataSet={
                headers:["Full name", "Position",  "Expirience", "Department"],
                rows:records
            }
        return dataSet
    }
}

На рисунке 2 попробуем показать принадлежность классов частям шаблона MVC.



Рис. 2 — Части приложения


На рисунке 3 покажем последовательность обмена сообщениями.



Рис. 3 — Диаграмма последовательности


Итоги


Условно назовем то, что создано “набор кода”.
“Набор кода” не обязывает создавать объекты-конструкторы модели предметной области. В Main.js источником данных для Table.js может быть что угодно. К каркасу, его реализации архитектуры (MVC) это не относится. Мы просто вызываем метод Table.js с определенным параметром. Это свойство библитеки.


“Набор кода” обязывает при использовании визуального компонента Table.js давать ему данные определенной структуры. Это не требование MVC, следовательно к каркасу отношения не имеет. Это свойство библиотеки.


“Набор кода” обязывает визуальные компоненты отправлять свои события по шине событий и чтобы был “слушатель” — подписчик этого события. Представление отделяем от обработки событий, управления, это уже элемент MVC. Это свойство каркаса. Код каркаса (EventBus.js) будет вызывать наш код. Это свойство каркаса.


Выводы


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


Если вы замечаете, что написанный вами или используемый компонент:


а) привносит в проект какой-либо архитектурный шаблон
б) компоненту передается управление ключевых “потоков” выполнения
в) компонент требует от вас некоторой организации ваших частей приложения, то вы имеете дело с каркасом.


Избавиться от каркаса в проекте гораздо сложнее, чем от библиотеки. Библиотеку можно заменить, исправив API-вызовы старой библиотеки на вызовы новой или самим написать реализацию библиотеки. Каркас принуждает строить приложение определенным образом, организовывать, связывать структурные единицы кода по определенным правилам. К тому же, каркасы обычно гораздо сложнее устроены, чем библиотеки.


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


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


Исходный код полностью

Теги:
Хабы:
+2
Комментарии25

Публикации

Изменить настройки темы

Истории

Работа

Ближайшие события