Mixins (примеси) в PHP @ DeForum.ru
DeДверь  
Логин:  
Пароль:  
  Автологин  
   
Разместить рекламу
Письмо админу
Правила | FAQ | *Поиск | Наша команда | Регистрация | Вход
 
 
На страницу 1 2  >  Страница 1 из 2 [ Сообщений: 36 ] 
*   Список форумов / Начинка и техника / Программирование для WWW » ответить » создать топик « | »
Автор Сообщение
AlexShop Муж.
участник
34
Сообщения: 1866
Зарегистрирован: 17.02.04
Заголовок сообщения: Mixins (примеси) в PHP
Сообщение Добавлено: 22 Июнь 2008, 05:31:44 
Здраствуйте коллеги,
хочу поговорить о том как сделать примеси (mixins) в PHP.

Допустим класс iPhone должен обладать функциональностью: Phone и iPod классов.
В PHP не подерживается множественное наследование, поэтому iPhone может наследовать только один класс (допустим Phone).

Что бы использовать реализацию другого класса (iPod):
• можно использовать Object composition
• можно сделать примесь
В каком случае использовать композицию, а в каком примесь - это отдельная тема.

В PHP есть одна хитрая вещь:
Код:
class Phone {}

class iPod
{
   public function play()
   {
      return "Playing ".$this->album;
   }
}

class iPhone extends Phone
{
   public $album;

   function play()
   {
      return iPod::play();
   }
}

$iPhone = new iPhone();
$iPhone->album = "The Wall";
echo $iPhone->play();

// Результат: Playing The Wall
Вы заметили, что iPhone не наследует iPod, но использует его метод play();
В данном контексте, в методе iPod::play() переменная $this указывает на Phone, а не на iPod.
Поэтому все работает!

Определимся с терминами: здесь класс iPod - "примесь".

Если мы хотим использовать другие методы класса iPod, то iPhone будет выглядеть примерно так:
Код:
class iPhone extends Phone
{
   public $album;

   function play() { return iPod::play(); }
   function nextTrack() { return iPod::nextTrack(); }
   function previousTrack() { return iPod::previousTrack(); }
   // и т.д.
}

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

Недостаток этого способа в том, что не красиво перечилять все методы (play, nextTrack, previousTrack).
Но у этого метода есть свои плюсы:
• очень наглядно видно, какие методы вызывают другие классы
• если у вас более 20-30 методов - вам надо пересмотреть структуру класса.
• iPod не является специальным "примесь" классом. Это может быть любой другой класс.

По теории, класс-примесь: "не предназначен для порождения самостоятельно используемых объектов".
Но в нашем случае класс iPod может порождать свои объекты. Я считаю это пока плюсом.

Поэтому я бы остановился на этом, но ради интереса продолжим: нельзя ли сделать что лучше?

В идеале мы не хотим перечислять все методы. Класс iPhone должен наследовать различные примеси.
Вызывая метод iPhone, класс должен:
• посмотреть есть ли такой "родной" метод
• если нет, то вызвать метод из примеси

Напрашивается магическая функция __call()! Сделаем простейший пример:
Код:
class iPhone extends Phone
{
   public $album;

   function __call($method, $args)
   {
      return call_user_func_array(array("iPod", $method), $args);
   }
}
Заменяем старый класс на новый. Запускаем и … опаньки получаем ошибку:
"Fatal error: Using $this when not in object context".

Есть статья "Mixins in PHP" которая решает эту проблему без eval(). Дадим слово автору:

Цитата:
"Два объекта — основной, и примесь. Основной объект хранит ссылки на экземпляры всех примесей. Примеси определяют методы, отсутствующие в основном объекте. В магическом методе __call() основного объекта в цикле по всем примесям определяем наличие в них требуемого метода. Если метод найден — передаем управление ему, если ни в одной из примесей метода нет — что ж,… эксепшен!"


Я отброшу массивы (где храняться примеси) и циклы по ним, а сделаю простейший пример согласно статье:
Код:
class Phone {}

class iPod
{
   private $obj;

   function __construct($obj)
   {
      $this->obj = $obj;
   }

   public function play()
   {
      return "Playing ".$this->obj->album;
   }
}

class iPhone extends Phone
{
   public $album;

   function __call($method, $args)
   {
      // Здесь должен быть код, который просматривает и выбирает нужную примесь
      $iPod = new iPod($this);
      return call_user_func_array(array($iPod, $method), $args);
   }

}

$iPhone = new iPhone();
$iPhone->album = "The Wall";
echo $iPhone->play();

Теперь у нас все работает, как надо. Осталось только как советует статья:
1. создать специальный класс Caller, написать там всю реализацию
2. наследовать iPhone от Caller.

Но нашем примере iPhone не может наследовать Caller. Потому что iPhone уже наследует Phone класс!
Что тогда делаем? Помните самый первый пример? Вместо того что бы наследовать, мы используем класс:
Код:
class Caller
{
   function call($method, $args)
   {
      // Здесь должен быть код, который просматривает и выбирает нужную примесь
      $iPod = new iPod($this);
      return call_user_func_array(array($iPod, $method), $args);
   }
}

class Phone {}

class iPod
{
   private $obj;

