ООП Практикум PHP5: эмуляция примесей (mixin) в языке

PHP*
Как-то вечером для реализации моделей поведения в ORM в моем велосипеде фреймворке мне понадобилось что-то, ведущее себя как примесь (mixin) в Ruby или как метод расширения (extension method) в C# (или как трейт / графт в будущих версиях PHP) Я решил для интереса посмотреть, как у меня получится реализовать примеси на PHP. Если вы не знаете, что такое примесь, не беда, сейчас все расскажу.

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



Что такое примесь?


Примесь – это класс, просто предоставляющий свои методы и свойства другим классам. Можно считать, что подмешивание в класс других классов – просто вариант эмуляции множественного наследования, которое в PHP не реализовано. Приведу небольшой пример на псевдокоде, похожем по синтаксису на PHP для ясности:
<?php
mixin Timeable {
    private $timeStarted;
    private $timeStopped;
    public function start()      { $timeStarted = time(); }
    public function stop()       { $timeStopped = time(); }
    public function getElapsed() { return $timeStopped - $timeStarted }
}

mixin Dumpable {
    public function dump() { var_dump($this); }
}

class MyClass mixing Timeable, Dumpable {
    public function hello() {}
}

$a = new MyClass();
$a->start();
sleep(250);
$a->stop();
echo $a->getElapsed();
$a->dump();
?>


