JavaScript

индекс
246,16

Реализация паттерна декоратор на JS

Суть паттерна в том, что есть класс с фактической функциональностью (компонент) и опциональными классами-обертками, которые дополняют основной функционал (декораторы). А фишка в том, что декораторов может быть сколько угодно, совмещаться они могут в произвольном порядке и (поскольку требуют от компонента только интерфейса) — могут работать с разными компонентами.

Безусловно, реализовать что-то похожее можно даже за счет только лишь того, что функции в JS являются объектами первого уровня, но мне бы хотелось поделиться реализацией весьма близкой к ГОСТу GoF'у.

UPD: ссылка на рабочий пример, спасибо Barttos.

Перед хабракатом: в скрипте присутствует инкапсуляция, наследование (по сути) осуществляется через call, jQuery отсутствует — если Ваша идеология не позволяет принять такие ограничения, пожалуйста, не пишите об этом в комментах и, еще лучше, не читайте эту статью. Конструктивная критика и вопросы приветствуются.

Реализовывать будем простую листалку блоков. Не ново, но реализуем мы её так, что сможем перелистывать дивы без анимации или с ней (компоненты) и сможем выбирать, будут ли у нас кнопки переключения «страниц» :) или номер страницы (декораторы), или и то, и другое. Самое интересное, при использовании всего этого «богатства», нам будет без разницы ни как оно листается, ни сколько и каких UI-элементов задействовано.

HTML и CSS у листалки такой:
<html>
	<head>
		<title> </title>
		<style>
			#container { padding-top: 53px; padding-bottom: 3px; border: 1px solid gray; }
			#container, #scroll div { width: 100px; }
			#scroll, #scroll div { height: 50px; }
			#scroll div { float: left; }
			#container { position: relative; overflow: hidden; }
			#scroll { position: absolute; top: 0px; width: 1000px; border-bottom: 1px solid gray; }
		</style>
		<script>
		/*
		* в конце топика
		*/
		</script>
	</head>
	<body>
		<div id="container">
			<div id="scroll" style="left: 0px;">
			<div style="background: #ffc;">страница 1</div>
			<div style="background: #fcf;">страница 2</div>
			<div style="background: #cff;">страница 3</div>
			<div style="background: #fcc;">страница 4</div>
			<div style="background: #ccf;">страница 5</div>
			<div style="background: #cfc;">страница 6</div>
			<div style="background: #ccc;">страница 7</div>
			</div>
		</div>
		<script>
			/*
			* использование
			*/
		</script>
	</body>
</html>


Как накрутить UI сверху

Компонент является самостоятельной частью, готовой перематывать страницы при вызове .nextPage и .prevPage. Чтобы накрутить что-нибудь сверху нам надо:
  1. создать декоратор;
  2. передать декоратору компонент;
  3. сделать у декораторов теже методы, что у компонента;
  4. работать с методами декоратора, а он уже будет делать свою функциональность и вызывать теже методы у компонента.
Ключ ко всему этому — одинаковое именование методов, то бишь интерфейс.

Участники

Все компоненты должны иметь одинаковый интерфейс, и все декораторы должны иметь тот же самый интерфейс, но дополнительно расширенный, чтобы уметь принимать компонент.

Самый-самый интерфейс называется Component (в листинге — Scroll), а его реализации — ConcreteComponent (в листинге: SimpleScroll и AnimScroll). Интерфейс декораторов, Decorator (и в листинге Decorator), тоже основан на интерфейсе Component. А уже реализации Decorator'а, ConcreteDecorator (в листинге: Decorator_SwitchPage и Decorator_PageNum), относятся к Component'у косвенно.

Scroll и Decorator
Рекомендую посмотреть Scroll и Decorator, листинг кода внизу статьи. Как видим, Decorator переписывает (перегружает) все методы Scroll'а:
  • комментировать каждый из них повторно не требуется :)
  • Decorator запускает тот же метод у вложенного (через setComponent) компонента.
И Scroll, и Decorator берут на себя очень весомую часть грязной работы, т.к. они скорее базовые классы, чем интерфейсы.

SimpleScroll и AnimScroll
Благодаря Scroll'у, оба класса умеют работать с локальными переменными container и scroll. Это ноды с position: relative и absolute соответственно. Методы hasNextPage, hasPrevPage, findPages и getCurPage совпадают, но я не стал выносить их в Scroll, чтобы тот хоть немного напоминал интерфейс. Вполне можно вынести эти методы в промежуточный класс.

А вот nextPage и prevPage различаются, но декораторы просто вызывают методы и могут работать с обеими классами.

Decorator_SwitchPage и Decorator_PageNum
SwitchPage добавляет в container кнопки «вперед» и «назад».
PageNum добавляет индикатор текущей страницы, и их общего количества.

После подключения Decorator'а нам надо сохранить ссылки на его методы — в качестве костыля выступает локальная переменная methods. Она используется в перегруженных методах, чтобы запустить соответствующий метод у вложенного компонента. Хороший момент, чтобы оценить разницу в количестве методов у Decorator'а и его реализаций ;)

Использование

При использовании будет создаваться компонент — SimpleScroll или AnimScroll. Затем декораторы: PageNum и SwitchPage. В первый декоратор передается компонент, во второй декоратор — первый декоратор. Работать мы будем с крайним (самым верхним) декоратором, а он будет отправлять вызов методов вниз по цепочке.

SimpleScroll + PageNum + SwitchPage
// создаем компонент - основу для дальнейшей работы
component = new SimpleScroll();
component.setContainer(document.getElementById("container"));
component.setScroll(document.getElementById("scroll"));

// создаем первый декоратор
decorator1 = new Decorator_PageNum();
decorator1.setComponent(component); // заворачиваем компонент в декоратор

// создаем еще один декоратор
decorator2 = new Decorator_SwitchPage();
decorator2.setComponent(decorator1); // заворачиваем первый декоратор во второй