   function __construct($obj)
   {
      $this->obj = $obj;
   }

   public function play($track)
   {
      return "Playing track $track from album ".$this->obj->album;
   }
}

class iPhone extends Phone
{
   public $album;

   function __call($method, $args)
   {
      return Caller::call($method, $args);
   }
}

$iPhone = new iPhone();
$iPhone->album = "The Wall";
echo $iPhone->play(2);

В моем примере так же показано (в отличие от примера в статье) что методы могут принимать параметры.
Ну а все остальное вы уже сами додумаете.

---------
Многое взято от сюда:
http://www.schleicher.ru/blog/183.html
http://advogato.org/article/470.html

_________________
Тот, кто задает вопрос, глупец в течение пяти минут, тот, кто его не задает, глупец всю свою жизнь. (Китайская поговорка)


Последний раз редактировалось AlexShop 22 Июнь 2008, 18:43:28, всего редактировалось 1 раз.
AlexShop Муж.
участник
34
Сообщения: 1866
Зарегистрирован: 17.02.04
Заголовок сообщения: Re: Mixins (примеси) в PHP
Сообщение Добавлено: 22 Июнь 2008, 18:42:03 
упс дубль

_________________
Тот, кто задает вопрос, глупец в течение пяти минут, тот, кто его не задает, глупец всю свою жизнь. (Китайская поговорка)
Crazy Муж.
Модератор
107
Сообщения: 14561
Зарегистрирован: 23.12.01
Откуда: Moscow
Сообщение Добавлено: 22 Июнь 2008, 19:32:56 
А теперь -- очень кратко о том, почему так делать не нужно. Возьмем первый пример:


Цитата:
class iPod
{
public function play()
{
return "Playing ".$this->album;
}
}



Выделенный фрагмент -- прямое и откровенное нарушение инкапсуляции, явное обращение к потрохам другого класса. Что самое печальное -- неизвестно какого класса. Соответственно, мы вместо отдельных управляемых классов получаем спагетти. Добро пожаловать в "goto hell" нового тысячелетия.
diezel2005 Муж.
новый человек
16
Сообщения: 140
Зарегистрирован: 12.08.06
Откуда: Украина
Сообщение Добавлено: 22 Июнь 2008, 19:51:56 
AlexShop,
Отвечу цитатой, взятой из "не помню откуда":

Цитата:
Ну если вы находите ваш подход приемлемым решением - не буду спорить, все сущее бренно...
Но я бы не хотел никогда встречаться с таким креативом в реальных больших проектах


_________________
Не можешь вынести хамства? Сосчитай до десяти и вынеси хама.
Crazy Муж.
Модератор
107
Сообщения: 14561
Зарегистрирован: 23.12.01
Откуда: Moscow
Сообщение Добавлено: 22 Июнь 2008, 19:58:15 
diezel2005, пять баллов. Добавить ну просто нечего. :)

P.S. Не далее как в пятницу, в процессе изучения "Programming Groovy. Dynamic Productivity for the Java Developer", обсуждали на работе, какие из описанных в книжке приемов нанесут наибольший ущерб коммерческому проекту. :)
diezel2005 Муж.
новый человек
16
Сообщения: 140
Зарегистрирован: 12.08.06
Откуда: Украина
Сообщение Добавлено: 22 Июнь 2008, 22:18:41 

Crazy писал(а):
наибольший ущерб коммерческому проект


Мне кажется наибольший ущерб приносит словосочетание "Dynamic Productivity". Я, наверное, ретроград и скоро покроюсь известняком, потому что путь "от конца" выбрать не смогу никогда. Когда ко мне приходит кодер и рассказывает, какую новую технологию он "щас прочитал" и как он ее "щас применит", мы спокойно садимся за стол, открывает BPM, OOM и, иногда, CDM, и я задаю простой вопрос - "Где в этих всех бумагах ты увидел эту самую, новую технологию?" Кодер покричит на мой консерватизм минут 10, потом успокаивается и идет дальше кодить "без огонька".

Может где-то(на Марсе, например) в разработке приветствуется креатив снизу. Но, ИМХО, реальные проекты так делать нельзя - потому что придет жопа. Рано придет, или поздно, - это неважно. Важно то, что она будет, потому что, если что-то нельзя задокументировать или впихнуть в модель - кто-нибудь из сопровождающих код обязательно споткнется. Ну, или будем переписывать код с каждым новым релизом Языка. :)

А пляски с бубном вокруг множественного наследования - это просто ужас какой-то. И каждый раз одно и то же: Почему не используете? Это же так удобно!

_________________
Не можешь вынести хамства? Сосчитай до десяти и вынеси хама.
Crazy Муж.
Модератор
107
Сообщения: 14561
Зарегистрирован: 23.12.01
Откуда: Moscow
Сообщение Добавлено: 22 Июнь 2008, 22:34:53 
diezel2005, вот мы ради того и обсуждение устраивали: чтобы отделить ту часть, которую стоит рекоменовать к использованию, от той части, которая категорически не рекомендуется в "инициативе снизу". :)

P.S. У нас один добрый кодер "щас применил" Weak References в своем java-коде. Типа свой кэш сбацал, чтобы быстрее работало. Да так ловко сбацал, что в пред-продакшене приложение выжрало за четверть часа 10 гигов памяти и упало в кору...