Идея понятна? Примеси просто добавляют в класс свой функционал, как будто класс унаследован сразу от них всех. При этом они могут манипулировать членами класса, в который они подмешаны. Вот такой функционал в PHP мы и будем реализовывать.
Давайте поставим себе задачу.
  • Нам нужно реализовать возможность подмешивать в экземпляры заданных классов функционал из указанных классов примесей.
  • Классы-примеси не должны загружаться прежде класса, в который они подмешиваются. В примере вверху использован псевдо-синтаксис, который позволил нам определить классы-примеси прямо в объявлении класса. Но такой способ имеет свои недостатки. Что, если в процессе работы программы нам понадобится добавить плагины, которые будут выступать в качестве примесей к классам нашей системы? В этом случае, мы могли бы где-нибудь в скрипте инициализации объявить все примеси и нам важно, чтобы такое объявление не приводило к загрузке классов.
  • Если примесь подмешивается в какой-то класс, это означает, что ее функционал должен быть доступен и в классе-потомке этого класса. Все же мы используем объектно-ориентированный язык и это будет логично.
  • При реализации желательно учитывать, что использование членов классов примесей не должно быть очень уж тормозным, особенно, если в системе будет использовано много примесей.
  • Модификация существующих классов для использования примесей не должна требовать перепроектировки существующей системы. Как следствие, это означает, что должна иметься другая возможность, помимо наследования от абстрактного класса, для того, чтобы научить класс подмешивать в себя функционал из других классов.
  • Публичные свойства и методы примесей должны быть доступны через экземпляр класса-хозяина(далее я буду называть его «агрегатор», поскольку он может агрегировать в себе несколько примесей). А приватные и защищенные должны быть видны только самой примеси.
  • Примесь должна иметь возможность обращаться даже к скрытым и защищенным полям своего класса-агрегатора (при выставлении такого требования я ориентировался на Ruby, в котором нет скрытых и защищенных свойств в том смысле, в каком они есть в C++, PHP или C#. Там обращаться отовсюду можно к любым полям класса. Но, поскольку примесь может добавлять новое поведение, ей может потребоваться защищенная информация из класса-агрегатора).


Проектируем реестр.


Давайте поразмыслим. Мы можем захотеть добавить разные примеси к разным классам системы. То есть, где-то мы должны хранить информацию о том, к каким классам какие примеси подмешаны. Такая информация для проекта глобальна и должна быть доступна отовсюду. Поэтому для реализации такого хранилища я выбрал статический класс (В PHP нет статических классов в том виде, в каком они есть в C#. Под статическим классом я подразумеваю класс, создавать экземпляры которого нет необходимости. Весь его функционал будет реализован статическими методами, доступными через имя класса). В качестве небольшого задания предлагаю (если вам будет интересно; после того, как вы дочитаете статью до конца) перепроектировать реестр, чтобы использование синглтона не требовалось.

Из вышесказанного следует, что реестр должен уметь регистрировать примеси для классов-агрегаторов. И еще чуть выше мы сказали, что если мы регистрируем примесь для какого-то класса, то функционал этой примеси должен подмешиваться и во все классы-потомки. Получить список классов-предков сразу во время регистрации мы не можем (нам ведь надо избегать загрузки классов, а инспекция иерархии классов этого потребует). Из этого следует вывод, что список соответствий (класс => список примесей) мы построим позже, когда он реально потребуется. Кроме того, такой список нужно будет закэшировать, чтобы при создании новых экземпляров классов-агрегаторов не строить его заново.

class Registry {
	private static $registeredMixins = array();

	public static function register($className, $mixinClassName) {
		$mixinClassNames = func_get_args();
		unset($mixinClassNames[0]);
		
		foreach ($mixinClassNames as $mixinClassName) {
			self::$registeredMixins[$className][] = $mixinClassName;
		
		}

		self::$classNameToMixinCache = array();
	}
}

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

Теперь напишем функцию кэширования. Она должна проходить по списку классов и зарегистрированных для них примесей и добавлять в него все классы-потомки данного с тем же списком примесей. В результате получится кэш.
Для функции кэширования нам понадобится функция, получающая список предков данного класса:
	private static $classNameToMixinCache = array();

	private static function getAncestors($className) {
		$classes = array($className);
		while (($className = get_parent_class($className)) !== false) {
			$classes[] = $className;
		}
		return $classes;
	}

	private static function precacheMixinListForClass($className) {
		if (isset(self::$classNameToMixinCache[$className])) {
			return;
		}
		
		$ancestors = self::getAncestors($className);
		$result = array();
		foreach ($ancestors as $ancestor) {
			if (isset(self::$registeredMixins[$ancestor])) {
				$result = array_merge($result, self::$registeredMixins[$ancestor]);
			}
		}
		self::$classNameToMixinCache[$className] = array_unique($result);
	}


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

Теперь, если нам понадобится получить список примесей для заданного класса, мы можем воспользоваться такой функцией:
	public static function getMixinsFor($className) {
		self::precacheMixinListForClass($className);
		return self::$classNameToMixinCache[$className];
	}

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

	private static $methodLookupCache = array();

	public static function getMixinNameByMethodName($className, $methodName) {
		if (isset(self::$methodLookupCache[$className][$methodName])) {
			return self::$methodLookupCache[$className][$methodName];
		}
		
		self::precacheMixinListForClass($className);
		
		foreach (self::$classNameToMixinCache[$className] as $mixin) {
			if (method_exists($mixin, $methodName)) {
				self::$methodLookupCache[$className][$methodName] = $mixin;
				return $mixin;
			}
		}
		throw new MemberNotFoundException("$className has no mixed method $methodName()!");
	}


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

Программируем примесь.


Итак, примесь. А что примесь? Примесь – это обычный класс. Просто он умеет работать с полями другого класса. И экземпляр этого другого класса будет логично передать ему в конструкторе.
class Base {
	protected $_owningClassInstance;

	protected $_owningClassName;

	public function __construct($owningClassInstance) {
		$this->_owningClassInstance = $owningClassInstance;
		$this->_owningClassName = get_class($owningClassInstance);
	}
}


Я назвал базовый класс примесей Base просто потому, что в моем проекте он принадлежит пространству имен Mixins и называть его более конкретно не требуется. Но вы можете назвать его как вам удобно.

Работать с публичными полями и методами мы можем напрямую через переменную owningClassInstance. А вот со скрытыми и защищенными придется работать через отражение. Ничего сложного. Привожу все определения функций:
	protected $_owningPropertyReflectionCache;

	protected $_owningMethodReflectionCache;

	protected function getProtected($name) {
		if (! isset($this->_owningPropertyReflectionCache[$name])) {
			$property = new \ReflectionProperty($this->_owningClassName, $name);
			$property->setAccessible(true);
			$this->_owningPropertyReflectionCache[$name] = $property;
		}
		return $this->_owningPropertyReflectionCache[$name]->getValue($this->_owningClassInstance);
	}

	protected function setProtected($name, $value) {
		if (! isset($this->_owningPropertyReflectionCache[$name])) {
			$property = new \ReflectionProperty($this->_owningClassName, $name);
			$property->setAccessible(true);
			$this->_owningPropertyReflectionCache[$name] = $property;
		}
		$this->_owningPropertyReflectionCache[$name]->setValue($this->_owningClassInstance, $value);
	}
	
	protected function invokeProtected($name, $parameters) {
		$method = new \ReflectionMethod($this->_owningClassName, $name);
		$method->setAccessible(true);
		$parameters = func_get_args();
		unset($parameters[0]);
		$method->invokeArgs($this->_owningClassInstance, $parameters);
	}


Обратите внимание на то, что здесь я снова задействовал кэширование, чтобы не создавать и не настраивать постоянно экземпляры системных классов для работы отражения. Для сокращения потребления памяти от кэширования можно отказаться, если необходимо.
Кто-то, возможно, уже заметил, что функции method_exists() и property_exists(), которые мы использовали в классе реестра проверяют у примеси наличие и скрытых и защищенных функций с данным именем, наряду с публичными. Это приводит к тому, что у класса-агрегатора получится «попытаться» вызвать и функцию с таким именем, если она определена как скрытая или защищенная. В результате мы все равно получим ошибку, но я предпочитаю сделать это явно:

	public function __call($name, array $arguments) {
		throw new MemberNotFoundException(
				"Method $name is not defined or is not accessible in mixin \"" . get_class() . "\"");
	}

	public function __get($name) {
		throw new MemberNotFoundException(
				"Property $name is not defined or is not accessible in mixin \"" . get_class() . "\"");
	}

	public function __set($name, $value) {
		throw new MemberNotFoundException(
				"Property $name is not defined or is not accessible in mixin \"" . get_class() . "\"");
	}


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

Гм. Вот и все. Примесь готова к употреблению. Остался последний шаг – реализация платформы для подмешивания примесей — классы-агрегаторы. Этим мы сейчас и займемся.

Пишем класс-агрегатор.


Что у нас умеет класс-агрегатор? Он умеет хранить в себе экземпляры классов примесей и вызывать их методы. Ну и к свойствам обращаться. Реализовывать такое поведение мы будем с использованием «магических» методов PHP.

class Aggregator {

	protected $_mixins;

	protected $_className;

	public function __construct($aggregatorClassInstance = false) {
		$this->_className = $aggregatorClassInstance ? get_class($aggregatorClassInstance) : get_class($this);
		$mixinNames = Registry::getMixinsFor($this->_className);
		foreach ($mixinNames as $mixinName) {
			$this->_mixins[$mixinName] = new $mixinName($aggregatorClassInstance ? $aggregatorClassInstance : $this);
		}
	}
}


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

Если объяснение выше показалось вам слишком сложным – не беда. Скользните чуть ниже, там есть примеры. Посмотрите, чем отличается пример Inheritance от примера Composition и как они работают.

Реализуем «магические методы».

	public function __call($name, array $arguments) {
		return call_user_func_array(array($this->_mixins[Registry::getMixinNameByMethodName($this->_className, $name)], 	$name), $arguments);
	}

	public function __get($name) {
		return $this->_mixins[Registry::getMixinNameByPropertyName($this->_className, $name)]->$name;
	}

	public function __set($name, $value) {
		$this->_mixins[Registry::getMixinNameByPropertyName($this->_className, $name)]->$name = $value;
	}

	public function __isset($name) {
		return isset($this->_mixins[Registry::getMixinNameByPropertyName($this->_className, $name)]->$name);
	}


Каждый из магических методов обращается к реестру за информацией. Все просто.

Класс исключения, который мы использовали, выглядит так:

class MemberNotFoundException extends \Exception {}


Посмотрим на несколько примеров



Сначала на традиционную схему с наследованием:

class MixinAggregatorSample extends Mixins\Aggregator {

}

class MixinHello extends Mixins\Base {

	protected $inaccessible;

	public $text = "I am a text!\r\n";

	public function hello() {
		echo ("Hello from mixin!\r\n");
	}
}

Mixins\Registry::register("MixinAggregatorSample", "MixinHello");

$a = new MixinAggregatorSample();
$a->hello(); //Accesing mixed methid
echo ($a->text); //Accessing mixed property
$a->text = "I am also a text!\r\n"; //Setting mixed property
//$a->inaccessible = 'Error here'; //Throws exception
//$a->inaccessible2 = 'Error here'; //Throws yet another exception (Homework: explain, why)
echo ($a->text);
var_dump(isset($a->text));


А теперь взгляните на схему с включением:

class MixinAggregatorSample {

	protected $_aggregator;

	public function __construct() {
		$this->_aggregator = new Mixins\Aggregator($this);
	}

	public function __call($name, $arguments) {
		return $this->_aggregator->__call($name, $arguments);
	}
}

class MixinHello extends Mixins\Base {

	public function hello() {
		echo ("Hellp from mixin!");
	}
}

Mixins\Registry::register("MixinAggregatorSample", "MixinHello");

$a = new MixinAggregatorSample();
$a->hello();


Видите разницу? В случае с включением мы свободны унаследовать наш класс-агрегатор от любого другого без потери функционала. Разумеется, для его нормального использования придется реализовать все магические методы, а не только __call().

Быстродействие



Я произвел некоторые замеры быстродействия получившейся библиотеки. Замеры очень приблизительные, проведены на домашнем компе с открытой IDE, Winamp и всем, что полагается

Time native: 0.57831501960754
Time byname: 1.5227220058441
Time mixed: 7.5425450801849
Time reflection: 12.221807956696


  • Native – время прямого вызова метода класса в PHP
  • Byname — время вызова метода класса через название $myClass->$methodName
  • Mixed – время вызова подмешенного метода
  • Reflection – время вызова подмешенного метода, изменяющего свойство класса через Reflection. Т.е. = mixed + reflection.
  • Время приведено в секундах для 800.000 вызовов.


Я думаю, приведенные цифры вполне приемлемы для того, чтобы подобный подход можно было использовать в большом проекте. Как правило, методы примесей не вызываются тысячи раз в скрипте и 10 микросекунд на вызов метода против 0,7 микросекунд для родных методов вполне приемлемый вариант. Особенно, если учитывать, что время, уходящее на htmlspecialchars(), например, на большом объеме текста или на выполнение запроса к БД гораздо выше.

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

Эпилог


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

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

Если тема примесей в PHP вас заинтересовала, предлагаю также пройтись по гуглу.
+60
7 ноября 2010, 21:39
108

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

+6
DevMan #
Эммм, а чем вам не угодил decorator?
0
DevMan #
Если сразу не понятно, то описание или на родном языке.
–1
FractalizeR #
А какой именно вариант его использования вы предлагаете? Просто… я люблю простые вещи :) Все должно быть как можно более просто, но не проще :) Если реализовывать декоратор так, это ckmyj усложнит схему, на мой взгляд.
+4
DevMan #
Я не предлагаю — просто поинтересовался. Вы ж просили критики ;)
А на счёт простоты — мне кажется магические методы её как раз и убивают (усложняют восприятие кода), хотя иногда без них никуда.
0
FractalizeR #
Я просто не понял, чем вы поинтересовались :)
Декоратор и примесь — это немного разные вещи, если я правильно понимаю. Декоратор добавляет поведение в объект, а примесь — в класс. Декоратор позволяет заворачивать один объект в другой, наращивая функционал. Если рассматривать такое расширение, как вертикальное, то примесь расширяет класс горизонтально, добавляя новое поведение прямо в него, полностью сохраняя существующий интерфейс.

