За последние 24 часа нас посетили 21889 программистов и 1383 робота. Сейчас ищут 749 программистов ...

динамическое множественное наследование

Тема в разделе "Прочие вопросы по PHP", создана пользователем dark-demon, 26 сен 2007.

  1. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    на правах извращения :)

    PHP:
    1. <?php
    2.  
    3. Object::$prototype= new Object( array() );
    4. class Object {
    5.     static $prototype;
    6.     var $delegates= array();
    7.     var $context= null;
    8.     var $class= '';
    9.     function Object( $delegates= null ){
    10.         $this->context= $this;
    11.         $this->class= get_class( $this );
    12.         if( func_num_args() ):
    13.             if( is_array( $delegates ) ):
    14.                 $this->delegates= $delegates;
    15.             else:
    16.                 $this->delegates= func_get_args();
    17.             endif;
    18.         else:
    19.             $this->delegates[]= eval( 'return ' . $this->class . '::$prototype;' );
    20.         endif;
    21.         $this->constructor();
    22.     }
    23.     function constructor( ){
    24.     }
    25.     function take( $arr ){
    26.         foreach( $arr as $key => $val ) $this->$key= $val;
    27.     }
    28.     function haveDelegate( $object ){
    29.         return $this->_findByObj( $object );
    30.     }
    31.     function _findByMethod( $method ){
    32.         if( method_exists( $this, $method ) ) return $this;
    33.         foreach( $this->delegates as $ex ):
    34.             if( $obj= $ex->_findByMethod( $method ) ) return $obj;
    35.         endforeach;
    36.         return null;
    37.     }
    38.     function _findByProp( $prop ){
    39.         if( isset( $this->$prop ) ) return $this;
    40.         foreach( $this->delegates as $ex )
    41.             if( $obj= $ex->_findByProp( $prop ) ) return $obj;
    42.         return null;
    43.     }
    44.     function _findByObj( $object ){
    45.         if( $this === $object ) return $this;
    46.         foreach( $this->delegates as $ex )
    47.             if( $obj= $ex->_findByObj( $object ) ) return $obj;
    48.         return null;
    49.     }
    50.     function apply( $obj, $method, $args ){
    51.         $context= $this->context;
    52.         $this->context= $obj->context;
    53.         $ret= call_user_func_array( array( $this, $method ), $args );
    54.         $this->context= $context;
    55.         return $ret;
    56.     }
    57.     function __call( $method, $args ){
    58.         $obj= $this->_findByMethod( $method );
    59.         if( $obj ) $obj->apply( $this, $method, $args );
    60.         endif;
    61.         $trace= debug_backtrace();
    62.         $file= $trace[1]['file'];
    63.         $line= $trace[1]['line'];
    64.         $class= $trace[1]['class'];
    65.         trigger_error( "[ unknown method {$class}->{$method}() in {$file} on {$line} line ]" );
    66.     }
    67.     function __get( $prop ){
    68.         $obj= $this->_findByProp( $prop);
    69.         if( $obj ) return $obj->$prop;
    70.         $trace= debug_backtrace();
    71.         $file= $trace[0]['file'];
    72.         $line= $trace[0]['line'];
    73.         $class= $trace[0]['class'];
    74.         trigger_error( "[ unknown property {$class}->{$prop} in {$file} on {$line} line ]" );
    75.     }
    76. }
    77.  
    78.  
    79. Rect::$prototype= new Rect( Object::$prototype );
    80. class Rect extends Object {
    81.     static $prototype;
    82.     var $width= 0;
    83.     var $height= 0;
    84.     function area( ){
    85.         return $this->context->width * $this->context->height;
    86.     }
    87. }
    88.  
    89. Rhomb::$prototype= new Rhomb( Object::$prototype );
    90. class Rhomb extends Object {
    91.     static $prototype;
    92.     var $width= 0;
    93.     var $height= 0;
    94.     function area( ){
    95.         return $this->context->width * $this->context->height / 2;
    96.     }
    97. }
    98.  
    99.  
    100. Figure::$prototype= new Figure( Object::$prototype );
    101. class Figure extends Object {
    102.     static $prototype;
    103. }
    104.  
    105. $fig1= new Figure;
    106. $fig1->take( array( 'width' => 10, 'height' => 10) );
    107. $fig2= new Figure;
    108. $fig2->take( array( 'width' => 5, 'height' => 5) );
    109.  
    110. print_r( $fig1->area() ); echo '<br/>';
    111. print_r( $fig2->area() ); echo '<br/>';
    112. echo '<br/>';
    113.  
    114. Figure::$prototype->delegates= array( Rect::$prototype );
    115. print_r( $fig1->area() ); echo '<br/>';
    116. print_r( $fig2->area() ); echo '<br/>';
    117. echo '<br/>';
    118.  
    119. $fig1->delegates= array( Rhomb::$prototype );
    120. print_r( $fig1->area() ); echo '<br/>';
    121. print_r( $fig2->area() ); echo '<br/>';
    122. echo '<br/>';
    123.  
    124. print_r( $fig1->haveDelegate( Rect::$prototype ) ? 1 : 0 ); echo '<br/>';
    125. print_r( $fig2->haveDelegate( Rect::$prototype ) ? 1 : 0 ); echo '<br/>';
    126. echo '<br/>';
    127.  
    128. $fig1->delegates= array( $fig2 );
    129. print_r( $fig1->haveDelegate( Rect::$prototype ) ? 1 : 0 ); echo '<br/>';
    130. print_r( $fig2->haveDelegate( Rect::$prototype ) ? 1 : 0 ); echo '<br/>';
    131. echo '<br/>';
    132. print_r( $fig1->haveDelegate( $fig2 ) ? 1 : 0 ); echo '<br/>';
    133. print_r( $fig2->haveDelegate( $fig2 ) ? 1 : 0 ); echo '<br/>';
    134. echo '<br/>';
    135. print_r( $fig1->haveDelegate( $fig1 ) ? 1 : 0 ); echo '<br/>';
    136. print_r( $fig2->haveDelegate( $fig1 ) ? 1 : 0 ); echo '<br/>';
    137. echo '<br/>';
    138.  
    139. ?>
    возвращает:
    Код (Text):
    1. 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
    2. 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
    3.  
    4. 100
    5. 25
    6.  
    7. 50
    8. 25
    9.  
    10. 0
    11. 1
    12.  
    13. 1
    14. 1
    15.  
    16. 1
    17. 1
    18.  
    19. 1
    20. 0
     
  2. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Комменты в коде были бы уместны, потому что понять что есть что и зачем тяжеловато..
    Идею не понял... (она даже не описана)
     
  3. Anonymous

    Anonymous Guest

    Даешь комменты!
    Примерно понял... но комменты нужны. Иначе не могу выдать свои...
     
  4. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    при вызове несуществующего метода или попытке доступа к несуществующему полю производится поиск объекта, среди перечисленных в $delegates, который это поле/метод имеет. поиск каскадный, что позволяет строить сложную иерархию.
    делегат - это аналог родителя в классово ориентированном программировании
    если искалось поле - возвращается его значение в найденном объекте
    если искался метод - в найденном объекте подменяется контекст выполнения, выполняется метод, после чего контекст восстанавливается обратно. например, при вызове $fig1->area(), $this->context при выполнении этого метода указывает на объект $fig1, а не на прототип Rect или Rhomb, как $this. $this->context - это аналог this в яваскрипте.
    если ни в одном из "предков" это поле/метод не нашлось, то генерируется нотис и возвращается null.
    прототип - это объект-синглетон, созданный для класса, что позволяет ему от имени класса учавствовать в общей иерархии объектов. все изменения полей для прототипа автоматически влияют и на все объекты того же класса, если соответствующие поля небыли переопределены и прототип небыл исключён из числа делегатов

    в общем, получилось что-то типа яваскрипта, но круче - можно задавать несколько "предков" для объекта.
     
  5. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    поправил объектную модель, чуть уменьшил писанины, добавил haveDelegate - аналог instanceof в яваскрипте, нарисовал более наглядный пример.
     
  6. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    двунаправленный стэк
    PHP:
    1. <?php
    2.  
    3. Deque::$prototype= new Deque( Object::$prototype );
    4. Deque::$prototype->data= array();
    5. class Deque extends Object {
    6.     static $prototype;
    7.     function push( ){
    8.         $this->context->adds();
    9.         foreach( func_get_args() as $val )
    10.             array_push( $this->context->data, $val );
    11.         return $this->context;
    12.     }
    13.     function unshift( ){
    14.         $this->context->adds();
    15.         foreach( func_get_args() as $val )
    16.             array_unshift( $this->context->data, $val );
    17.         return $this->context;
    18.     }
    19.     function pop( ){
    20.         $this->context->adds();
    21.         return array_pop( $this->context->data );
    22.     }
    23.     function shift( ){
    24.         $this->context->adds();
    25.         return array_shift( $this->context->data );
    26.     }
    27.     function adds( $arr= array() ){
    28.         if( !isset( $this->context->data ) )
    29.             $this->context->data= $this->context->data;
    30.         foreach( $arr as $key => $val )
    31.             $this->context->push( $val );
    32.         return $this->context;
    33.     }
    34.     function contains( $value ){
    35.         foreach( $this->context->data as $val ):
    36.             if( $value === $val ) return $val;
    37.         endforeach;
    38.         return null;
    39.     }
    40.     function drop( $value ){
    41.         $this->context->adds();
    42.         foreach( $this->context->data as $key => $val ):
    43.             if( $value === $val ) unset( $this->context->data[ $key ] );
    44.         endforeach;
    45.         return null;
    46.     }
    47.     function join( $del= '' ){
    48.         $first= true;
    49.         $str= '';
    50.         $data= $this->context->data;
    51.         foreach( $data as $val ):
    52.             if( !$first ) $str.= $del;
    53.             $str.= $val;
    54.             $first= false;
    55.         endforeach;
    56.         return $str;
    57.     }
    58.     function __toString( ){
    59.         return $this->context->class . '[ ' . $this->context->join( ', ' ) . ' ]';
    60.     }
    61. }
    62.  
    63.  
    64. $arr1= new Deque;
    65. $arr2= new Deque;
    66. $arr1->adds( array( 'abc', 'def', 'ghi' ) )->drop( 'def' );
    67. $arr2->push( $arr1, 123 );
    68. $arr1->push( $arr1->contains( 'def' ) ? 1 : 0 );
    69. $arr1->unshift( $arr1->contains( 'abc' ) ? '8-)' : '8-(' );
    70. echo $arr2->join( ' ' ) . '<br/>';
    71. echo '<br/>';
    72.  
    73. $arr= new Deque;
    74. echo $arr . '<br/>';
    75. Deque::$prototype->data= array( 'default data' );
    76. echo $arr . '<br/>';
    77. echo '<br/>';
    78.  
    79. $arr->adds( );
    80. Deque::$prototype->data= array( 'another default data' );
    81. echo $arr . '<br/>';
    82. echo ( new Deque ) . '<br/>';
    83. echo '<br/>';
    84.  
    85. ?>
    выводит:
    Код (Text):
    1. Deque[ 8-), abc, ghi, 0 ] 123
    2.  
    3. Deque[ ]
    4. Deque[ default data ]
    5.  
    6. Deque[ default data ]
    7. Deque[ another default data ]
     
  7. zerkms

    zerkms Активный пользователь

    С нами с:
    21 мар 2006
    Сообщения:
    3
    Симпатии:
    0
    Адрес:
    Russia, Komsomolsk-on-Amur
    есть подозрение что такое поведение уже давно описано паттерном chain of responsibility
     
  8. Anonymous

    Anonymous Guest

    zerkms, так покажите свою реализацию.
     
  9. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    zerkms, нет, "цепочка обязанностей" не предполагает выполнение метода одного объекта в контексте другого - там просто происходит всплытие "сообщения" по цепочке объектов пока какой-нибудь из них его не перехватит.
     
  10. zerkms

    zerkms Активный пользователь

    С нами с:
    21 мар 2006
    Сообщения:
    3
    Симпатии:
    0
    Адрес:
    Russia, Komsomolsk-on-Amur
    Горбунов Олег
    мне, слава богу, такого изврата ещё не требовалось ;)

    dark-demon
    угу, тогда пардон ;)
     
  11. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    dark-demon
    Только безумный фанат Жабаскрипта мог написать такое :twisted:
     
  12. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    вот бы ещё яваскрипт можно было бы в пхп превратить... :) в частности, очень не хватает магических функций __call, __get и __set
     
  13. Amian

    Amian Активный пользователь

    С нами с:
    15 мар 2007
    Сообщения:
    189
    Симпатии:
    0
    dark-demon
    Это все-таки Chain of responsability в контексте извращенного PHP-шного делегирования :p
     
  14. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    ИМХО, в PHP оно нафиг надо :)
     
  15. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    ага, мы такого паттерна не знаем, поэтому назовём его "Chain of responsability в контексте извращенного PHP-шного делегирования" :)
     
  16. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    Psih, как сказать, я сейчас класс для работы с бд перевожу на такую схему. объекту перадаётся dsn в соответствии с которым он "вгружает в себя" драйвер для работы с соответствующей субд. получается как буд-то этот класс изначально был предназначен для работы именно с этой субд.
     
  17. Anonymous

    Anonymous Guest

    Аццкий дзен. Погружение драйвера в себя... dark-demon конечно изрядная сволочь, но за умение мыслить нестандартно я его обожаю!
    /Щас кончится коньяк — и кончатся признания ГО ) /
     
  18. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    Я тут переработал немного базовый класс.
    PHP:
    1. <?php
    2. class O_Error extends Exception {}
    3.  
    4. class O_Object {
    5.     static $prototype;
    6.     private $_context= null;
    7.     private $_donors= array();
    8.     var $a_b= 1;
    9.     function __construct( $donors= null ){
    10.         $this->_context= $this;
    11.         $this->_donors= is_array( $donors ) ? $donors : func_get_args();
    12.     }
    13.     static function birth( ){
    14.         return new self( array( 'parent' => self::$prototype ) );
    15.     }
    16.     function _birth( ){
    17.         return new $this( array( 'parent' => $this ) );
    18.     }
    19.     function _apply( $obj, $method, $args ){
    20.         $self= $this->_context;
    21.         $this->_context= $obj->_context;
    22.         $ret= call_user_func_array( array( $this, $method ), $args );
    23.         $this->_context= $self;
    24.         return $ret;
    25.     }
    26.     function _hasDonor( $donor ){
    27.         if( $this === $donor ) return $this;
    28.         foreach( $this->_donors as $ns => $don )
    29.             if( $d= $don->_hasDonor( $donor ) ) return $d;
    30.     }
    31.     function __get( $name ){
    32.         $localName= strstr( $name, '_' );
    33.         $nameSpace= substr( $name, 0, strlen($name) - strlen( $localName ) );
    34.         $localName= substr( $localName, 1 );
    35.         $obj= $this;
    36.         if( $localName ):
    37.             if( $nameSpace ) $obj= $obj->_donors[ $nameSpace ];
    38.             return $obj->{$localName};
    39.         endif;
    40.         $localName= $nameSpace;
    41.         foreach( $obj->_donors as $nameSpace => $donor ):
    42.             $ret= $donor->{$localName};
    43.             if( $ret ) return $ret;
    44.         endforeach;
    45.         return null;
    46.     }
    47.     function __set( $name, $value ){
    48.         $localName= strstr( $name, '_' );
    49.         $nameSpace= substr( $name, 0, strlen($name) - strlen( $localName ) );
    50.         $localName= substr( $localName, 1 );
    51.         $obj= $this;
    52.         if( $localName ):
    53.             if( $nameSpace ) $obj= $obj->_donors[ $nameSpace ];
    54.             return $obj->{$localName}= $value;
    55.         endif;
    56.         return $obj->{$nameSpace}= $value;
    57.     }
    58.     function __call( $name, $args ){
    59.         $localName= strstr( $name, '_' );
    60.         $nameSpace= substr( $name, 0, strlen($name) - strlen( $localName ) );
    61.         $localName= substr( $localName, 1 );
    62.         $self= $this->_context;
    63.         if( $localName ):
    64.             $obj= $this;
    65.             if( $nameSpace ):
    66.                 $obj= $this->_donors[ $nameSpace ];
    67.                 if( !$obj ) return null;
    68.             endif;
    69.             return $obj->_apply( $this->_context, $localName, $args );
    70.         endif;
    71.         $localName= $nameSpace;
    72.         foreach( $this->_donors as $nameSpace => $donor ):
    73.             try {
    74.                 return $donor->_apply( $this->_context, $localName, $args );
    75.             } catch( O_Error $e ){
    76.             }
    77.         endforeach;
    78.         throw new O_Error( 'Method "' . $localName . '" not found' );
    79.     }
    80.     function __toString( ){
    81.         $donors= '';
    82.         foreach( $this->_donors as $nameSpace => $donor ):
    83.             if( $donors ) $donors.= ', ';
    84.             $donors.= $donor->__toString();
    85.         endforeach;
    86.         return get_class( $this ) . "( {$donors} )";
    87.     }
    88. }
    89.  
    90. O_Object::$prototype= new O_Object( );
    91. ?>
    из основных отличий: теперь для доступа к полям и методам можно использовать пространства имён. в качестве символа разделителя используется знак подчёркивания.
    пусть, например, у нас есть класс для работы с конкретной субд (драйвер) и есть абстрактный класс который реализует логику не зависимую от субд. у обоих есть метод query.
    метод query драйвера принимает "сырой" запрос. метод же абстрактного класса экранирует по определённой схеме, формирует "сырой запрос" и передаёт его методу query драйвера.

    PHP:
    1. <?php
    2.     function query( $query ){ # query( 'insert into table values (', (string) $_GET[ 'a' ], ')' );
    3.         if( !$query ) $this->wrongCall( "missing query string" );
    4.         $self= $this->_context;
    5.         $sql= $self->prepare( func_get_args( ) );
    6.         return $self->driver_query( $sql );
    7.     }
    8. ?>
    при этом драйвер подгружается в абстрактный класс следующим образом:

    $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 методов.
     
  19. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    короче, использование такого наследования позволяет создавать абстрактные реализации алгоритмов и "подцеплять" их к любому объекту, который поддерживает необходимый им интерфейс.

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

    $obj->_donors[]= Math::$prototype;
    echo 'factorial of ' . $obj->getValueAsNumber() . '= ' . $obj->factorial();

    при этом метод factorial для получения начального значения просто вызывает getValueAsNumber.

    PHP:
    1. <?php
    2. function factoial( ){
    3.     $val= 1;
    4.     for( $i=  $this->_context->getValueAsNumber(); $i; --$i )
    5.         $val*= $i;
    6.     return $val;
    7. }
    8. ?>
     
  20. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    кстати, для этого велосипеда и термин уже есть. :)
    "штрих" - это абстрактный объект реализующий одни интерфейсы на базе других, который может быть "подмешан" к любому объекту поддерживающему необходимый интерфейс.