P.P.S. а "Dynamic Productivity" здесь в другом контексте -- про то, что использование динамических языков может повысить продуктивность программирования. Ну где-то -- да, может...
AlexShop Муж.
участник
34
Сообщения: 1866
Зарегистрирован: 17.02.04
Сообщение Добавлено: 22 Июнь 2008, 22:46:56 
Товарищи.. товарищи.. :)
Безусловно, примесь ведет к более запутанному коду. Но есть же случаи когда это оправдано.



Crazy писал(а):
Выделенный фрагмент -- прямое и откровенное нарушение инкапсуляции, явное обращение к потрохам другого класса.


Я изменил первый пример, и добавил к нему Setter и Getter:
Код:
class iPod
{
   public function play()
   {
      return "Playing ".$this->getAlbum();
   }
}

class iPhone
{
   private $_album;

   public function setAlbum($album) { $this->_album = $album; }
   public function getAlbum() { return $this->_album; }

   public function play()
   {
      return iPod::play();
   }
}

$iPhone = new iPhone();
$iPhone->setAlbum("The Wall");
echo $iPhone->play();
// Результат: Playing The Wall

Если изменить метод:
Код:
public function getAlbum()
и объявить его protected или private, код перестанет работать.
Значит инкапсуляция сохраняется и к "потрохам" нет доступа.



Crazy писал(а):
Что самое печальное -- неизвестно какого класса.

С помощью get_class() функции, можно узнать класс переменной $this. Помоему это обычный полиморфизм. :)

_________________
Тот, кто задает вопрос, глупец в течение пяти минут, тот, кто его не задает, глупец всю свою жизнь. (Китайская поговорка)
Crazy Муж.
Модератор
107
Сообщения: 14561
Зарегистрирован: 23.12.01
Откуда: Moscow
Сообщение Добавлено: 22 Июнь 2008, 23:04:18 

AlexShop писал(а):
Я изменил первый пример, и добавил к нему Setter и Getter:



Вот это как раз случай, о котором говорил diezel2005: когда некий код добавляется не потому, что он нужен для реализации модели, а исключительно для удовлетворения потребностей "новой прекрасной технологии".

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

Но это только половина прикола. Итак, мы вызываем у неизвестного объекта метод getAlbum. Поскольку объект неизвестен -- никакого контракта у нас с ним нет. Соответственно, мы не знает, что он будет делать и какое значение нам вернет. Когда мы читали поле -- мы хотя бы были уверены, что он просто вернет нам значение. А если нам дали класс, в котором getAlbum имеет семантику "Отформатировать диск, записать на него заготовку альбома и вернуть имя созданного каталога"? :)

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


Цитата:
С помощью get_class() функции, можно узнать класс переменной $this. Помоему это обычный полиморфизм. :)



Это не "обычный полиморфизм", а та самая точка, где он заканчивается. Напомню: полиморфизм -- возможность единообразно обращаться с объектами разный классов. Как только вызвал get_class и начали анализировать его результат -- единообразность кончилась. Так что это что угодно, только не полиморфизм.

Это, опять таки, во-первых. А во вторых у нас идет предложение доработать код так, чтобы он действительно использовал get_class, дабы убедиться в правильности вызова. А потом обсудим, насколько этот код будет пригоден к сопровождению.
Crazy Муж.
Модератор
107
Сообщения: 14561
Зарегистрирован: 23.12.01
Откуда: Moscow
Сообщение Добавлено: 22 Июнь 2008, 23:06:58 

AlexShop писал(а):
Безусловно, примесь ведет к более запутанному коду. Но есть же случаи когда это оправдано.



Максималистская точка зрения такова: запутывание кода не оправдано никогда. Но чисто теоретически можно обсудить: в какие случаях применение mixin'ов более оправдано, чем, к примеру, вынесение кода в статические методы отдельного класса или в синглетон?

Или нас интересуют только решения, расширябщие интерфейс класса? Здесь тоже есть альтернативы.
AlexShop Муж.
участник
34
Сообщения: 1866
Зарегистрирован: 17.02.04
Сообщение Добавлено: 23 Июнь 2008, 02:01:17 

Crazy писал(а):
Вот это как раз случай, о котором говорил diezel2005: когда некий код добавляется не потому, что он нужен для реализации модели, а исключительно для удовлетворения потребностей "новой прекрасной технологии".


Замечание неверно. Я добавил геттер и сеттер что бы показать как код работает с ними (я так программирую). Но все прекрасно работает и без них.
Вот оригинал самого первого примера:
Код:
class Phone {}

class iPod
{
   public function play()
   {
      return "Playing ".$this->album;
   }
}

class iPhone extends Phone
{
   public $album;

   function play()
   {
      return iPod::play();
   }
}

$iPhone = new iPhone();
$iPhone->album = "The Wall";
echo $iPhone->play();

// Результат: Playing The Wall
Если изменить переменную:
Код:
public $album;
и объявить ее protected или private -- то код перестанет работать, потому что механизм инкапсуляции работает.


Crazy писал(а):
Отлично, мы добавили геттер и сеттер. Но тем самым мы открыли доступ для всех. Мы изменили публичный интерфейс класса, движимые исключительно нуждами упрощения реализации.


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