Магические методы действительно немного усложняют все дело в первую очередь потому, что проверки времени компиляции нет и Intellisense тоже обламывается :) Но в целом… Чего еще хотеть от динамических языков? Ведь в этом их гибкость. Если магические методы усложняют, то что можно сказать о Ruby как о языке, в котором, с точки зрения PHP, все магическое?
0
DevMan #
У каждого метода есть свои преимущества и свои недостатки.

Что касается магических методов, я имел в виду, что они усложняют восприятие кода, делая его не очевидным — сам код они то упрощают.
0
Elvis_the_King #
Магические методы — медленно
0
DevMan #
Про это в топике говорилось. Однако, довольно часто этим «медленно» можно пренебречь — не везде эти миллисекунды заметны и критичны.
+3
FractalizeR #
А преждевременная оптимизация — зло. На фоне запросов к БД вызов магических методов — ерунда, не стоящая внимания. Если, конечно, как я уже говорил, они не вызываются в цикле тысячи раз.
+1
graber #
0
FractalizeR #
Да, да, в начале статьи об этом сказано. Но трэйты не имеют состояния. Это только методы без свойств. Больше похоже на Графт, но тоже не совсем оно.

wiki.php.net/rfc/horizontalreuse
0
FractalizeR #
Кстати, как трэйт и графт по-русски? :) Или еще нет аналога?
+1
tzlom #
trait — дословно — черта (незнаю как по русски это будет)
graft — дословно — имплантант (а по нашему встраивание, или инджект xD)
+1
DevMan #
ИМХО, для graft больше подходит «наращивание» или «добавление», а для trait я вижу только «модель».
+1
DevMan #
ИМХО, для graft больше подходит «наращивание» или «добавление», а для trait я вижу только «модель».
Хотя это тот случай, когда понимаешь, а назвать не можешь :)
0
tenshi #
trait — это штрих
0
Yan169 #
Встречал перевод trait как типаж.
0
Disturbed #
Да, если верить вики
+4
b00taNik #
Здесь идет старческое нытье о том, что это костыль, и что на C++ это вообще не нужно

Далее ( в следующем абзаца ) небольшое рассуждение о том, что php — вообще тормознутый, и все надо писать на asm.

А если серьезно, штука полезная, сам подобным костылем пользуюсь нередко.
+3
djsv #
В Yii Framework примеси реализованы в виде классов-поведений, н-р есть базовый класс CComponent, на который можно навешивать своих наследников CBehavior. Ещё подобная практика встречалась вроде в Doctrine.
0
alexerm #
Подход хорош. Так не хватало множественного наследования в PHP.

Мне кажется так более читабельно:

MixingHello::__extend('MixingAggregatorSample');