decorator2.init();
decorator2.nextPage();


Без декораторов
component = new SimpleScroll();
component.setContainer(document.getElementById("container"));
component.setScroll(document.getElementById("scroll"));

component.init();
component.nextPage();


AnimScroll + SwitchPage
component = new AnimScroll();
component.setContainer(document.getElementById("container"));
component.setScroll(document.getElementById("scroll"));

decorator1 = new Decorator_SwitchPage(); // или new Decorator_PageNum();
decorator1.setComponent(component);

decorator1.init();
decorator1.nextPage();


Javascript

// Component (интерфейс)
// из-за scope, в нем так же будут заданы локальные переменные и сеттеры/геттеры к ним - не академично, но и не страшно
// если есть желание использовать только прототипирование, то надо создавать локальные переменные "на месте",
// а все функции, которые их используют в Scroll оставить пустыми
function Scroll() {
	var container, scroll;

	this.setContainer = function(val) {
	container = val;
	};
	this.setScroll = function(val) {
	scroll = val;
	};
	this.getContainer = function() {
	return container;
	};
	this.getScroll = function() {
	return scroll;
	};

	this.init = function() { }; // ручная инициализация после задания container и scroll
	// это несколько упростит задачу, т.к. нам не надо будет переписывать сеттеры

	this.nextPage = function() { }; // перематываем вперед
	this.prevPage = function() { }; // перематываем назад

	this.hasNextPage = function(depth) { }; // наличие следующей страницы, или следующих depth страниц; по-умолчанию depth = 1
	this.hasPrevPage = function(depth) { }; // наличие предыдущей страницы, или следующих depth страниц; по-умолчанию depth = 1
	this.findPages = function() { }; // метод возвр. кол-во страниц
	this.getCurPage = function() { }; // метод возвр. номер текущей страницы
}

// ConcreteComponent (реализация Component'а)
// самый простой скроллинг
function SimpleScroll() {
	var dublicate = this; // постоянная ссылка на инстанцирующийся объект (для работы с любым this)
	Scroll.call(this); // агрегируем интерфейс
	// Scroll уже умеет работать с container и scroll, но ничего более -
	// теперь нам надо реализовать (перегрузить) все пустые методы
	var curPage = 0; // текущая страница (0-4)

	this.init = function() { };

	this.nextPage = function() {
		if (dublicate.hasNextPage()) {
		this.getScroll().style.left = ++curPage * -100 +"px";
		}
	};
	this.prevPage = function() {
		if (dublicate.hasPrevPage()) {
		this.getScroll().style.left = --curPage * -100 +"px";
		}
	};

	this.hasNextPage = function(depth) {
		var depth = depth || 1;
		return curPage + depth < dublicate.findPages();
	};
	this.hasPrevPage = function(depth) {
		var depth = depth || 1;
		return curPage - depth >= 0;
	};
	this.findPages = function() {
		return this.getScroll().getElementsByTagName("div").length;
	};
	this.getCurPage = function() {
		return curPage;
	};
}

// ConcreteComponent (реализация Component'а)
// другой скроллинг, с анимацией
function AnimScroll() {
	var dublicate = this; // постоянная ссылка на инстанцирующийся объект (для работы с любым this)
	Scroll.call(this); // агрегируем интерфейс
	var curPage = 0; 
	var curOffset = 0; // текущее смещение страницы в пикселях

	this.init = function() { };

	this.nextPage = function() {
		if (dublicate.hasNextPage() && !curOffset) {
			curPage++; // прибавим сразу, чтобы декораторы работали
			curOffset = 0;
			nextPageIterate();
		}
	};
	function nextPageIterate() {
		curOffset -= 10;
		dublicate.getScroll().style.left = curOffset + (curPage-1)* -100 +"px";
		if (curOffset>-100) {
			window.setTimeout(arguments.callee, 20);
		} else {
			curOffset = 0;
		}
	}

	this.prevPage = function() {
		if (dublicate.hasPrevPage() && !curOffset) {
			curPage--;
			curOffset=0;
			prevPageIterate();
		}
	};
	function prevPageIterate() {
		curOffset += 10;
		dublicate.getScroll().style.left = curOffset + (curPage+1)* -100 +"px";
		if (curOffset<100) {
			window.setTimeout(arguments.callee, 20);
		} else {
			curOffset = 0;
		}
	}

	this.hasNextPage = function(depth) {
		var depth = depth || 1;
		return curPage + depth < dublicate.findPages();
	};
	this.hasPrevPage = function(depth) {
		var depth = depth || 1;
		return curPage - depth >= 0;
	};
	this.findPages = function() {
		return this.getScroll().getElementsByTagName("div").length;
	};
	this.getCurPage = function() {
		return curPage;
	};
}


// Decorator (интерфейс)
// умеет инкапсулировать (сохранять в локальную переменную component) компонент или другой декоратор
// и просто передает вызовы методов в component (кроме setComponent и getComponent)
function Decorator() {
	var component;

	this.setComponent = function(val) {
		component = val;
	};
	this.getComponent = function() {
		return component;
	};

	this.setContainer = function(val) {
		return component.setContainer(val);
	};
	this.setScroll = function(val) {
		return component.setScroll(val);
	};
	this.getContainer = function() {
		return component.getContainer();
	};
	this.getScroll = function() {
		return component.getScroll();
	};

	this.init = function() {
		return component.init();
	};

	this.nextPage = function() {
		return component.nextPage();
	};
	this.prevPage = function() {
		return component.prevPage();
	};

	this.hasNextPage = function(depth) {
		return component.hasNextPage(depth);
	};
	this.hasPrevPage = function(depth) {
		return component.hasPrevPage(depth);
	};
	this.findPages = function() {
		return component.findPages();
	};
	this.getCurPage = function() {
		return component.getCurPage();
	};
}
Decorator.prototype = new Scroll();
Decorator.prototype.constructor = Decorator;