Crazy писал(а):
Но это только половина прикола. Итак, мы вызываем у неизвестного объекта метод getAlbum. Поскольку объект неизвестен -- никакого контракта у нас с ним нет. Соответственно, мы не знает, что он будет делать и какое значение нам вернет.


Но ведь так же работает полиморфизм:
- мы знаем только интерфейс объекта, но не его класс
- мы не знаем что объект будет делать и какое значение он вернет


Crazy писал(а):
Напомню: полиморфизм -- возможность единообразно обращаться с объектами разный классов. Как только вызвал get_class и начали анализировать его результат-- единообразность кончилась


Хорошо, но никто не заставляет вызывать get_class() функцию.
Как и в случае с полиморфизмом: можно проверить только интерфейс объекта ($this).

А если страшно что там находится команда "отформатировать диск" -- значит должно быть страшно использовать полиморфизм (в любом месте программы).


Пока у меня все
вдумчиво читаю остальное :)

_________________
Тот, кто задает вопрос, глупец в течение пяти минут, тот, кто его не задает, глупец всю свою жизнь. (Китайская поговорка)
Crazy Муж.
Модератор
107
Сообщения: 14561
Зарегистрирован: 23.12.01
Откуда: Moscow
Сообщение Добавлено: 23 Июнь 2008, 09:40:46 

AlexShop писал(а):
Замечание неверно. Я добавил геттер и сеттер что бы показать как код работает с ними (я так программирую). Но все прекрасно работает и без них.



Если переменная album входит в публичный интерфейс класса -- отлично. Но это только частный случай. В значительной (а то и бОльшей) части случаев mixin'ам потребуются данные, которые не входят в публичный интерфейс.

Или ты предлагаешь использовать mixin'ы исключительно для работы с теми элементами, которые составляют публичный интерфейс класса? Если да, то мы ценой резкого снижения применимости освобождаемся от одной проблемы. Но в полный рост остается втоорая: если мы имеем ТОЛЬКО ИМЯ некоторого элемента класса, о котором мы вообще ничего не знаем, то мы вообще-то не имеем права делать предположений о том, что этот элемент будет делать.


Цитата:
// Результат: Playing The Wall[/code]Если изменить переменную:
Код:
public $album;
и объявить ее protected или private -- то код перестанет работать, потому что механизм инкапсуляции работает.



У тебя постоянно возникает путаница между концепциями ООП и языковыми средствами их реализации. Инкапсуляция -- копцепция. Спецификаторы protected и private -- встроенные в язык средства ее реализации.

Дело вовсе не в том, что код перестает работать при добавлении private -- это просто механическое проговаривание вслух действий парсера. ВНАЧАЛЕ мы решили убрать переменную из публичного интерфейса класса и с этого момента все обращения к ней стали недопустимы (магия, ага). И только ПОТОМ мы добавили спецификатор private, чтобы парсер показал нам, где, по его мнению, остались забытые обращения к этой переменной извне.


Цитата:
Но ведь так же работает полиморфизм:
- мы знаем только интерфейс объекта, но не его класс
- мы не знаем что объект будет делать и какое значение он вернет



Второе утверждение противоречит первому. Интерфейс класса -- это его контракт. Контракт включает не только сигнатуры методов, но и обязательства по их семантике. Если у нас есть интерфейс Drawable с методом draw, для которого заявлена семантика "отобразить объект на экране", то объект Sword, имеющий метод draw с семантикой "вынуть из ножен" НЕ реализует интерфейс класса. Если программист написал "class Sword implements Drawable" -- это программная ошибка. Тот факт, что компилятор не сможет ее отловить, еще не делает код правильным.

Соответственно, если мы знаем заявленный интерфейс класса, то мы знаем семантику операций, входящих в этот интерфейс и мы имеем право их вызвать. Если не знаем интерфейс -- не имеем права вызывать. BTW, не зыбываем, что существует множество способов заявлять интерфейс...


Цитата:
Как и в случае с полиморфизмом: можно проверить только интерфейс объекта ($this).



Не вижу примера кода. :)


Цитата:
А если страшно что там находится команда "отформатировать диск" -- значит должно быть страшно использовать полиморфизм (в любом месте программы).



Вслепую -- страшно. Поэтому его вслепую и не используют. Именно поэтому в промышленных ОО-языках разработки ПО и уделено столько внимания контролю типов.
Crazy Муж.
Модератор
107
Сообщения: 14561
Зарегистрирован: 23.12.01
Откуда: Moscow
Сообщение Добавлено: 23 Июнь 2008, 12:24:21 
На всякий случай уточняю: сами по себе mixin'ы не есть зло. Но важно понимать, как интегрировать их в процесс разработки, чтобы от них было больше пользы, чем вреда. :)
AlexShop Муж.
участник
34
Сообщения: 1866
Зарегистрирован: 17.02.04
Сообщение Добавлено: 24 Июнь 2008, 06:12:10 

Crazy писал(а):
Интерфейс класса -- это его контракт. Контракт включает не только сигнатуры методов, но и обязательства по их семантике. Если у нас есть интерфейс Drawable с методом draw, для которого заявлена семантика "отобразить объект на экране", то объект Sword, имеющий метод draw с семантикой "вынуть из ножен" НЕ реализует интерфейс класса.