вместо Mixins\Registry::register
+8
DevMan #
Так не хватало множественного наследования в PHP.
Не холивара ради, но многие с вами не согласятся.
0
FractalizeR #
Это потребует загрузки класса примеси для регистрации. А я уже говорил, что мне хотелось бы этого избежать. Кроме того, не совсем понятно, как тогда кешировать вызовы методов агрегатора. Я так понимаю, вы предлагаете от реестра отказаться?
0
tzlom #
это не реализация множественного наследования, это ближе к шаблонам в С++
+2
DIDJER #
ооо щас начнётся, сразу говорю, каждый метод хорош только для решение определённого типа задачи.
+11
jackxxl #
ИМХО: множественное наследование — не есть хорошо. Если вы думаете, что в процессе решения какой-то задачи нужно множественное наследование — то скорее всего ваша архитектура не верна. Я лично убедился в этом на практике. Хотя раньше также считал «мне не хватает множественного наследования».
–2
FractalizeR #
Согласен. Только примеси — не множественное наследование. Вернее, не совсем оно.
+2
tenshi #
примеси афайк — это и есть множественное наследование, ибо имеют доступ к приватным полям. это приводит к излишней жёсткости классовой модели и нарушению принципа инкапсуляции. планируемые в пхп штрихи этого недостатка лишены ибо действуют только через публичный интерфейс,
–1
FractalizeR #
Просто иметь доступ к приватному полю класса еще не означает наследование. Но на мой взгляд, вы совершенно правы в том, что доступ к приватным полям представляет некоторую опасность. Вообще-то я его реализовал больше для полноты картины, нежели для чего-то существенного. Без него вполне можно обойтись. Вполне достаточно того, что примесь имеет свое состояние и может вызывать, скажем, публичные методы класса, к которому подмешана. Впрочем, необходимость специального оформления агрегатора для поддержки примесей позволяет проектировать его с учетом возможностей, которые вы хотите примесям предоставить. Так что, даже доступ к приватному полю, если вы его планируете и желаете, может не быть большой проблемой.

Кстати, в моей реализации с доступом к приватным полям будут некоторые проблемы :) PHP Reflection не позволяет обратиться к приватному полю класса, определенному в предке через имя класса-потомка. Нужно обращаться через имя класса, в котором приватное поле определено непосредственно. А с этим есть некоторые проблемы производительности (требуется перебирать всю иерархию). С доступом к protected членам проблем нет.
0
tenshi #
а что же означает? объект и примесь хранят данные в одном объекте и тем самым накладывают друг на друга кучу ограничений. или у тебя для каждой примеси создаётся отдельный объект с состоянием? тогда это никакая не примесь, а агрегация с делегированием.
0
FractalizeR #
Да, для каждой примеси внутри объекта-агрегатора создается объект, хранящий состояние примеси. Доступ к этому объекту имеет только слой агрегатора, который отвечает за диспетчеризацию вызовов.

Разумеется, это не настоящая примесь, а только эмуляция, о чем красноречиво говорит заголовок статьи.

Агрегация и делегирование требуют ручной реализации методов агрегированных объектов. В моей реализации все автоматизировано. Агрегация и делегирование — более фундаментальные понятия, чем примесь, которая на них основана. Я бы не стал путать эти понятия.
–1
tenshi #
это не эмуляция, а реализация совершенно другого. и ничего агрегация с делегированием не требует, не выдумывай.
0
FractalizeR #
Почему же совершенно другого? Вы можете как-то обосновать свое мнение? Что такого «совсем другого» есть в моей реализации? Что, совсем на примесь не похоже? Ни капельки?

Что касается агрегации с делегированием, реализация методов все же требуется. Приведу два примера агрегирования:
class A {
   public function hello();
}

class B {
   public function hello2();
}


class Aggregator1 {
    private $a;
	private $b;
	
	public function __construct() {
	    $this->a = new A();
		$this->b = new B();
	}
	
	public function hello() {
		$this->a->hello();
	}
	
	public function hello2() {
	    $this->b->hello2();
	}
}

$test = new Aggregator1();
$test->hello();
$test->hello2();

class Aggregator2 {
    public $a;
	public $b;
	
	public function __construct() {
	    $this->a = new A();
		$this->b = new B();
	}
}

$test = new Aggregator2();
$test->a->hello();
$test->b->hello2();


Второй пример Aggregator2 достаточно неуклюж, правда? Не агрегирование, а кошмар какой-то. С таким же результатом можно отдельные объекты иметь.

А первый пример Aggregator1 для добавления в Aggregator1 реализации новых методов helloX() требует их явного определения. Разве не так? Или я вас не так понял?

и ничего агрегация с делегированием не требует, не выдумывай.

Если не возражаете, я бы вас попросил о более уважительном отношении к собеседнику. Возможно, я вас не понял, но, в таком случае, может быть, вы сможете сделать свою точку зрения более понятной?
0
tenshi #
я уже обосновал в предыдущем сообщении.

первый пример использует делегирование, второй не использует. любой ооп язык поддерживает делегирование без явного объявления методов (__call, doesNotUnderstand, method_missing, __noSuchMethod__)
0
FractalizeR #
Насчет «любой ООП язык» можно поспорить. Pascal например, как в варианте Freepascal, так и в Дельфи-варианте не поддерживает делегирование без явного объявления методов. Произвольный метод по имени можно вызвать только через RTTI. С# — аналогично. Вы не можете вызывать произвольный метод объекта просто так, без плясок с бубном. Разве что использовать dynamic модификатор, но это уже совсем другая песня (которая, кстати, не так давно появилась в языке). Я не большой спец в Ява, но, насколько мне известно, этот язык тоже не поддерживает «прямое делегирование». Оно поддерживается только такими языками, как PHP, Ruby, Python, которые не всегда ООП, но просто динамические по своей природе.

По поводу «это не эмуляция, а реализация совершенно другого» вы мне так и не ответили.

По поводу делегирования в моем примере вы отчасти правы. Совершенно верно, в первом варианте используется делегирование + агрегация на статической типизации. Но для достижения функционала примеси мне нужна динамическая типизация, то есть прямое делегирование вызова метода. В PHP оно реализовано через __call. Вот именно такое я и реализовал. А в моем примере с Aggregator1 в него невозможно добавить другой функционал без изменения объекта, что должно быть возможно с примесью.
–2
tenshi #
нельзя поспорить. приведённые языки не являются объектно ориентированными в отличие от руби и питона. динамизм — основа ооп.
0
FractalizeR #
Что за бред вы несете?

Wikipedia и авторы языков с вами не согласны.

Идите, расскажите Гослингу и Липперту, что их языки вовсе даже не объектно-ориентированы.