// ConcreteDecorator (реализация Decorator'а)
// кнопки для переключения страниц
function Decorator_SwitchPage() {
	var dublicate = this; // постоянная ссылка на инстанцирующийся объект (для работы с любым this)
	// подключаем Decorator и записываем ссылки на методы, которые определенны в "интерфейсе" Decorator'а, и которые будут переопределяться здесь
	Decorator.call(this);
	var methods = {
		nextPage: this.nextPage,
		prevPage: this.prevPage,
		init: this.init
	};
	var buttonNext, buttonPrev;

	this.init = function() {
		dublicate.getContainer().appendChild( buttonPrev = createButton("<", dublicate.prevPage) );
		dublicate.getContainer().appendChild( buttonNext = createButton(">", dublicate.nextPage) );
		buttonNext.disabled = !dublicate.hasNextPage();
		buttonPrev.disabled = !dublicate.hasPrevPage();
		return methods.init();
	};

	this.nextPage = function() {
		buttonNext.disabled = !dublicate.hasNextPage(2);
		buttonPrev.disabled = !dublicate.hasPrevPage(-1);
		return methods.nextPage();
	};
	this.prevPage = function() {
		buttonNext.disabled = !dublicate.hasNextPage(-1);
		buttonPrev.disabled = !dublicate.hasPrevPage(2);
		return methods.prevPage();
	};

	function createButton(text, onclick) {
		var ret = document.createElement("button");
		ret.appendChild( document.createTextNode( text ) );
		ret.onclick = onclick;
		return ret;
	}
}

// ConcreteDecorator (реализация Decorator'а)
// индикатор текущей страницы
function Decorator_PageNum() {
	var dublicate = this; // постоянная ссылка на инстанцирующийся объект (для работы с любым this)
	Decorator.call(this);
	var methods = {
	nextPage: this.nextPage,
	prevPage: this.prevPage,
	init: this.init
	};
	var text;

	this.init = function() {
		dublicate.getContainer().appendChild( text = document.createTextNode( "..." ) );
		var ret = methods.init();
		chText();
		return ret;
	};

	this.nextPage = function() {
		var ret = methods.nextPage();
		chText();
		return ret;
	};
	this.prevPage = function() {
		var ret = methods.prevPage();
		chText();
		return ret;
	};

	function chText() {
		text.nodeValue = " "+ (dublicate.getCurPage()+1) +" / "+ dublicate.findPages();
	}
}


Пряники
Когда компонентов или декораторов будет много, можно написать удобную функцию:
function cr() {
		var ret, last;
		for (var i=0, l=arguments.length; i<l; i++) {
			ret = new arguments[i]();
			if (!i) {
				ret.setContainer(document.getElementById("container"));
				ret.setScroll(document.getElementById("scroll"));
			} else (
				ret.setComponent(last);
			}
		last = ret;
		}
		return ret;
	}


Демка
К верстке добавляем:
<table cellpadding="5"><tr>
	<th>workWith</th>
	<td>=</td>
	<td>
		<div>
			<input type="radio" name="component" value="simple" id="component-simple" checked="checked" />
			<label for="component-simple">SimpleScroll</label>
		</div>
		<input type="radio" name="component" value="simple" id="component-anim" />
		<label for="component-anim">AnimScroll</label>
	</td>
	<td>+</td>
	<td>
		<input type="checkbox" id="pageNum" checked="checked" />
		<label for="pageNum">pageNum</label>
	</td>
	<td>+</td>
	<td>
		<input type="checkbox" id="switchPage" checked="checked" />
		<label for="switchPage">switchPage</label>
	</td>
</tr></table>
<button onclick="create();">Пересоздать компонент с декораторами</button>
<br />
<button onclick="workWith.prevPage();">workWith.prevPage()</button>
<button onclick="workWith.nextPage();">workWith.nextPage()</button>
<br />


И вот такой script:
var component;
var decorator1;
var decorator2;
var workWith;

function reset() {
	component = decorator1 = decorator2 = workWith = null;
	var node = document.getElementById("scroll");
	node.style.left = "0px";
	while(node.nextSibling) {
		node.parentNode.removeChild(node.nextSibling);
	}
}

function create() {
	reset(); // сбрасываем предыдущие настройки (если были)
	
	// создаем компонент - основу для дальнейшей работы
	if (document.getElementById("component-simple").checked) {
		component = new SimpleScroll();
	} else {
		component = new AnimScroll();
	}
	component.setContainer(document.getElementById("container"));
	component.setScroll(document.getElementById("scroll"));
	workWith = component;
	
	if (document.getElementById("pageNum").checked) {
		// создаем первый декоратор
		decorator1 = new Decorator_PageNum();
		decorator1.setComponent(component); // заворачиваем компонент в декоратор
		workWith = decorator1;
	}
	
	if (document.getElementById("switchPage").checked) {
		// создаем еще один декоратор
		decorator2 = new Decorator_SwitchPage();
		if (decorator1) {
			// заворачиваем первый декоратор во второй
			decorator2.setComponent(decorator1); 
		} else {
			// заворачиваем компонент в декоратор
			decorator2.setComponent(component); 
		}
		workWith = decorator2;
	}
	
	workWith.init();
}

create();
+32
23 июля 2009, 22:50
49

комментарии (97)

+3
eosunknown #
Рабочий пример было бы неплохо добавить… Сегодня в моске я это скомпилировать не могу.
+1
hooz #
о Очень даже неплохо!
0
Zitrix #
я просто не знаю, где это разместить можно.
понимаю, что это не айс, но можно это все в текстовый редактор и сохранить.html
+2
Barttos #
nvartolomei.com/decorator/
0
Zitrix #
большое спасибо!
–14
xaja #
Автор в эпоху джекверей создал деревянный велосипед и описал его как ядерный реактор.
да-да, я че то видел в статье по поводу джекверей, но мой маленький мозг не может понять почему это нереализуемо на них.
0
Zitrix #
не реализуемо что?
0
Zitrix #
«деревянный велосипед» изобрели светила современного программирования, те самые GoF, вы можете погуглить что такое «шаблоны проектирования» (да ту же вики почитать).