Непонимаю:
Проверить сигнатуры можно. А как проверить обязательства по семантике?



Crazy писал(а):
Если программист написал "class Sword implements Drawable" -- это программная ошибка. Тот факт, что компилятор не сможет ее отловить, еще не делает код правильным.


Получается все держится на честном слове программиста.

Почему тогда мы доверяем программисту который написал Drawable и Sword
но не доверяем программисту, который реализует Mixin (упомянутым способом, с применением интерфейсов)?

пример кода следует..

_________________
Тот, кто задает вопрос, глупец в течение пяти минут, тот, кто его не задает, глупец всю свою жизнь. (Китайская поговорка)


Последний раз редактировалось AlexShop 24 Июнь 2008, 07:32:02, всего редактировалось 1 раз.
AlexShop Муж.
участник
34
Сообщения: 1866
Зарегистрирован: 17.02.04
Сообщение Добавлено: 24 Июнь 2008, 07:29:00 
Код:
interface Drawable
{
   public function draw();
}

class Artist
{
   public function draw()
   {
      if (! $this instanceof Drawable) throw new Exception ("Invalid interface. Drawable expected.");
      return "Drawing on the ".$this->canvas." canvas";
   }
}

class Student implements Drawable
{
   public $canvas;

   public function draw()
   {
      return Artist::draw();
   }
}

$student = new Student();
$student->canvas = "linen";
echo $student->draw();

// Результат: Drawing on the linen canvas
Оператор instanceof проверяет: реализует ли переменная $this интерфейс Drawable.* :)


---------------------
*Ради справедливости отмечу что оператор instanceof вернет TRUE:
если $this наследует класс Drawable и реализует другой интерфейс.

Но это особенность PHP языка.
Это нисколько не умаляет мой пример, потому что так пишутся PHP программы и интерфейсы.

---------------------
** Правильнее было бы сказать:
наследует ли переменная $this класс, который реализует интерфейс Drawable. :)

_________________
Тот, кто задает вопрос, глупец в течение пяти минут, тот, кто его не задает, глупец всю свою жизнь. (Китайская поговорка)


Последний раз редактировалось AlexShop 24 Июнь 2008, 07:45:38, всего редактировалось 1 раз.
Crazy Муж.
Модератор
107
Сообщения: 14561
Зарегистрирован: 23.12.01
Откуда: Moscow
Сообщение Добавлено: 24 Июнь 2008, 07:45:22 

AlexShop писал(а):
Непонимаю:
Проверить сигнатуры можно. А как проверить обязательства по семантике?



Проверить кем?



Цитата:
Почему тогда мы доверяем программисту который написал Drawable и Sword
но не доверяем программисту, который реализует Mixin (упомянутым способом, с применением интерфейсов)?



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

Интересный аспект здесь другой -- организационный...
Crazy Муж.
Модератор
107
Сообщения: 14561
Зарегистрирован: 23.12.01
Откуда: Moscow
Сообщение Добавлено: 24 Июнь 2008, 07:47:37 
AlexShop, я не понял, ЧТО ты хотел иллюстрировать своим примером кода. С нюансами, но это работает. Но mixin'ов в этом коде нет.

Так что смысл демонстрации мне неясен.
AlexShop Муж.
участник
34
Сообщения: 1866
Зарегистрирован: 17.02.04
Сообщение Добавлено: 24 Июнь 2008, 08:06:23 
ok, почти пришли к согласию.
Ты говоришь что: интерфейс должен проверяться в классе
А я говорю что: интерфейс проверяется в mixin'e

Кстати очень интересно...


Что у нас там на повестке:

Crazy писал(а):
в какие случаях применение mixin'ов более оправдано, чем, к примеру, вынесение кода в статические методы отдельного класса или в синглетон?

Это надо сравнить.


P.S.
кстати спасибо за понятие "договор" в интерфейсах. Узнал новое. :)

_________________
Тот, кто задает вопрос, глупец в течение пяти минут, тот, кто его не задает, глупец всю свою жизнь. (Китайская поговорка)
AlexShop Муж.
участник
34
Сообщения: 1866
Зарегистрирован: 17.02.04
Сообщение Добавлено: 24 Июнь 2008, 08:10:21 

Crazy писал(а):
Но mixin'ов в этом коде нет.


Не ну настоящих mixin'ов нет, потому что PHP их не имеет.
Это попытка реализовать mixin'ы в PHP.
Причем пример - самый минимальный.

_________________
Тот, кто задает вопрос, глупец в течение пяти минут, тот, кто его не задает, глупец всю свою жизнь. (Китайская поговорка)
Crazy Муж.
Модератор
107
Сообщения: 14561
Зарегистрирован: 23.12.01
Откуда: Moscow
Сообщение Добавлено: 24 Июнь 2008, 13:20:27 

AlexShop писал(а):
Ты говоришь что: интерфейс должен проверяться в классе



Я говорю вовсе не это.


Цитата:
кстати спасибо за понятие "договор" в интерфейсах. Узнал новое. :)



BTW, рекомендую посмотреть рафинированный вариант этой концепции: http://en.wikipedia.org/wiki/Design_by_contract