Впрочем, я понял. Вы просто тролль. Сначала мне это показалось даже забавным, а сейчас стало скучновато.
0
tenshi #
это их половые трудности. автору термина виднее что он означает
0
FractalizeR #
Да что вы? Как грубо. Я вам ответил внизу. Почитайте.
0
Mikhus #
Полностью согласен с tenshi. Вы лукавите говоря о неудобстве в предыдущем примере:
class A {
	public function aa() {
		echo __METHOD__ . " called!\n";
	}
}

class B {
	public function bb() {
		echo __METHOD__ . " called!\n";
	}
}

class AggregateDelegate {

	protected $_objects = array();

	public function __construct() {
		$args = func_get_args();
		for ($i = 0, $s = sizeof( $args); $i < $s; $i++) {
			if (!is_object( $args[$i])) {
				throw new Exception( 'Invalid argument!');
			}
			$this->_objects[] = $args[$i];
		}
	}

	public function __call( $name, $args) {
		if (!method_exists( $this, $name)) {
			for ($i = 0, $object; $object = $this->_objects[$i]; $i++) {
				if (method_exists( $object, $name)) {
					return call_user_func_array(
						array( $object, $name),
						$args
					);
				}
			}
		}
	}

}

class C extends AggregateDelegate {

	public function cc() {
		echo __METHOD__ . " called!\n";
	}

}

$c = new C( new A(), new B());
$c->aa();
$c->bb();
$c->cc();


А можно и так:

class C extends AggregateDelegate {

	public function __construct() {
		parent::__construct( new A, new B);
	}

	public function cc() {
		echo __METHOD__ . " called!\n";
	}

}

$c = new C;
$c->aa();
$c->bb();
$c->cc();


В конкретной реализации возможно следует продумать как разруливать последовательности объектов и их methods overloading, но для примера и так сгодится.

При этом откуда взялись A и B нас мало волнует. Ибо, как правило, такое может быть нужно именно тогда, когда A и B — 3'd-party (из понятных соображений). А там где «все мое» ваша эмуляция «mixins» вдвойне неоправданна и проблематична… ИМХО.
0
FractalizeR #
Так я и не отрицаю. Просто и вы и Господин тролль слишком увлеклись критикой. Я никого не призывал использовать примеси в рабочих проектах. Наоборот, я говорил, что прежде, чем это использовать, нужно хорошенько подумать. Я демонстрировал только интересный концепт, а не проповедовал использование примесей где надо и не надо.
+4
MyraJKee #
+. Не зря умные дядьки которые проектировали JAVA, отказались от множественного наследования.
+3
FractalizeR #
А Страуструпп и авторы языков Common Lisp, EuLisp, Curl, Dylan, Eiffel, Logtalk, Object REXX, Scala, OCaml, Perl, Perl 6, Python, и Tcl, видимо, протупили, допустив множественное наследование в том или ином виде в этих языках? :)

Я бы не был столь категоричным. Конечно, множественное наследование имеет свои недостатки. Однако и некоторые достоинства ему тоже присущи. Недостатки же его столь существенны, что начинающим программистам, видимо, лучше вообще его в руки не давать.
0
MyraJKee #
Я бы не был столь категоричным

Да, наверное :) просто я как-то считаю JAVA лучшим образцом объектно-ориентированного языка программирования…
0
FractalizeR #
А мне C# нравится. Но для меня это не значит, что все остальные языки — менее объектно-ориентированы :)
0
MyraJKee #
Дык я не спорю что менее ОО, скорее может быть менее логичны и не такие «стройные», ну это конечно все субъективно. Мой начальник вообще Perl предпочитает любому другому языку, а на JAVA плюется.
0
FractalizeR #
Фанатов Brainfuck мне еще встречать не доводилось, но вполне допускаю, что и такие найдутся :)
–1
khizhaster #
А кроме java какие ОО(П) языки вам известны в достаточном и одинаковом с java объёме, что бы сравнивать и что бы делать такие выводы?
0
MyraJKee #
Хм, в достаточном и ОДИНАКОВОМ объеме ПХП разве что. Знаком с C++, Object Pascal и Smalltalk…
Это допрос?)
–2
khizhaster #
Неа, это всего лишь подтверждение того, что делать такие выводы не зная ни единого ООП языка, кроме java бессмысленно :) Ясное дело, что единственный хорошо известный язык и будет «лучшим» образцом.

(php можно не учитывать, этот мусор на ООП не тянет совершенно).
0
FractalizeR #
Я думаю, ваше мнение о PHP тоже можно не учитывать :)
0
khizhaster #
Да я даже о себе говорить не буду, просто ради интереса посоветую поискать по запросу «пхп» на хабре, здесь было уже десятка три холиваров на тему «ООП в ПХП» и всегда сходится к одному мнению, что ооп в этом мусоре как пятая нога, ни реализации, ни применения. Особенно если учесть, что ооп себя оправдывает в проектах в десятки (а то и сотню) тысяч строк, а судя по современным проектам (стартапам, хе-хе!) таких насчитываются единицы. Зато о модной теме ООП судачат все, кому не лень и пихают его везде, где ни попадя, при чём в 95% процентах не представляя себе, что это такое и как же его с умом применить. Почитайте эти холивары, там много чего интересного есть в плане статистики и выводов, которые люди делают. Не говоря о применении, уж реализацию обсосали со всех сторон. Там, в этих постах, и будет опровержение вашего вывода.
+3
FractalizeR #
здесь было уже десятка три холиваров на тему «ООП в ПХП» и всегда сходится к одному мнению, что ооп в этом мусоре как пятая нога, ни реализации, ни применения./blockquote>
Не хочу никого оскорбить, но кто именно высказывал такое мнение? Этот человек/люди имеет опыт программирования на PHP? У него большой опыт программирования в целом? Кто конкретно развел холивар? Школота, купившая инвайт на Хабр за $25 и кричащая «Я — крутой профи», а другие — отстойные недоноски? Это мнение высказывалось аргументировано? Делались сравнения, обобщения, анализ? Или просто так — кинул в пустоту «PHP — гавно» и пошел дальше?

Особенно если учесть, что ооп себя оправдывает в проектах в десятки (а то и сотню) тысяч строк, а судя по современным проектам (стартапам, хе-хе!) таких насчитываются единицы.

То есть, мы говорим уже о том, что ООП вообще не нужен в небольших проектах?
Кстати, а что работа Facebook, Yahoo Bookmarks (Delicious) и Dailymotion на PHP вам недостаточно? Проекты, скажем так, не маленькие. Впрочем, я соглашусь, что сайтов действительно астрономического масштаба на PHP действительно не так уж много. Впрочем, их вообще в мире не так уж много, не так ли?

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