вы не дочитали всего-навсего реализацию на JS с очень незначительными вариациями от классики, в отличие от множества других примеров декоратора когда «работоспособность остается», а профит от подхода много меньше.
+10
kodji #
Я пожалуй возьму на себя смелость слегка перефразировать комментарий автора. Автор статьи не пытается сделать анимацию переключений. Он пытается показать реализацию шаблона — паттерна проектирования под названием «декоратор» средствами языка javascript.
Спасибо, ваш К.О.
+9
ap3rus #
Если пользоваться одними JQuery, мозг не только станет маленьким, но еще и полностью гладким
–8
chiaroscuro #
Если пользоваться одними шаблонами, мозг не только станет маленьким, но еще и полностью гладким.
–7
chiaroscuro #
Ну и за что минусим?

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

Ну и еще один гвоздь: решая задачу, отталкиваться надо именно от нее, а не от манящего решения в виде шаблона. Часто бывает, что шаблоны применяют без повода (как и ООП), а потом в коде черт ногу сломит.

Это как не видеть лес за деревьями, то бишь, конечно, настоящее решение за шаблонами.
+4
ap3rus #
Шаблоны это лишь способ организации архитектуры, никто не мешает тебе реализовывать идею именно так, как ее нужно реализовать наилучшим образом; что шаблоны и ООП отлично структурируют код и упрощают его повторное использование (если конечно уметь правильно реализовывать и ООП, и шаблоны).
0
ap3rus #
слово «что» было лишним :)
0
dshalkhakov #
>если конечно уметь правильно реализовывать и ООП, и шаблоны

О. А что есть «правильно»? И как реализовывать ООП?
+1
ap3rus #
Правильно реализовывать шаблоны — значит реализовывать их именно так, как говорится в спецификации к шаблону, это же очевидно)
На тему ООП в javascript существует куча статей, в частности msdn.microsoft.com/ru-ru/magazine/cc163419.aspx
0
dshalkhakov #
>Правильно реализовывать шаблоны — значит реализовывать их именно так, как говорится в спецификации к шаблону, это же очевидно)

Я не спрашивал про шаблоны. Я спрашивал про «правильность» и «реализацию ООП» (очевидно «правильную»).
0
Zitrix #
Вы в чем-то нашли не«правильность»?
–2
dshalkhakov #
Я просто прошу дать мне определение «правильности».
0
ap3rus #
Я ответил и про шаблоны, и про ООП. Зачем холиварить? :)
0
Zitrix #
я подумал, что я что-то пропустил
0
dshalkhakov #
Вы не ответили про «правильность». Либо дайте мне его определение, либо не применяйте его в споре.
0
ap3rus #
Не вижу смысла в очевидных вещах, ну да ладно. Под правильностью реализации ООП я понимаю применение архитектуры ООП к некоему алгоритму в рамках синтаксиса, который поддерживается тем или иным языком программирования.
0
dshalkhakov #
>Не вижу смысла в очевидных вещах, ну да ладно.

Отличный демагогический прием.

>Под правильностью реализации ООП я понимаю применение архитектуры ООП к некоему алгоритму в рамках синтаксиса, который поддерживается тем или иным языком программирования.

Что такое «архитектура ООП»? Как ее применить к алгоритму? Причем тут синтаксис?
+2
ap3rus #
> Что такое «архитектура ООП»
ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5

> Как ее применить к алгоритму
С помощью мозга, видимо

> Причем тут синтаксис
Синтаксис при среде реализации алгоритма. ООП в javascript и в C++ — довольно разные вещи
–2
dshalkhakov #
>> Что такое «архитектура ООП»
>ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5

Расскажите своими словами про «архтитектуру ООП». У вас это хорошо получается. Узнаю много нового.

>> Как ее применить к алгоритму
>С помощью мозга, видимо

А конструктивно можете объяснить?

>Синтаксис при среде реализации алгоритма.

ЛОЛШТО?
0
ap3rus #
> Расскажите своими словами про «архтитектуру ООП», А конструктивно можете объяснить?

Ну если вам это так интересно, почитайте книжку)

>ЛОЛШТО?

лолшто?
–2
dshalkhakov #
>Ну если вам это так интересно, почитайте книжку)

Читал и не одну. А на вопрос ответьте.
0
chiaroscuro #
Выучи сначала, что такое «архитектура» и какое отношение это слово имеет к программированию.

> С помощью мозга, видимо

У тебя он гладкий, что и требовалось показать.

> Синтаксис при среде реализации алгоритма. ООП в javascript и в C++ — довольно разные вещи

Синтаксис в JS и C++ довольно похожий (Си-подобный же), зато разная модель памяти (в JS с выделением машинной памяти вообще не сталкиваешься), хотя модель вычислений примерно одна и та же (вычисление в окружении, но оно тоже по-разному выглядит, ибо в JS есть функции первого класса и замыкания).

Короче, учи матчасть, а то я тут уже несколько раз под стол падал от твоих сентенций.
+1
Zitrix #
синтаксис, я так понимаю, применялся в значении «реализация на конкретном языке». сходства у любых языков имеются.

я так и не понял, почему у ap3rus'а мозг гладкий? я даже готов признать, что мой мозг маленький и гладкий, есть Вы доступно, с толком, с расстановкой это аргументируете
0
chiaroscuro #
> синтаксис, я так понимаю, применялся в значении «реализация на конкретном языке».

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

> сходства у любых языков имеются.

Это только в теории. На практике же, дай вам программку на Haskell, вы ее не осилите без должной подготовки — слишком мало общего с JS на первый, второй и n-ый взгляд. :) (на Haskell пишут совсем не так, как на JS — «культура» сказывается)

