Инверсия управления

Инверсия управления (Inversion of Control) — принцип программирования, который уменьшает связанность между программными компонентами. Разрыв зависимости достигается благодаря некому абстрактному интерфейсу, через который один компонент взаимодействует с другим. Отличной реализацией такого абстрактного интерфейса может быть система событий. Один компонент генерирует события, а другой отслеживает их появление.

DOM-события, кстати, — это тоже одна из реализаций IoC. Кнопка на веб-страничке сама по себе ни на что не может влиять, но она генерирует события, отражающие ее состояния.

Разберем пример, как организовать комплексную связь между несколькими компонентами интерфейса.

Есть задача сделать список объектов и панель инструментов для работы с этими объектами.

Ни один объект в списке не выбран

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

В списке выбран один объект

Если выбрано несколько объектов, то их можно только удалить.

В списке выбрано несколько объектов

В списке есть дополнительная «галочка» для выбора всех объектов в списке.

В списке выбраны все объекты

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

Для связи компонент списка и панели инструментов важно отслеживать 3 состояния списка:

  • ни один объект не выбран (selected-zero)
  • выбран один объект (selected-one)
  • выбрано несколько объектов (selected-many)

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

var btnEdit = $("#toolbar input.btn-edit"),
    btnDelete = $("#toolbar input.btn-delete");

$("#list")
    .bind("selected-zero", function () {
        btnEdit.attr("disabled", true);
        btnDelete.attr("disabled", true);
    })
    .bind("selected-one", function () {
        btnEdit.attr("disabled", false);
        btnDelete.attr("disabled", false);
    })
    .bind("selected-many", function () {
        btnEdit.attr("disabled", true);
        btnDelete.attr("disabled", false);
    });

В списке перехватываем изменение состояние «галочек».

var list = $("#list"),
    checkboxes = list.find("tbody input:checkbox");

checkboxes.bind("change click", function (e) {
    $(this).closest("tr").toggleClass("sel", this.checked);
    // блокируем всплытие события click, так как уже отработали его
    e.stopPropagation();
    // оповещаем подписчиков, что изменилось состояние списка
    notify();
}).trigger("change");

«Галочка» в шапке списка передает свое состояние всем остальным

list.find("thead input:checkbox").bind("change click", function () {
    checkboxes.attr("checked", this.checked).trigger("change");
});

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

list.find("tbody tr").bind("click", function () {
    var chb = $("input:checkbox", this);
    chb.attr("checked", !chb.get(0).checked).trigger("change");
});

Диспетчер событий.

function notify() {
    var count = checkboxes.filter(":checked").length,
        type = count === 0 ? "selected-zero" : count === 1 ? "selected-one" : "selected-many";
    list.trigger(type);
}

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

Подведем итог:

  1. Связываем несколько программных компонент не прямыми вызовами их методов, а через абстрактный интерфейс событий.
  2. Генерируем столько событий, сколько нам требуется для описания состояния компонента.
  3. Исключаем дублирование кода с помощью синтетических DOM-событий.