Если вы накидаете мне ссылок, я с удовольствием почитаю. Если, конечно, в этих ссылках не говносрач пустой, а какая-то более-менее взрослая дискуссия с аргументами или хотя бы ссылками на мнения уважаемых людей.
0
MyraJKee #
Я представляю что такое ООП в C++ и Object Pascal, этого не достаточно? Javascript и PHP для вас видимо совсем не авторитеты… )
PHP развивается достаточно динамично, PHP 5.3 это уже далеко не 4.3… Так что не надо так категорично… Хотя спорить об ООП в ПХП дело неблагодарное… )
–13
Vladson #
Слова РНР и быстродействие это несовместимые вещи, человек знающий РНР на достаточном уровне никогда не скажет слово «быстродействие» ибо оно (хоть и заметно ниже чем в других языках, особенно компилируемых) НИКОГДА не бывает узким местом!!!
+2
DevMan #
НИКОГДА не бывает узким местом
Ой, да шо вы говорите. Серьёзно?
+2
Vladson #
Если в вашем проекте РНР слабое место, вы выбрали или не тот ЯП или приложение спроектировано криво.
+1
FractalizeR #
Мы ведь говорим о том, что слабое место — быстродействие, а не PHP. Или уже все поменялось? :)
+1
Vladson #
Я говорю о том что нет смысла измерять быстродействие РНР, ибо когда РНР был использован к месту и грамотно, оно не будет слабым местом. (вот уж удивлён что такие простые истины приходится повторять)
+1
FractalizeR #
Измерять быстродействие чего бы то ни было бессмысленно, если все в этом чем-то было использовано к месту и грамотно. Весь вопрос в том, что нужно измерить быстродействие, а затем, если оно неудовлетворительно, искать вещи, использованные не к месту или неграмотно.

Если бы были на свете программисты, умеющие использовать ВСЕ БЕЗ ИСКЛЮЧЕНИЯ к месту и грамотно, у нас бы не было программ с багами и тормозами.
0
b00taNik #
«Использовать к месту», «использовать грамотно», «не тот ЯП» — вроде люди взрослые, а ругаетесь как первоклашки в столовке. Использовать надо то, что удобно.

Мне вот удобно сайты на PHP, под винду приложения на C#, а сервера на Java, и с кем бы я не говорил, я найду кучу аргументов что так надо, и не потому что «грамотно» и т.д., а потому что мне так удобно.
0
FractalizeR #
Мне кажется, вы не прочитали нашу «перепалку». Она началась с «Слова РНР и быстродействие это несовместимые вещи».
0
Vladson #
А мне кажется вы прочитали не то что написано, а придрались к словам.