> я так и не понял, почему у ap3rus'а мозг гладкий?

Потому что он, как попугай, повторяет чьи-то истины, о которых не знает сам.

> Вы доступно, с толком, с расстановкой это аргументируете

Я уже аргументировал, толку ноль, зато минусов нахватался. :) За что? За то, что тут кучка идиотов возводит шоблончеги в божество какое-то, а я посмел его осквернить.

А вообще-то не надо признавать, чей мозг мелкий и гладкий, читайте книжки просто. ;-)
+1
Zitrix #
> Интересное значение, раньше не встречал.
согласен, но из контекста понятно, что человек имел ввиду

> слишком мало общего с JS
да, именно это я имел ввиду

> Я уже аргументировал
простите, но с моей точки зрения, его контр-аргументация убедительна, только на кодосрач зря скатились (оба)

> повторяет чьи-то истины
2) только я куда больше обеспокоен «мнением» xaja (первый коммент в ветке)
1) наврядли кто-то из комментаторов внес вклад в развитие программирования, как науки ;) но возможно, что Вы повторяете более продвинутые истины.

> кучка идиотов возводит шоблончеги в божество какое-то
тут надо учесть контекст сегодняшнего клиент-сайда. это готовые плагины для jQuery (копипаста 2.0), на этом «культура» клиент-сайда начинается и заканчивается. очень хорошо, что люди задумываются о гибкости своих разработок, понимают что если продукт работает, то это еще ничего не говорит о его качестве. даже их бездумное применение принесет для JS очень много пользы.
+1
ap3rus #
> «Синтаксис в JS и C++ довольно похожий»
Я не говорил, что он разный, я сказал что реализация ООП в javascript и в C++ довольно отличается, в Javascript нет наследования, но есть прототипирование, в javascript любой объект или функция является одновременно и объектом, проектирование ООП модели следует делать полагаясь на эти различия.
0
chiaroscuro #
> проектирование ООП модели следует делать полагаясь на эти различия

Модели чего? Реального мира, наверное. А где вы в реальном мире видели «декораторов»? :) (нет, ну конечно же, можно придумать физическую аналогию; я тут придираюсь к слову «модель» просто)

Но вы съехали куда-то не туда. ООП (абстракция данных, если хотите) с шаблонами (костылями, если угодно) не являются ответом на все вопросы! Надо знать, когда ООП применимо, а когда нет, а в распеаренных книжках Буча и прочих «светил» об этом ни слова.
+1
ap3rus #
> Модели чего? Реального мира, наверное.
Такое чувство, будто я разговариваю с учителем литературы)

> Надо знать, когда ООП применимо, а когда нет… а в распеаренных книжках Буча
С учителем я погорячился ;)
Я как раз об этом и говорю, нужно использовать свой мозг, перед тем, как пытаться что-либо применить. А читать книжки нужно, хотя бы для того, чтобы знать что такое ООП.
0
Zitrix #
а кто говорил, что это панацея?
0
chiaroscuro #
Ну а кто применяет шаблоны там, где не надо?

PS я определяю рамки применимости шаблонов как очень скромные: там, где язык не позволяет выразить какую-то мысль явно, приходится использовать шаблоны, дабы обойти ограничения. Примеры: функции первого класса и замыкания.
0
chiaroscuro #
В вашем же случае: отсутствие первоклассных сообщений (и следовательно, возможности делегировать сообщение другому объекту) заставляет юзать какой-то там «декоратор» (название-то какое!).

Вообще-то делегировать в JS можно посредством прототипов, надо подумать.

Но вы же не в ту степь ломанулись сразу же: абстрагировать данные там, где нужно абстрагировать алгоритм!

А все отчего? От того, что я писал выше: отталкиваться надо от проблемы, а не от шаблона.
0
Zitrix #
не буду утверждать, что данный пример — эталон js+декоратор, но вполне себе хороший пример. сможете привести получше — скажу спасибо
0
chiaroscuro #
Дык я ж не о том совсем! Я исключительно о применении шаблона не по назначению.

Что же касается примеров шаблона — зачем они? Мне кажется, если у человека возникает необходимость в делегировании в JS — то он берет и использует прототипы.
+1
Zitrix #
видимо, я чего-то не понимаю.

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

можно этот же пример сделать с помощью «не особо делегирования», нам понадобятся следующие классы: BaseScroll_plus_Buttons, BaseScroll_plus_PageNum, BaseScroll_plus_Buttons_plus_PageNum — и еще столько же для AnimScroll.

А если мы не хотим иметь шесть классов, то можно сделать метод компоненты и декораторы :)
0
chiaroscuro #
> делегирование это когда есть объект, который перенаправляет методы в другой инкапсулированный объект.

Да, но по ходу действия он может все что хочет делать с сообщением. ;) Делегирование и наследование — это две стороны одной монеты.

Кстати, объект-получатель может быть неинкапсулированным, а каким-нибудь глобальным. Достаточно, чтобы он был замкнут относительно объекта-прокси.
0
Zitrix #
есть несколько, уже упомянутых, понятий: делегирование, декорирование, наследование. они, правда, в разных весовых категориях, но можно взять абсолютно любой код, выдрать из него ± любой кусок и сказать: «здесь употребляется (делегирование|декорирование|наследование)».

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

передача вызовов в глобальный объект в 90% (не 100%, только 90%) случаев, имхо, скорее просто код безо всякой архитектуры. согласны?

// да, я не говорю, что просто код это плохо по определению
0
chiaroscuro #
Это не понятия, это каша.

Понятия — это сообщение, и передача сообщения которое тоже есть сообщение. :)

> передача вызовов в глобальный объект в 90% (не 100%, только 90%) случаев, имхо, скорее просто код безо всякой архитектуры. согласны?