Последний раз редактировалось Crazy 24 Июнь 2008, 13:24:49, всего редактировалось 1 раз.
Crazy Муж.
Модератор
107
Сообщения: 14561
Зарегистрирован: 23.12.01
Откуда: Moscow
Сообщение Добавлено: 24 Июнь 2008, 13:23:55 

AlexShop писал(а):
Это попытка реализовать mixin'ы в PHP.
Причем пример - самый минимальный.



Чем предложенный код лучше приведенного ниже?

Код:
class Artist
{
   public function draw($canvas)
   {
      return "Drawing on the ".$canvas." canvas";
   }
}

class Student implements Drawable
{
   private $canvas;

   public function draw()
   {
      return Artist::draw($this->canvas);
   }
}

$student = new Student();
$student->canvas = "linen";
echo $student->draw();


Альтернативный вариант (выбор зависит от потребностей):

Код:
class Artist
{
   public function draw($drawable)
   {
      return "Drawing on the ".$drawable->canvas." canvas";
   }
}

class Student implements Drawable
{
   public $canvas;

   public function draw()
   {
      return Artist::draw($this);
   }
}

$student = new Student();
$student->canvas = "linen";
echo $student->draw();


В обоих случаях нет никаких нетривиальных ситуаций.
AlexShop Муж.
участник
34
Сообщения: 1866
Зарегистрирован: 17.02.04
Сообщение Добавлено: 11 Октябрь 2008, 08:38:33 
Все мои потуги в этой теме оказались фиаско!


AlexShop писал(а):
В PHP есть одна хитрая вещь:
Код:
class Phone {}

class iPod
{
   public function play()
   {
      return "Playing ".$this->album;
   }
}

class iPhone extends Phone
{
   public $album;

   function play()
   {
      return iPod::play();
   }
}

$iPhone = new iPhone();
$iPhone->album = "The Wall";
echo $iPhone->play();

// Результат: Playing The Wall
Вы заметили, что iPhone не наследует iPod, но использует его метод play();
В данном контексте, в методе iPod::play() переменная $this указывает на Phone, а не на iPod.
Поэтому все работает!



Ничего не работает.

В E_STRICT режиме PHP выдает предупреждение. В PHP6 такой код выдаст фатальную ошибку.
См. http://www.php.net/~derick/meeting-note … thod-calls

----------
Мой пример (класс iPhone "наследует" функциональность iPod и Phone классов) также считаю неудачным.

Класс "B" должен наследовать от класса "A", когда "B" является более специфичной версией "А".

iPhone не является более специфичной версией класса Phone.
iPhone является новым устройством которое совмещает в себя Phone и iPod.

Поэтому здесь вместо наследования, надо использовать композицию.

_________________
Тот, кто задает вопрос, глупец в течение пяти минут, тот, кто его не задает, глупец всю свою жизнь. (Китайская поговорка)
Crazy Муж.
Модератор
107
Сообщения: 14561
Зарегистрирован: 23.12.01
Откуда: Moscow
Сообщение Добавлено: 11 Октябрь 2008, 09:43:44 

AlexShop писал(а):
Класс "B" должен наследовать от класса "A", когда "B" является более специфичной версией "А".



...что проверяется контрольной фразой: "там, где используется объект класса A, можно подставить объект класса B". (The Liskov Substitution Principle)

[quoute]iPhone не является более специфичной версией класса Phone.
iPhone является новым устройством которое совмещает в себя Phone и iPod.[/quote]

iPhone является новым устройством, которое совмещает в себе функциональность Phone и iPod и может быть использован везде, где используется Phone. Следовательно, его следует наследовать от Phone. И от iPod'а, кстати.
AlexShop Муж.
участник
34
Сообщения: 1866
Зарегистрирован: 17.02.04
Сообщение Добавлено: 12 Октябрь 2008, 09:00:21 

Crazy писал(а):
...что проверяется контрольной фразой: "там, где используется объект класса A, можно подставить объект класса B". (The Liskov Substitution Principle)


Именно о LSP я читал когда писал это.



Crazy писал(а):
iPhone является новым устройством, которое совмещает в себе функциональность Phone и iPod и может быть использован везде, где используется Phone. Следовательно, его следует наследовать от Phone. И от iPod'а, кстати.


Помоему iPhone должен иметь интерфейсы Phone и iPod.
А всю функциональность заимствовать путем композиции, а не наследования.

Недостаток наследования заключается в том что iPhone может наследовать ненужные методы от Phone.
Например: подключение к телефонной розетке, импульсный набор (как при прокручивании диска).

Создание промежуточного класса CellPhone не решает проблем (рис. a); так как теперь CellPhone наследует ненужные методы. Следующая схема (рис. b) представляется наиболее правильной.

Изображение

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

Изменения классов (в отличии от эволюции) происходит часто. Поэтому схема b. быстро может стать неправильной.
Для этого достаточно добавить публичные методы для класса CellPhone, которые ненужны для iPhone.

ИМХО это одна из причин почему предпочтение отдается композиции, а не наследованию.