Оптимизация даже одного SQL запроса, в 99% пересилит любую РНР-оптимизацию, по этому замерять скорости разных подходов я не считаю разумным, использовать надо тот который даст читабельный и удобно-поддерживаемый код. В случае если вы пишете свой гугл, там да, каждая милисекунда важна, но разве вы станете писать его на РНР?
0
FractalizeR #
Вы все очень правильно говорите. Если вы посмотрите на мои посты, вы увидите, что именно в этом я и пытался убедить Vladson, к сожалению, используя его аррументацию, из-за чего оказался непонятым.
0
FractalizeR #
Знаете, я и в PHP и в любом другом языке могу такую муть навернуть, что она любой сервер подвесит намертво на сайтах с одной страничкой :) Вот вам и не бывает быстродействие в PHP узким местом…
+3
kashey #
Я бы честно сказал — не удобно.
Приместь это смесь декоратора и интерфейса. Точнее примесь это встроенный в обьект интерфейс декоратора( ;=P )
И что делать если одинаковые функции есть как в базовом класе, так и в примесях(нескольких).
Имхо без управления очередность исполнения цепочки — труба.
У меня этот момент объединения классов называется просто — interference
/** interference (Яндекс.Словари — Перевод ABBYY Lingvo)
* 2. сексуальное домогательство, изнасилование

Назвал так, потому что это действительно так.
Да и как физическое явление хорошо подходит.
–1
FractalizeR #
Совершенно верно. Что делать, если есть одинаковые функции в примеси и основном классе решает язык. В Ruby я так и не понял, какие правила действуют в данном случае. В моей реализации при конфликте имен будет произведен доступ к полю/методу, примесь которого была помешана первой.

Я не хочу сказать, что это хорошо. Это отвратительно. Неоднозначности и сложности такого рода меня всегда раздражают. Но, в принципе, и без примесей в ООП языках достаточно возможностей, которые легко использовать неправильно.
+3
xscript #
Не думаю, что использовать подобные «архитектурные решения» полезно в проектах. Подумайте о тех людях, кто будет поддерживать этот код, у них задач хватает. Практичней использовать декоратор(о котором все знают), чем обучать команду разработчиков и поддерживать вот это
–1
FractalizeR #
Я уже отмечал, что декоратор сильно отличается от примесей. Примесь — достаточно стандартный подход в программировании, о котором многие знают. Кто интересуется чем-то помимо своего единственного и неповторимого языка программирования, конечно.

Примеси реализованы в таком фреймворке, как Symfony, например. Думаю, мнение Фабьена достаточно весомый аргумент.

Да и вообще, данная статья не ставила своей целью пропаганду использования примесей везде и всюду. Я просто выполнял упражнение по реализации в языке возможности, в нем изначально отсутствующей. Я вовсе не призываю ее широко использовать или вообще использовать. Это просто упражнение. Практикум. Экзерсис :)
+6
Porshen #
1) я бы за такой фреймворк оторвал руки и ни в коем случае не пустил на бой.
2) пока читал, вспомнил двух знакомых, способных на подобное. у них хоть обе руки оторви — все равно не прекратят такими абстракциями баловаться.

Думать, ребята, надо о поддерживаемости кода. Когда бизнес-логика начинает занимать львиную долю строк в классах, подобное баловство, допущенное на старте проекта, станет вашей постоянной головной болью. А бложик набросать с такой штуковиной — вполне прокатит
+1
graber #
в целом согласен, но думаю что область применения подобных техник лежит в другой плоскости: в библиотечных классах и различных апи. вот, например, в доктрине таких «магий» навалом. там это во много оправдано.
0
Porshen #
любое API должно быть прозрачным как и любая другая часть проекта. в большом проекте может присутствовать несолько API для каждой из подсистем. я бы не стал использовать магическое API даже если это сторонняя библиотека, Doctrine в частности.
взгляните на Zend Framework — там местами не всё так элегантно или иногда мелочевки какой-то не хватает, но вот зато там все ясно и прозрачно.
+1
graber #
ну :) хоть это и немного офтоп, но я бы на вашем месте не стал ручаться за «прозрачность» zf. одна диспетчеризация чего стоит. на этот процесс влияет столько внешних факторов: сплошная магия.
вот что будет после:

$this->_forward('some-action');
$this->getRequest()->setDispatched(true);

Это прозрачно? ;)

Я просто хочу сказать что не всегда удается использовать исключительно ясные и прозрачные методы. Но это не значит, что к этому не нужно стремиться.
0
dVaffection #
$request->setActionName($action)
        ->setDispatched(true);

0
dVaffection #
setDispatched(true)
— значит, что запрос был отправлен на диспечирезацию. Другими словами вызывая переход на новый экшн из некого контроллер-экшена, при этом устанавливая этот флаг в true — значит, что этот экшнн не будет включен в цикл дистпечеризации.

Но мне остался непонятнен ваш вопрос о прозрачности цикла диспечеризации?
0
FractalizeR #
www.symfony-project.org/book/1_0/17-Extending-Symfony

Фабьену тоже руки оторвем? Вместе с Yahoo? :)
0
chEbba #
Я бы оторвал за ветку 1.х — ужас полный. Когда я увидел код 2ой версии, мне даже не верилось, что это один и тот же человек писал =) Кстати во второй ветке я не видел этих извратов, хотя может просто пропустил.
+1
FractalizeR #
А мне понравилась и первая. Хотя, нельзя отрицать, что код там в целом более монолитен и создает впечатление не модульного, а цельного.
0
chEbba #
Я бы сказал, что он не создает впечатление а таковым и является =) и вообще код на уровне php4, ООП модель используется довольно слабо, а иногда просто непонятным способом (это, например, про миксины).
И огромный респект Фабиену, за труд в 2х ветке, где фактически все переписано с нуля.
+1
FractalizeR #
А примеры есть? То, что код вам не понравился, это, конечно, плохо. :)

Но объективные доводы есть какие-нибудь?
0
develop7 #
таковым и является
ну и приведите пару фактов, проиллюстрировать
код на уровне php4
и будьте любезны формальное определение «уровня php4», «уровня php5» и остальных «уровней» кода
ООП модель используется довольно слабо
и снова — будьте любезны, парочку фактов, иллюстрирующих это, в студию
иногда просто непонятным способом (это, например, про миксины)
вот в том числе и за них symfony в народе зовут «рельсами на php».
0
develop7 #
Я бы оторвал за ветку 1.х — ужас полный
О как. А поконкретнее? И да, а что не ужас?
0
chEbba #
Поконкретнее чуть выше.
Не ужас: zf 1.x, symfony 2.x. Это как минимум в плане кода и архитектуры.
+1
FractalizeR #
ZF 1.x я не сильно изучал, но мне очень не нравится там один момент. require_once везде по коду раскидано. В 2.x они собираются от этого избавиться.

Кстати, модули ZF писались совсем разными людьми и в коде все же прослеживается несколько различные подходы к программированию местами. Так что, если говорить, что нравится код ZF, надо конкретизировать, какой пакет :)
0
develop7 #
а, дада, помню Zend Library.
Вот лично мне нравится не руками каждый раз собирать проект из кирпичей с нуля, а через начинать имплементить бизнес-логику 2 часа после старта проекта. symfony даёт такую возможность искаропки. Плюс туева хуча плагинов.
0
FractalizeR #
А почему из кирпичей и с нуля? Мне тоже не очень понравилась ZF, но это просто потому, что когда я выбирал между ней и Symfony, я долго пытался разобраться с MVC у Zend, потом прочел доку по Symfony и въехал с полпинка. Так что — чисто субъективно.

Кстати, NetBeans поддерживает быструю разработку для ZF: blogs.sun.com/netbeansphp/entry/zend_framework_support_added
0
develop7 #
Я ещё в начале 2009 его пробовал посмотреть всерьёз, так что не исключаю, что многое могло поменяться. Тамошний quickstart чуть менее чем полностью состоял из копипасты и ручного кода. Не впечатлило и забил. Особенно умиляли популярные тогда «layoutы» — «а вот у меня так файло лежит в проекте», «а у меня вот эдак», «а у меня — вообще вот так».
0
FractalizeR #
Да, кстати. Точно так и было. Теперь с этой стороны все более-менее в порядке. А глубже пока не лез. Для простых вещей мне своего хватает, а для сложных — жду Symfony2.
+1
ionicman #
Как пример технологии конечно интересно, спасибо за полезную информацию, но ИМХО множественное наследование — зло, тем более в языке где оно не реализовано. Это как стандартное ООП делать на прототайпах в JS :)

В одной книге по программированию были слова Бьерна по поводу того что «Множественное неаследование — это очень мощный но в тоже время опасный инструмент, сильно усложняющий понимание кода, использовать его надо ТОЛЬКО там где оно ДЕЙСТВИТЕЛЬНО необходимо, а таких мест весьма немного».

И я с ним согласен, кроме того большинство задач решаются абсолютно нормально без него если грамотно проектировать.
0
kashey #
Если немного закрыть глаза на скорость — то «стандартное» ООП на JS делается изумительно.
Если не закрывать — то пострадает либо стиль кода, либо приват переменые( и вообще переменые с одинаковыми названиями в иерахии классов)

Кстати — те же примеси на js делаются не просто, а очень просто.

А множественное наследование в виде примесей — очень полезная и крайне мощная чтука.
Могу привести пару примеров.
Хотя, конечно, можно сделать тоже самое и без примесей. Но с ними — «каноничней» и, как не странно, прозрачней…

А вообще представим себе что обьект состоит из трех частей. Все три части используются в конечном классе, но друг на друга им совершенно наплевать и вообще они сами по себе не работают.
Собирая как бы из блоков конечный класс и навертывая на него малек функционала получаем конфетку.
Например возьмем ORM как db+query+loger+cache. Каждый компонент умеет только что-то свое — отправлять нативные запросы к БД, парсить «наш» язык запросов, что-то логировать и кешировать.
Конечный класс переводит запрос в понятный базе, на запросы навешивает кеш и логирует медленные запросы.
Можно сделать через различные тама драйвера, декораторы, интерфейсы и враперы.
Можно написать
query(){
cache:run();
query:parse();
log:log(database:query())
}

А можно просто везде определить(интерфейс) функцию query и в конечном класе описать порядок разрешения конфликта имен.

На выходе будет одни и те же фломастеры.
0
tenshi #
супер, и каждый объект будет жёстко привязан к конкретному логгеру, базе и кэшу. а если хочется закоммитить что-нибудь в другую базу — извольте писать новый орм? х)
+1
ionicman #
На JS надо писать теми средствами и тем «ООП» который там есть — поверьте он ничем не хуже, а иногда даже удобней и этим поражает адептов ООП :)
0
ojiga #
множественное наследование не нужно. это против природы OOP
0
FractalizeR #
Авторы языков C++, Curl, Dylan, Eiffel, Logtalk, Object REXX, Scala, OCaml, Perl, Perl 6, Python, и Tcl с вами несогласны.
0
tenshi #
как и любое другое наследование
+2
FractalizeR #
Даешь функциональное программирование! Долой грязное ООП! :)
–6
tenshi #
ты дурак?
+3
FractalizeR #
Вообще, если вы заметили, там смайлик стоит. Т.е. это шутка была. Как и та фраза, на которую я ответил.

А вы вообще понятие о правилах приличия имеете? Или вам эго мешает им следовать? Элементарное понятие об уважении, я смотрю, у вас вообще отсутствует.
0
tenshi #
я не шутил
0
FractalizeR #
Значит, вы плохо подготовлены в теоретическом плане. Наследование — одна из фундаментальных концепций ООП: en.wikipedia.org/wiki/Object-oriented_programming#Fundamental_concepts_and_features

Конечно, еще есть программирование на основе прототипов, но это тоже похоже на наследование, только немного в другом плане.

У вас есть аргументы в защиту вашей точки зрения «наследование не нужно, оно против природы ООП»?
0
tenshi #
userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay_oop_en
+1
FractalizeR #
Признаться, вы меня удивили. После вашего тупейшего поста про презервативы на Хабре, я думал, этот аргумент в свою защиту вы не приведете. Что ж, давайте немного подискутируем.

Вы хорошо текст прочли? Алан говорит

At Utah sometime after Nov 66 when, influenced by Sketchpad, Simula, the design for the ARPAnet, the Burroughs B5000, and my background in Biology and Mathematics, I thought of an architecture for programming. It was probably in 1967 when someone asked me what I was doing, and I said: «It's object-oriented programming».


I didn't like the way Simula I or Simula 67 did inheritance (though I thought Nygaard and Dahl were just tremendous thinkers and designers). So I decided to leave out inheritance as a built-in feature until I understood it better.


Он не говорит, что наследование против природы ООП. Он говорит о том, что ему не понравилось, как его реализовали в языках Simula I и Simula 67. То есть, к тому времени, как Алан формулировал свои выводы, наследование уже было «изобретено». Алан также говорит следующее:

So I decided to leave out inheritance as a built-in feature until I understood it better.
.

Вам понятна эта фраза?

Термин «объекто-ориентированный» стал появляться гораздо раньше, чем был изобретен Smalltalk — еще в 50-60 годах. В Sketchpad наследование и объекты уже были (применительно к графическим примитивам, правда, почитайте, что такое Sketchpad), а датируется эта система шестидесятым.

Smalltalk Алана был создан почти на 10 лет позже Симулы. Он вобрал в себя многие ее идеи, но в отличии от Симулы, этот язык полностью динамический (в отличии, от Симулы, где типизация статична).

К чему я веду. При всем моем уважении к Алану Кэю, нельзя говорить о том, что он один-единственный изобрел ООП и он один-единственный имеет право говорить о том, что такое ООП, а что нет, что в нем хорошо, а что плохо. ООП — это парадигма программирования. Она осмысливалась, обсуждалась и формировалась под влиянием множества людей, каждый из которых вносил свой вклад. В шестидесятых Кристен Нюгорд (жаль, что этого великого человека с нами уже нет) выразил свое понимание ООП в Симуле. Через почти десять лет Алан Кэй сделал это в Smalltalk. Каждый из этих людей внес свой вклад в ООП как парадигму программирования. Каждый из них что-то делал по-своему. Никто из них никогда не старался присвоить себе право «изобретателя ООП».

Да, конечно, термин ООП ныне слегка расплывчатый. Но, мне кажется, так бывает всегда, когда формулируется новая концепция, над которой работает множество людей. В 2003 Митчел говорил о том, что термин ООП включает в себя динамические вызовы, абстракцию, полиморфизм и наследование. А чуть позже в 2006 Ли Скотт упоминает только инкапсуляцию, наследование и динамические вызовы.
0
tenshi #
он говорит, что наследование совсем не является основой ооп и оставлено лишь на всякий случай — типа вдруг пригодится.

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

0
FractalizeR #
Неужели? Где это он так говорит? Вы вообще прочитали, что я вам написал, или так просто, троллим опять?
0
developer #
php программисты — это такие очень хитрые и изворотливые программисты.
0
FractalizeR #
Жизнь у нас такая :)
0
maxidler #
Заинтересованным стоит посмотреть limb-project.com на предмет пакета lmbToolkit.
Имеющим практическую необходимость в использовании Traits стоит собрать PHP-5.4 (trunk) и использовать его там Traits уже есть.
0
FractalizeR #
lmbToolkit интересен. Правда, это не совсем то, что примесь, конечно, но все равно было очень любопытно. А вот ветки PHP 5.4, кажется, уже нет: https://svn.php.net/repository/php/php-src/branches/ После долгих дебатов ее ведь, кажется, удалили.
+1
maxidler #
Я имел ввиду trunk. Он собирается как PHP 5.3.99-dev
А про удаление — скорбная новость. Теперь Traits придется ждать еще долго. Итак уже 2 года жду ( с момента публикации патча в internals )
+1
toxicmt #
Вижу много ругани, что множественное наследование зло, но примеси ничего общего с множественным наследованием не имеют (если мы говорим про ruby). Каждая новая примесь встраивается в иерархию наследования становясь родителем для текущего класса.
0
VolCh #
А в чём разница с множественным наследованием? Каждый новый базовый класс тоже встраивается в иерархию наследования, становясь родителем для текущего класса :)

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