Нет. Допустим, что «Класс» это глобальный объект, которому мы можем отправить сообщение, чтобы определить новый класс. Смотрим Smalltalk и удивляемся.
0
Zitrix #
да, единственный способ заставить объект работать — вызвать его метод (сообщение передать). когда мы говорим классу: «new» — нам возвращается новый объект. чтобы упростить устное общение с коллегами по цеху, придумали слово «наследование». и прочую кашу другие понятия.

> по ходу действия он может все что хочет делать с сообщением
если делегирование работает как декорирование (их вообще этично сравнивать?), то не лучше так его и назвать?

> глобальный объект… чтобы определить новый класс
процентаж употребления этого метода при работе с глобальной областью видимости больше 10?
0
chiaroscuro #
> да, единственный способ заставить объект работать — вызвать его метод (сообщение передать)

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

> чтобы упростить устное общение с коллегами по цеху, придумали слово «наследование». и прочую кашу другие понятия.

Понятия «наследование», «полиморфизм», «делегирование» и пр. в мейнстримовых языках являются кашей из-за отсутствия их четкого формального определения. Это самое определение должно схватить самую *суть*, а не сиюминутные прихоти «орхетекторов» языка.

И напоследок, пару пойнтов.

В настоящем ООП всякие костыли вроде таких любимых народом шаблончегов просто не нужны (в них не возникает необходимости, так то!).

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

Dixi.
+1
habraname #
/me как бы вторит К.О.: работа выполнена, конечно же, из академического интереса?
0
Zitrix #
изначально, да, был чисто академический интерес. сначала посмотрел примеры, книгу почитал, написал на js абстрактное решение. потом начал думать о хоть каком-нибудь примере из реальной жизни, и додумался до вот такой штучки.
и вот когда я до неё додумался :) понял профитность реализации «как написано», а не как умею.
0
Loengreen #
Статья, что называется в руку. Третий день думаю как реализовать взаимодействие между «классами». Спасибо и низкий поклон! :)
0
Zitrix #
рад помочь. очень рекомендую GoF почитать, там много идей, до которых своим умом тыщу лет идти
+1
artyfarty #
Вы уж меня простите, я не стал полнсотью читать статью (потому что я не люблю кодить на жс без фремйворка).
Я не знаком с паттернами, но не так давно я делал один сайт, и у меня в голове зародилось кое-что похожее.

Но комментарий не об этом. Хочу написать, где вообще в JS может пригодится подобное.

Дано: сайт, на нём дизайнер родил аж три галереи. Функционал похожий: область где показывается текущая картинка и область filmstripa. Но дизайн везде разный, филмстрип где вертикальный, где горизонтальный, анимации разные, где-то нужно оверлеить дополнительную информацию.

Тогда я написал базовый класс (используя мутулсы), управляющий всем этим безобразием, а для каждой конкретной галереи он доопределялся своими классами: добавлялись какие-то действия на события, уточнялись настройки. Вышло чертовски прикольно и кратко (сильно короче листинга в посте).
0
kellas #
Хорошо, интересно.

Вот только как это можно применить, зачем это?
+3
artyfarty #
Прямо над вашим комментарием я написал хороший пример того, как можно применить что-то подобное.

И думаю это не единственная ситуация.
0
kellas #
Ой а я и не заметил, спасибо.
+3
Zitrix #
не хотелось бы отвечать «где угодно» или копипастить, попробую «от души» ответить, голосом сердца :)

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

что бы то ни было: калькуляторы, фотогалереи, система сообщений между пользователями на сайте — корзина отдельно, яйца отдельно :)

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

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

фотогалерея уже нашлась в комментах, см. выше.

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

повторюсь, я не призываю юзать декоратор везде, а только там, где это действительно профитно.
0
Tone #
К сожалению я не знаю js достаточно хорошо, поэтому хочу спросить — если в нем какой-нибудь аналог системы перехвата несуществующих функций объекта, как например метод __call() в пхп или класс Proxy в ас3?

Если есть то по идее можно было бы с помощью него улучшить абстрактный декоратор, реализовав в нем такую систему вместо перечисления функций декорируемого объекта
0
aubt #
на сколько я знаю, нету.
можно конечно exception-ы хватать и как-то обрабатывать, но это тот еще изврат :)
0
Zitrix #
именно перехватывать несуществующие методы нельзя, только если с помощью try/catch, но это или try/catch при каждом вызове каждого метода (полный бред), или велосипед ввиде отдельной функции, которой передаются имя метода и аргументы, и уже она проверяет typeof(this[methodName]), но тоже красотой не блещет.

можно вот такой велосипед придумать, но следует ли им пользоваться — большой вопрос
function Component() {
	this.fn1 = function(val) { alert("Component > fn1 " + val); };
	this.fn2 = function(val) { alert("Component > fn2 " + val); };
}

function IDecorator() {
	var dublicate = this;
	var component;

	this.setComponent = function(val) {
		component = val;
		for (var key in val) {	
			if (typeof(val[key])=="function") {
				addMethod(key, val[key]);
			}
		}
	};
	this.getComponent = function() {
		return component;
	};
	
	this.applyComponentMethod = function(name, argsArray) {
		if (typeof(component[name])=="function") {
			component[name].apply(dublicate, argsArray || []);
		}
	};
	
	function addMethod(name, componentFn) {
		dublicate[name] = function() {
			return componentFn.apply(this, arguments || []);
		}
	}
}

function MyDecorator(component) {
	var dublicate = this;
	IDecorator.call(this);
	this.setComponent(component);
	
	this.fn1 = function(val) {
		alert("MyDecorator > fn1 " + val);
		dublicate.applyComponentMethod("fn1", [val]);
	};
}

var c = new Component();
var d = new MyDecorator(c );
d.fn1(1); // "MyDecorator > fn1 1", "Component > fn1 1"
d.fn2(2); // "Component > fn2 2" и не думаю, что это хорошо


