на правах извращения PHP: <?php Object::$prototype= new Object( array() ); class Object { static $prototype; var $delegates= array(); var $context= null; var $class= ''; function Object( $delegates= null ){ $this->context= $this; $this->class= get_class( $this ); if( func_num_args() ): if( is_array( $delegates ) ): $this->delegates= $delegates; else: $this->delegates= func_get_args(); endif; else: $this->delegates[]= eval( 'return ' . $this->class . '::$prototype;' ); endif; $this->constructor(); } function constructor( ){ } function take( $arr ){ foreach( $arr as $key => $val ) $this->$key= $val; } function haveDelegate( $object ){ return $this->_findByObj( $object ); } function _findByMethod( $method ){ if( method_exists( $this, $method ) ) return $this; foreach( $this->delegates as $ex ): if( $obj= $ex->_findByMethod( $method ) ) return $obj; endforeach; return null; } function _findByProp( $prop ){ if( isset( $this->$prop ) ) return $this; foreach( $this->delegates as $ex ) if( $obj= $ex->_findByProp( $prop ) ) return $obj; return null; } function _findByObj( $object ){ if( $this === $object ) return $this; foreach( $this->delegates as $ex ) if( $obj= $ex->_findByObj( $object ) ) return $obj; return null; } function apply( $obj, $method, $args ){ $context= $this->context; $this->context= $obj->context; $ret= call_user_func_array( array( $this, $method ), $args ); $this->context= $context; return $ret; } function __call( $method, $args ){ $obj= $this->_findByMethod( $method ); if( $obj ) $obj->apply( $this, $method, $args ); endif; $trace= debug_backtrace(); $file= $trace[1]['file']; $line= $trace[1]['line']; $class= $trace[1]['class']; trigger_error( "[ unknown method {$class}->{$method}() in {$file} on {$line} line ]" ); } function __get( $prop ){ $obj= $this->_findByProp( $prop); if( $obj ) return $obj->$prop; $trace= debug_backtrace(); $file= $trace[0]['file']; $line= $trace[0]['line']; $class= $trace[0]['class']; trigger_error( "[ unknown property {$class}->{$prop} in {$file} on {$line} line ]" ); } } Rect::$prototype= new Rect( Object::$prototype ); class Rect extends Object { static $prototype; var $width= 0; var $height= 0; function area( ){ return $this->context->width * $this->context->height; } } Rhomb::$prototype= new Rhomb( Object::$prototype ); class Rhomb extends Object { static $prototype; var $width= 0; var $height= 0; function area( ){ return $this->context->width * $this->context->height / 2; } } Figure::$prototype= new Figure( Object::$prototype ); class Figure extends Object { static $prototype; } $fig1= new Figure; $fig1->take( array( 'width' => 10, 'height' => 10) ); $fig2= new Figure; $fig2->take( array( 'width' => 5, 'height' => 5) ); print_r( $fig1->area() ); echo '<br/>'; print_r( $fig2->area() ); echo '<br/>'; echo '<br/>'; Figure::$prototype->delegates= array( Rect::$prototype ); print_r( $fig1->area() ); echo '<br/>'; print_r( $fig2->area() ); echo '<br/>'; echo '<br/>'; $fig1->delegates= array( Rhomb::$prototype ); print_r( $fig1->area() ); echo '<br/>'; print_r( $fig2->area() ); echo '<br/>'; echo '<br/>'; print_r( $fig1->haveDelegate( Rect::$prototype ) ? 1 : 0 ); echo '<br/>'; print_r( $fig2->haveDelegate( Rect::$prototype ) ? 1 : 0 ); echo '<br/>'; echo '<br/>'; $fig1->delegates= array( $fig2 ); print_r( $fig1->haveDelegate( Rect::$prototype ) ? 1 : 0 ); echo '<br/>'; print_r( $fig2->haveDelegate( Rect::$prototype ) ? 1 : 0 ); echo '<br/>'; echo '<br/>'; print_r( $fig1->haveDelegate( $fig2 ) ? 1 : 0 ); echo '<br/>'; print_r( $fig2->haveDelegate( $fig2 ) ? 1 : 0 ); echo '<br/>'; echo '<br/>'; print_r( $fig1->haveDelegate( $fig1 ) ? 1 : 0 ); echo '<br/>'; print_r( $fig2->haveDelegate( $fig1 ) ? 1 : 0 ); echo '<br/>'; echo '<br/>'; ?> возвращает: Код (Text): Notice: [ unknown method Figure->area() in T:\home\localhost\testo\proto\index.php on 107 line ] in T:\home\localhost\testo\proto\index.php on line 63 Notice: [ unknown method Figure->area() in T:\home\localhost\testo\proto\index.php on 108 line ] in T:\home\localhost\testo\proto\index.php on line 63 100 25 50 25 0 1 1 1 1 1 1 0
Комменты в коде были бы уместны, потому что понять что есть что и зачем тяжеловато.. Идею не понял... (она даже не описана)
при вызове несуществующего метода или попытке доступа к несуществующему полю производится поиск объекта, среди перечисленных в $delegates, который это поле/метод имеет. поиск каскадный, что позволяет строить сложную иерархию. делегат - это аналог родителя в классово ориентированном программировании если искалось поле - возвращается его значение в найденном объекте если искался метод - в найденном объекте подменяется контекст выполнения, выполняется метод, после чего контекст восстанавливается обратно. например, при вызове $fig1->area(), $this->context при выполнении этого метода указывает на объект $fig1, а не на прототип Rect или Rhomb, как $this. $this->context - это аналог this в яваскрипте. если ни в одном из "предков" это поле/метод не нашлось, то генерируется нотис и возвращается null. прототип - это объект-синглетон, созданный для класса, что позволяет ему от имени класса учавствовать в общей иерархии объектов. все изменения полей для прототипа автоматически влияют и на все объекты того же класса, если соответствующие поля небыли переопределены и прототип небыл исключён из числа делегатов в общем, получилось что-то типа яваскрипта, но круче - можно задавать несколько "предков" для объекта.
поправил объектную модель, чуть уменьшил писанины, добавил haveDelegate - аналог instanceof в яваскрипте, нарисовал более наглядный пример.
двунаправленный стэк PHP: <?php Deque::$prototype= new Deque( Object::$prototype ); Deque::$prototype->data= array(); class Deque extends Object { static $prototype; function push( ){ $this->context->adds(); foreach( func_get_args() as $val ) array_push( $this->context->data, $val ); return $this->context; } function unshift( ){ $this->context->adds(); foreach( func_get_args() as $val ) array_unshift( $this->context->data, $val ); return $this->context; } function pop( ){ $this->context->adds(); return array_pop( $this->context->data ); } function shift( ){ $this->context->adds(); return array_shift( $this->context->data ); } function adds( $arr= array() ){ if( !isset( $this->context->data ) ) $this->context->data= $this->context->data; foreach( $arr as $key => $val ) $this->context->push( $val ); return $this->context; } function contains( $value ){ foreach( $this->context->data as $val ): if( $value === $val ) return $val; endforeach; return null; } function drop( $value ){ $this->context->adds(); foreach( $this->context->data as $key => $val ): if( $value === $val ) unset( $this->context->data[ $key ] ); endforeach; return null; } function join( $del= '' ){ $first= true; $str= ''; $data= $this->context->data; foreach( $data as $val ): if( !$first ) $str.= $del; $str.= $val; $first= false; endforeach; return $str; } function __toString( ){ return $this->context->class . '[ ' . $this->context->join( ', ' ) . ' ]'; } } $arr1= new Deque; $arr2= new Deque; $arr1->adds( array( 'abc', 'def', 'ghi' ) )->drop( 'def' ); $arr2->push( $arr1, 123 ); $arr1->push( $arr1->contains( 'def' ) ? 1 : 0 ); $arr1->unshift( $arr1->contains( 'abc' ) ? '8-)' : '8-(' ); echo $arr2->join( ' ' ) . '<br/>'; echo '<br/>'; $arr= new Deque; echo $arr . '<br/>'; Deque::$prototype->data= array( 'default data' ); echo $arr . '<br/>'; echo '<br/>'; $arr->adds( ); Deque::$prototype->data= array( 'another default data' ); echo $arr . '<br/>'; echo ( new Deque ) . '<br/>'; echo '<br/>'; ?> выводит: Код (Text): Deque[ 8-), abc, ghi, 0 ] 123 Deque[ ] Deque[ default data ] Deque[ default data ] Deque[ another default data ]
zerkms, нет, "цепочка обязанностей" не предполагает выполнение метода одного объекта в контексте другого - там просто происходит всплытие "сообщения" по цепочке объектов пока какой-нибудь из них его не перехватит.
вот бы ещё яваскрипт можно было бы в пхп превратить... в частности, очень не хватает магических функций __call, __get и __set
ага, мы такого паттерна не знаем, поэтому назовём его "Chain of responsability в контексте извращенного PHP-шного делегирования"
Psih, как сказать, я сейчас класс для работы с бд перевожу на такую схему. объекту перадаётся dsn в соответствии с которым он "вгружает в себя" драйвер для работы с соответствующей субд. получается как буд-то этот класс изначально был предназначен для работы именно с этой субд.
Аццкий дзен. Погружение драйвера в себя... dark-demon конечно изрядная сволочь, но за умение мыслить нестандартно я его обожаю! /Щас кончится коньяк — и кончатся признания ГО ) /
Я тут переработал немного базовый класс. PHP: <?php class O_Error extends Exception {} class O_Object { static $prototype; private $_context= null; private $_donors= array(); var $a_b= 1; function __construct( $donors= null ){ $this->_context= $this; $this->_donors= is_array( $donors ) ? $donors : func_get_args(); } static function birth( ){ return new self( array( 'parent' => self::$prototype ) ); } function _birth( ){ return new $this( array( 'parent' => $this ) ); } function _apply( $obj, $method, $args ){ $self= $this->_context; $this->_context= $obj->_context; $ret= call_user_func_array( array( $this, $method ), $args ); $this->_context= $self; return $ret; } function _hasDonor( $donor ){ if( $this === $donor ) return $this; foreach( $this->_donors as $ns => $don ) if( $d= $don->_hasDonor( $donor ) ) return $d; } function __get( $name ){ $localName= strstr( $name, '_' ); $nameSpace= substr( $name, 0, strlen($name) - strlen( $localName ) ); $localName= substr( $localName, 1 ); $obj= $this; if( $localName ): if( $nameSpace ) $obj= $obj->_donors[ $nameSpace ]; return $obj->{$localName}; endif; $localName= $nameSpace; foreach( $obj->_donors as $nameSpace => $donor ): $ret= $donor->{$localName}; if( $ret ) return $ret; endforeach; return null; } function __set( $name, $value ){ $localName= strstr( $name, '_' ); $nameSpace= substr( $name, 0, strlen($name) - strlen( $localName ) ); $localName= substr( $localName, 1 ); $obj= $this; if( $localName ): if( $nameSpace ) $obj= $obj->_donors[ $nameSpace ]; return $obj->{$localName}= $value; endif; return $obj->{$nameSpace}= $value; } function __call( $name, $args ){ $localName= strstr( $name, '_' ); $nameSpace= substr( $name, 0, strlen($name) - strlen( $localName ) ); $localName= substr( $localName, 1 ); $self= $this->_context; if( $localName ): $obj= $this; if( $nameSpace ): $obj= $this->_donors[ $nameSpace ]; if( !$obj ) return null; endif; return $obj->_apply( $this->_context, $localName, $args ); endif; $localName= $nameSpace; foreach( $this->_donors as $nameSpace => $donor ): try { return $donor->_apply( $this->_context, $localName, $args ); } catch( O_Error $e ){ } endforeach; throw new O_Error( 'Method "' . $localName . '" not found' ); } function __toString( ){ $donors= ''; foreach( $this->_donors as $nameSpace => $donor ): if( $donors ) $donors.= ', '; $donors.= $donor->__toString(); endforeach; return get_class( $this ) . "( {$donors} )"; } } O_Object::$prototype= new O_Object( ); ?> из основных отличий: теперь для доступа к полям и методам можно использовать пространства имён. в качестве символа разделителя используется знак подчёркивания. пусть, например, у нас есть класс для работы с конкретной субд (драйвер) и есть абстрактный класс который реализует логику не зависимую от субд. у обоих есть метод query. метод query драйвера принимает "сырой" запрос. метод же абстрактного класса экранирует по определённой схеме, формирует "сырой запрос" и передаёт его методу query драйвера. PHP: <?php function query( $query ){ # query( 'insert into table values (', (string) $_GET[ 'a' ], ')' ); if( !$query ) $this->wrongCall( "missing query string" ); $self= $this->_context; $sql= $self->prepare( func_get_args( ) ); return $self->driver_query( $sql ); } ?> при этом драйвер подгружается в абстрактный класс следующим образом: $self->_donors[ 'driver' ]= $mysqlDriver; теперь можно написать просто $db->query и будет вызван абстрактный query. можно написать $db->driver_query и будет вызван метод драйвера query. аналогично можно использовать и свойства. $db->abc= '123'; // присвоит значение свойству abc объекта $db $db->driver_abc= '123'; // присвоит значение свойству abc объекта-драйвера такая схема позволяет, например, к драйверам подгрузить поддрайверы (например, к драйверу PDO подгрузить драйвер для работы с конкретной субд). при этом из любого места (даже этого "поддрайвера") можно вызвать $self->query и будет пройдена вся цепочка query методов.
короче, использование такого наследования позволяет создавать абстрактные реализации алгоритмов и "подцеплять" их к любому объекту, который поддерживает необходимый им интерфейс. ещё пример: пусть объект поддерживает интерфейс getValueAsNumber и пусть у нас есть абстрактный класс Math использующий этот метод для релизации различных математических функций. тогда мы можем подцепить к нашему объекту реализации этих алгоритмов и спокойно использовать: $obj->_donors[]= Math::$prototype; echo 'factorial of ' . $obj->getValueAsNumber() . '= ' . $obj->factorial(); при этом метод factorial для получения начального значения просто вызывает getValueAsNumber. PHP: <?php function factoial( ){ $val= 1; for( $i= $this->_context->getValueAsNumber(); $i; --$i ) $val*= $i; return $val; } ?>
кстати, для этого велосипеда и термин уже есть. "штрих" - это абстрактный объект реализующий одни интерфейсы на базе других, который может быть "подмешан" к любому объекту поддерживающему необходимый интерфейс.