_________________
Тот, кто задает вопрос, глупец в течение пяти минут, тот, кто его не задает, глупец всю свою жизнь. (Китайская поговорка)
Crazy Муж.
Модератор
107
Сообщения: 14561
Зарегистрирован: 23.12.01
Откуда: Moscow
Сообщение Добавлено: 12 Октябрь 2008, 10:07:50 
AlexShop, у тебя большие проблемы по части проектирования иерархий. Айфон не наследует ненужных методов от Телефона, поскольку Телефон не имеет ненужных методов. У него нет свойства ГнездоДляПровода и метода ПокрутитьДиск. Свойство -- у класса "ПроводнойТелефон" (к примеру), а метод -- у ДисковогоТелефона (опять же -- к примеру).

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

А причин, по которым отджается предпочтение композиции, всего две:

1. Во многих случаях не сработало правило подстановки. Нет причин использовать наследование.
2. Многие языки не имеют множественного наследования. Соответственно, нет возможности полноценно использовать наследование.
AlexShop Муж.
участник
34
Сообщения: 1866
Зарегистрирован: 17.02.04
Заголовок сообщения: Re: Mixins (примеси) в PHP
Сообщение Добавлено: 25 Январь 2009, 05:32:08 
Crazy,
извиняюсь за поздний ответ.

Есть еще одна важная причина, по которой предпочтение отдается композиции.
Композиция всегда происходит во время выполнения программы (Run-time). А наследование не всегда. :)

_________________
Тот, кто задает вопрос, глупец в течение пяти минут, тот, кто его не задает, глупец всю свою жизнь. (Китайская поговорка)
Crazy Муж.
Модератор
107
Сообщения: 14561
Зарегистрирован: 23.12.01
Откуда: Moscow
Сообщение Добавлено: 25 Январь 2009, 10:17:41 

AlexShop писал(а):
Композиция всегда происходит во время выполнения программы (Run-time). А наследование не всегда. :)



Композиция всегда происходит на этапе проектирование. Равно как и наследование. Hint: оба эти термина -- из языка проектировщика.

BTW, понятие "композиция" вообще отсутствует в языках программирования -- что в Java, что в PHP (в качестве возражения приму только специфическую синтаксическую конструкцию, используемые в языке исключительно для обозначения композиции). :) Понятие "наследование" есть и в PHP, и в Java, но означает неидентичные вещи, причем в обоих случаях отличные от того, что это означает в проектировании.
AlexShop Муж.
участник
34
Сообщения: 1866
Зарегистрирован: 17.02.04
Заголовок сообщения: Re: Mixins (примеси) в PHP
Сообщение Добавлено: 26 Январь 2009, 06:23:15 
Crazy,
все верно, понятие композиции отсутствует в языках программирования, точно также как и понятие "Декоратор" и др.
Но синтаксис языка не является последней инстанцией. Если нужные понятия не предусмотрены синтаксисом языка, то их создают кустарными методами. Слышал мнение что совершенный язык не нуждается в паттернах.


Crazy писал(а):
Понятие "наследование" есть и в PHP, и в Java, но означает неидентичные вещи, причем в обоих случаях отличные от того, что это означает в проектировании.

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

Вернусь к теме: композиция против наследования.

Имеем два класса: ПроводнойТелефон и ДисковыйТелефон. Оба наследуют от родительского класса Телефон.
Есть три возможности реализовать наследование:
Изображение

Все три схемы, соответствуют The Liskov Substitution Principle. К примеру, там где используем ПроводнойТелефон можно использовать Телефон. Но несмотря на это, все эти схемы я считаю неудачными.

Схема а неподходит, т.к. нам может понадобиться телефон который имеет провод и диск (мн. наследование не рассматриваю).
Схема б неподходит, т.к. нам может понадобиться проводной телефон который использует кнопки (вместо диска).
Схема в неподходит, т.к. нам может понадобиться беспроводной телефон с диском (вместо кнопок).

Также в схеме б отсутствует логика: почему ПроводнойТелефон наследует от ДисковогоТелефона, а не наоборот?
В схеме в отсутствует схожая логика.

Поэтому я думаю что даже если правило подстановки Лисков срабатывает - это еще не повод использовать наследование.
Как предпочтительней сделать? Конечно композицией:
Изображение
:)

_________________
Тот, кто задает вопрос, глупец в течение пяти минут, тот, кто его не задает, глупец всю свою жизнь. (Китайская поговорка)
Enemigo Муж.
участник
59
Сообщения: 1094
Зарегистрирован: 16.04.04
Откуда: Кишинев
Сообщение Добавлено: 26 Январь 2009, 10:00:01 

AlexShop писал(а):
почему ПроводнойТелефон наследует от ДисковогоТелефона, а не наоборот?В схеме в отсутствует схожая логика.


Где вы видели радиотелефоны с дисковым набором? :gent:
Сначала шнур, потом диск либо кнопки. Как-то так...

_________________
Хомячки не умеют плавать.
Научить их — наша задача!
Брось с размаху их в синее море.
Пусть сопутствует им удача!
AlexShop Муж.
участник
34
Сообщения: 1866
Зарегистрирован: 17.02.04
Заголовок сообщения: Re: Mixins (примеси) в PHP
Сообщение Добавлено: 26 Январь 2009, 10:33:26 
Enemigo,
вместо телефона может быть любой другой предмет. Это ни сколько не умаляет мой пример, потому что программисты привыкли думать абстрактно.
Google: rotary cellphone :)