… но я бы лучше перечислял методы, ничего зазорного я в этом не вижу
0
ap3rus #
я кое-чего у тебя не понял, ты написал метод
this.setComponent = function(val) {
component = val;
for (var key in val) {
if (typeof(val[key])==«function») {
addMethod(key, val[key]);
}
}
};
в нем ты задаешь component, а так же копируешь в IDecorator все методы component, но далее внутри
this.applyComponentMethod = function(name, argsArray) {
if (typeof(component[name])==«function») {
component[name].apply(dublicate, argsArray || []);
}
};
ты вызываешь метод именно объекта component, вопрос — зачем ты копируешь все методы к декоратору в IDecorator.setComponent? Нравится хранить мусор? =)
0
Zitrix #
а, если их не копировать, то как потом в декораторе вызывать?
т.е. у компонента есть fn2, а у декоратора его нет — как вызвать fn2, обращаясь к декоратору и не проверяя каждый раз наличие fn2?
0
ap3rus #
А действительно, с утра мозг сбуксовал)
Я хотел сказать другое, у IDecorator есть метод setComponent(val), который задает методы из объекта val, но если этот метод вызвать два раза для разных объектов, мусор скопится, можно было бы вставить проверку на то, что у IDecorator уже задан component, удалить все его методы, и только потом добавить новые методы. Либо же просто задание компонента перенести в конструктор IDecorator
0
Zitrix #
d.fn2(2); // «Component > fn2 2» и не думаю, что это хорошо

ой, извиняюсь, поведение правильное
0
Tone #
упс, «есть ли» вместо «если» в первой строке.
0
garex #
Не уловил кошерности в примере.

На сколько я знаю этот шаблон нам нужен, когда у нас есть GOD-объект, а от него нам надо только А, Б и В.

Или, когда мы религиозные экстремисты и не приемлем иного фреймворка, нежели %framework_name%, и внешний компонент для скорости, оборачиваем в декоратор, который кошерен.
0
Zitrix #
GOD-объект? кошерность в использовании: нужны кнопки — добавляем кнопки, нужен индикатор страницы — добалвяем индикатор страницы.
0
garex #
Я ошибся, перепутав с Фасадом.

ru.wikipedia.org/wiki/%D0%94%D0%B5%D0%BA%D0%BE%D1%80%D0%B0%D1%82%D0%BE%D1%80_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)
0
Zitrix #
и снова ошиблись, ссылка на декторатор)
+1
garex #
Да нет, это я уже специально — запутать хочу.

Я т.е. декоратор вкурил, а внизу него еще и фасад есть.

Кароче не суть.
0
GEMOzloBIN #
[зануда]Подсветите плиз код, а то не очень удобно его читать[/зануда]
–2
chiaroscuro #
Плоховастенько (то бишь есть куда стремиться), ибо много кода.

Недавно (пару месяцев назад) писал анимацию на Flapjax, там была композиционность и то, на что вы тут тратите свое время, решалось просто и быстро.

Статья тут: chiaroscuro.yvision.kz/blog/9468.html

анимация с easeInOut:

> insertValueB(liftB(anim(em,-30.0,0.0), moveB(ev, 25, 250, easeInOut)), 'my-element', 'style', 'left');

без анимации (точнее, с мгновенной анимацией):

> function instantB(ev) {
> return startsWith(constantE(ev,1.0), 0.0);
> }
> insertValueB(liftB(anim(em,-30.0,0.0), instantB(ev), 'my-element', 'style', 'left');
0
chiaroscuro #
Хех, и тут минусы. Вы бы разобрались, в чем дело-то сперва.

ОП просто-напросто нафигачил целое полотно из-за своего незнания, что есть такая штука, как абстракция алгоритма.

Зато шаблоны же, еба.
+1
aubt #
статья о шаблоне, а не о анимации.
0
chiaroscuro #
Статья приведена как пример использования алгоритмической абстракции, о которой любители шоблончегов и слыхом не слыхивали.
–1
dshalkhakov #
Вам шашечки или ехать?
+1
Zitrix #
аллоу, какие «шашечки или ехать»? статья называется «Реализация паттерна декоратор на JS», неужто она должна быть про анимацию, или абстракцию алгоритма?
–1
chiaroscuro #
> статья называется «Реализация паттерна декоратор на JS»

Round hole, meet square peg.
Square peg, meet round hole.
I think you'll fit together just fine.

Воистину, когда в держишь в руках молоток, видишь вокруг одни гвозди.
+1
4pcbr #
Вы знаете, мое мнение — данный пример не пригоден за рамками академического интереса. Паттерны GOF довольно универсальны, но зацикливаться на них не стоит. В книге они реализованы для C++ и Smaltalk, но не стоит забывать об уникальной специфике каждого языка. Так например родились на свет паттерны J2EE. JS — клиентский язык, и в наших интересах писать крайне сжатый и быстрый код. Паттерны же в свою очередь смотрят в сторону повторного использования и простоты расширения, забывая об объеме кода — для серверных приложений это не столь принципиально. В этой книге имеется описание паттерна декоратор, но контекст применения совершенно иной, не dom-виджеты. Если честно — еще не читал, в ближайшее время прочитаю и вам советую: )
+1
slik #
JS — клиентский язык

Не согласен. Реализация VM этого языка обычно встаивается в браузеры или Qt-программы, но называть целый язык клиенским — не надо. Язык вообще не может быть серверным/клиентским, только VM.

Вот вам пример серверной VM языка Javascript code.google.com/p/v8cgi/
А вот даже фреймворк, на основе небезызвестного MooTools для серверного Javascript raccoon.keetology.com/
+1
slik #
Так что, по моему мнению, если не сейчас, то в скором будущем, реализации паттернов на Javascript станут нужными и будут использоватся на практике
0
4pcbr #
Мы говорим о чем? О том коде, который клиент загружает себе каждый раз? (не думаем сейчас о кеше). Лично для меня JS прежде всего это клиентский язык. В данном топике речь идет о джаваскриптовом dom-скриптинге — не клиентские скрипты по вашему мнению??

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

Вы не считайте, что до паттернов в JS никто еще не додумался, почитайте блог Дена Эдвардса например, все на свои места встанет.

Дело в том, что я сам когда стал въезжать в паттерны, стал их тутже реализовывать в JS, потом пришло чувство осознания, что я не самый хитрый.
0
slik #
Если говорить о серверном применении, естественно нам безразличен размер скриптов и интересует возможность расширения.

Именно об этом я и говорю. Вы сказали что
данный пример не пригоден за рамками академического интереса.

так как
JS — клиентский язык

Я дал вам пример того где данный пример пригоден к использованию. Автор сделал рабочий пример в среде браузера, но никто не мешает пользоватся этим кодом на стороне сервера.
0
4pcbr #
ок, насчет серверной части согласился с вами, но для меня это нечто к реальной жизни не относящееся пока.
+1
django #
Ерунда. Для js объём кода тоже давно не главное, всегда можно обфусцировать/запаковать, да и не бывает его слишком много в подавляющем большинстве случаев. Так что то что паттерны так увеличить код что даже обфусцированный он будет долго загружаться клиентом — полнейшая чушь. Хотя и область применения паттернов в JS мала, реально они требуются если пишете большой проект типа FCK Editor, в остальном только запутают код.
0
4pcbr #
Ерунда-не ерунда, а код должен быть максимально маленьким и оптимизированным. факт.
0
4pcbr #
* максимально сжатым

ато смешно звучит
0
Zitrix #
я люблю всех посетителей сайта, даже ie-посетителей, даже тех, у которых отключен css/js, но в минимализме тоже меру надо знать, никто от лишних 5-50 килобайт не лопнет, это соизмеримо с одной картинкой. в «не лопнет» тоже надо меру знать.
0
Zitrix #
dom-виджет я выбрал, потому что визуальный пример с формочкой всегда понятнее, чем код, или описание кода.

книгу надо бы почитать, спасибо за ссылку, но зацикливаться на ней я тоже буду :)

краткость кода это хорошо, но его модифицируемость — гораздо лучше. быстрый код? не думаю, что эта штука будет тормозить. а реализовывать функционал, требующий максимальной скорости нужно, как бы это, «на языке», без архитектуры вообще… не знаю, как это по-человечески сказать :)

у меня тоже есть отступления от темы. Scroll, и Decorator — базовые классы, а не интерфейсы, из-за локальных переменных.

я не зацикливаюсь, просто под декоратором люди понимают все, что угодно: AOP, колбэки, перекидывание функции (именно функции, у которой return число возвращает) аргументом при инстанцировании декоратора. «но мне бы хотелось поделиться реализацией весьма близкой к GoF'у».

пример имеет, по сути, один JS-недостаток — объем кода. это плохо, но, имхо, еще не причина оставлять его в рамках академического интереса.
+1
4pcbr #
ок, думаю, тему тяжелого клиента раскрыли: ) Хотел лишь обратить общее внимание на ярко выраженную специфичность JS. За статью спасибо.
0
Zitrix #
* книгу надо бы почитать, спасибо за ссылку, но зацикливаться на ней я тоже не буду :)

мой любимый тип опечаток
+1
qmax #
после такого дисклэймера, напрашивается постскриптум:
за время реализации не пострадало ни одной функции :)

по оформлению:
я бы заподозрил очень малое количество читателей этого блога со встроенным в мозгу движком JS.
хотябы раскрашенный код был бы гораздо читабельнее.
а ещё лучше — сделать в духе документации по jquery:
код + рабочий пример

по теме:
Все компоненты должны иметь одинаковый интерфейс,

а что мешает использовать интроспекцию кмпонента и _породить_ такойже интерфейс, расширив «интересные» методы и оставив ностальные в покое?
или это будет уже другой паттерн?
0
Pilat #
>а что мешает использовать интроспекцию кмпонента и _породить_ такойже интерфейс,
>расширив «интересные» методы и оставив ностальные в покое?
>или это будет уже другой паттерн?

Это будет слишком просто — пройти по списку функций и сделать такие же. Но у автора есть это в комментарии — только я не понял, почему это плохо.

this.setComponent = function(val) {
component = val;
for (var key in val) {
if (typeof(val[key])==«function») {
addMethod(key, val[key]);
}
}
};
+1
Zitrix #
потому что 03:22 :)
0
olegchir #
У меня есть большущий вопрос: какой паттерн нужен для эмуляции паттернов, требующих callback сервера? С использованием современных браузеров, желательно кроссбраузерный.

Например, я хочу чтобы раз в полчаса сервер мог обртиться к загруженной у клиента страничке (eg, 1 секунда), посмотреть сделала ли она нужное дело (eg, отрисовала гуй). И скажем, RPC'шнуть какой-нибудь JS-метод из клиента, например попросить перерисовать что-нибудь еще.

Как сделать это когда есть _нормальный_ клиент и _нормальный_ сервер — понятно. Как сделать это с помощью современного браузера и стандартного Apache? Ладно, а _нестандартного_ Апача, какой модуль нужно написать? Реализовал ли его уже кто-то?
+1
slik #
По моему надо смотреть в сторону Comet
+1
olegchir #
Гугль пробегал и оставил это здесь: en.wikipedia.org/wiki/Comet_(programming)
+1
Vii #
Про паттерны в JS очень (очень!, очень! :)) рекомендую «Pro Javascript design patterns» (не уверен, но на русский ее кажется не переводили, хотя я особо и не искал).

IMHO, книга из серии musthave для JS-разработчика.
+1
olegchir #
А скачать ее можно здесь: book.pdfchm.net/Pro-Javascript-Design-Patterns/9781590599082/

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