_________________
Тот, кто задает вопрос, глупец в течение пяти минут, тот, кто его не задает, глупец всю свою жизнь. (Китайская поговорка)
diezel2005 Муж.
новый человек
16
Сообщения: 140
Зарегистрирован: 12.08.06
Откуда: Украина
Сообщение Добавлено: 26 Январь 2009, 15:05:06 

AlexShop писал(а):
потому что программисты привыкли думать абстрактно.


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

По поводу самой задачи скажу так: убирая семантические особенности языковых конструкций и понятий, мы получим просто задачу, которую нужно решить. И если не ставить для себя цели смоделировать реальный мир ваще, то для решения одной задачи может подойти модель а, другой - б. Мне лень рисовать картинки, но я даже могу себе представить задачу, которую описывает модель: Провод->Телефон->Диск.
В данном конкретном случае задача: "Имеем два класса: ПроводнойТелефон и ДисковыйТелефон. Оба наследуют от родительского класса Телефон." Что неясно? Все модель а - рабочая. Думать о том, что нам может понадобиться должен постановщик задач, а не реализатор.
Имхо, везде важна не возможная программная реализация, а контекст задачи. Либо пример неудачен, либо я тупой, но выше приведена элементрная подмена понятий - свойства подменяются целыми классами. И провод, и диск - свойства. Ведь, когда я беру в руки телефон, мне без разницы есть провод или нет, есть диск или нет, мне нужен результат паблик протектед метода "позвонить", "перерезать провод" или "запустить в голову". Когда мне нужен будет "ПроводнойДисковыйТелефон" или "ГибридныйВодостойкийТелефон" - я его, опять же, унаследую от Телефона. Да или нет?

Давно пора теорию программирования отнести к фундаментальным наукам и не обращать на нее внимания.

_________________
Не можешь вынести хамства? Сосчитай до десяти и вынеси хама.
Crazy Муж.
Модератор
107
Сообщения: 14561
Зарегистрирован: 23.12.01
Откуда: Moscow
Сообщение Добавлено: 26 Январь 2009, 22:15:52 
AlexShop, приведенная тобой модель показывает, что если нечто криво спроектировать, то это потом никакой реализацией не исправить.

Если сам не найдешь за два дня плюху в своей модели -- напомни мне, я расскажу.
AlexShop Муж.
участник
34
Сообщения: 1866
Зарегистрирован: 17.02.04
Заголовок сообщения: Re: Mixins (примеси) в PHP
Сообщение Добавлено: 27 Январь 2009, 06:50:48 
diezel2005 приветствую!
diezel2005 писал(а):
выше приведена элементрная подмена понятий - свойства подменяются целыми классами. И провод, и диск - свойства.

Все верно. В последнем примере: провод и диск - это свойства, а в этих свойствах хранятся объекты.


Crazy,
плюха есть!

Проводу в телефоне не место (он не имеет никакой функциональности). И диск не нужен.
Нужен один интерфейс для ввода и вывода информации.
Потом к телефону подключаем различные устройства которые соответствуют этому интерфейсу.
Для ввода это: кнопки, диск, распознаватель речи и т.п.
Для вывода это: трубка, наушники, динамик и т.п.

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

На UML схему мне надо несколько дней (кстати интересная задача). :beer:

_________________
Тот, кто задает вопрос, глупец в течение пяти минут, тот, кто его не задает, глупец всю свою жизнь. (Китайская поговорка)
Crazy Муж.
Модератор
107
Сообщения: 14561
Зарегистрирован: 23.12.01
Откуда: Moscow
Сообщение Добавлено: 27 Январь 2009, 18:12:43 
AlexShop, ты движешься в правильном направлении. Но это еще не все.
AlexShop Муж.
участник
34
Сообщения: 1866
Зарегистрирован: 17.02.04
Заголовок сообщения: Re: Mixins (примеси) в PHP
Сообщение Добавлено: 28 Январь 2009, 04:58:16 

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

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

Интерфейс 1:
устройства ввода (когда ты хочешь звонить): кнопки, диск, распознаватель речи и т.п.
устройства вывода (когда тебе звонят): звонок

Интерфейс 2:
устройства ввода (когда ты хочешь сказать): микрофон
устройства вывода (когда ты хочешь услышать): трубка, наушники, динамик и т.п.

_________________
Тот, кто задает вопрос, глупец в течение пяти минут, тот, кто его не задает, глупец всю свою жизнь. (Китайская поговорка)
*   Список форумов / Начинка и техника / Программирование для WWW « | » » ответить » создать топик
На страницу 1 2  >  Страница 1 из 2 [ Сообщений: 36 ] 
Показать сообщения за:   Поле сортировки  
Найти:
Перейти:  
Уровень доступа: Вы не можете начинать темы. Вы не можете отвечать на сообщения. Вы не можете редактировать свои сообщения. Вы не можете удалять свои сообщения. Вы не можете добавлять вложения.
cron


ООО ДеФорум
При использовании материалов сайта ссылка на DeForum.ru — обязательна.
Проект Павла Батурина ©2001-2077; // Powered by phpBB © 2013 phpBB Group
Rambler's Top100