[英]Is it possible to extend the functionality of an object or to Import methods into an object in PHP?
我面臨着一個嚴重的設計問題,這使我發瘋。 我認為只能通過多重繼承或其他方式解決。 所以這是我想做的:
假設我以這種方式定義了一個稱為OrdinaryUser的基本用戶類:
class OrdinaryUser
{
private $id,$name;
public function __construct($id)
{
$this->id = $id;
$this->name = fictionalDB::getUserNameById($id);
}
public function getName()
{
return $this->name;
}
public function getId()
{
return $this->id;
}
}
我有一個名為AdminUser的子類,具有其他功能:
class AdminUser extends OrdinaryUser
{
public function deleteUser($id)
{
echo "Deleting user where id=$id";
}
}
問題 :如果我已經實例化了一個類型為“ OrdinaryUser”的對象並將其即時變為AdminUser對象,該怎么辦? 有沒有一種“擴展對象”的方法,以避免實例化子類,而不必用相同的數據重新填充新對象的字段?
另一個相關的問題 :以后可能定義了許多其他類別的用戶,每種類別都有自己獨特的行為,但始終是基本行為,在這種情況下創建層次結構沒有任何意義,因為大多數情況下,一種類型的對象應該最好不要從另一種類型繼承方法,盡管可能希望將一種類型的附加功能動態地“導入”到另一種類型。
目前尚無法在PHP中立即實現。 為了便於實現,這里有一些替代方案。
首先 ,如果您知道相關類之間的所有可能轉換,則可以輕松地創建一個方法,該方法采用當前對象,填充新類的干凈實例並返回它。 這就是您在原始問題中提到的想法。 這是您可以做的最簡單,最安全的事情。
其次 ,如果您使用的是足夠現代的PHP版本,則可以使用Serializable接口進行巧妙的操作。 如果實現該接口,則永遠不會調用__sleep
/ __wakeup
, __sleep
__wakeup
也不會調用。 這意味着您可以以便宜的方式使用這些方法。 這是一些沒有界面演示的愚蠢的演示代碼:
[mcg@mcg-workstation ~]$ php -a
Interactive shell
php > class Foo { public $a; public $b; }
php > class Bar extends Foo { public $c; }
php > class Baz extends Foo { public $c; }
php > $one = new Bar();
php > $one_s = serialize($one);
php > echo $one_s;
O:3:"Bar":3:{s:1:"c";N;s:1:"a";N;s:1:"b";N;}
php > $one_s = explode(':', $one_s, 4);
php > print_r($one_s);
Array
(
[0] => O
[1] => 3
[2] => "Bar"
[3] => 3:{s:1:"c";N;s:1:"a";N;s:1:"b";N;}
)
php > $two_s = $one_s;
php > $two_s[1] = strlen('Baz'); $two_s[2] = '"Baz"';
php > $two_s = join(':', $two_s);
php > echo $two_s;
O:3:"Baz":3:{s:1:"c";N;s:1:"a";N;s:1:"b";N;}
php > $two = unserialize($two_s);
php > echo get_class($two);
Baz
如果您不遵循,則此代碼將替換序列化數據中的類名稱。 通過這樣做,我將Bar
轉換為具有所有相同屬性的Baz
。 僅當屬性相同時,這才真正起作用。 我不確定如果屬性不匹配,PHP將做什么,並且如果您實現Serializable,則您的serialize和unserialize方法將需要處理轉換。
這也是一個巨大的漏洞,可能導致您的代碼的未來維護者想要跟蹤您並傷害您。 可能還會有輕微的性能損失。 如果最終使用它,請確保進行基准測試。 並雇用保鏢。 或者至少確保將來的維護者不會成為能夠找到您地址的謀殺性精神病患者。
第三 ,回到基於類的構造,而不是基於對象的構造:如果您可以等待PHP 5.4(或當前的主干最終成為現實),則可以在屬性中放置匿名函數並像調用它們一樣他們是方法。 盡管您可以在早期版本的屬性中放置匿名函數,但至少從PHP 5.3起,這些函數不能引用$this
,因此作為對象的一部分是完全沒有用的。 此限制也是導致您無法使用__call
magic方法立即實現同一功能的原因。
第四 ,這是一個完全沒有答案的方法,如果您想使用此類彎曲類體操,請考慮使用Ruby或Perl。 我認為Python可以做類似的事情,但是我還沒有做過,也不能確定。 在面向對象方面,PHP並不是一種靈活的語言,內部人員名單上的人對將OO提升到其他語言的更有趣的標准沒有興趣。
關於您的相關問題,聽起來您確實需要實例級特征。 PHP 5.4也具有特征,但在類級別,而不在實例級別。 有關此內容的評論,請參見#4。
我認為您的方案可能需要工廠模式
也許您本來不應該實例化OrdinaryUser的〜
聽起來裝飾器模式可能會幫助您
<?php
/*
an interface to ensure we only decorate Users
and possibly some of the most common methods that all users have
so that we don't always suffer the overhead of the magic __call method
*/
interface User
{
public function getId();
public function getName();
}
class OrdinaryUser implements User
{
private $id,$name;
public function __construct($id)
{
$this->id = $id;
$this->name = fictionalDB::getUserNameById($id);
}
public function getName()
{
return $this->name;
}
public function getId()
{
return $this->id;
}
}
/*
There aren't any abstract methods in this class
but it is declared abstract because there is no point in instantiating one
*/
abstract class UserDecorator implements User
{
protected $user;
public function __construct( User $user )
{
$this->user = $user;
}
public function getId()
{
return $this->user->getId();
}
public function getName()
{
return $this->user->getName();
}
/*
Any methods that aren't implemented by this type of
user are dealt with by the decorated user
*/
public function __call( $method, $arguments )
{
return call_user_func_array( array( $this->user, $method ), $arguments );
}
}
class AdminUser extends UserDecorator
{
/*
Add any methods that are particular to this type of user
*/
public function addedMethod()
{
// do AdminUser type stuff
return "doing added method stuff\n";
}
}
class FooUser extends UserDecorator
{
public function foo()
{
// do some foo
return "doing fooness\n";
}
}
// just for testing
class fictionalDB
{
public static function getUserNameById( $id )
{
$db = array(
1 => 'Peter',
2 => 'Paul'
);
return $db[$id];
}
}
$user = new OrdinaryUser( 1 );
echo $user->getName(); // Peter
// make Peter into an AdminUser
$user = new AdminUser( $user );
// and also add some fooness
$user = new FooUser( $user );
echo $user->addedMethod(); // doing added method stuff
echo $user->foo(); // doing fooness
您描述的內容是不可能的,您是否考慮過其他方法? 如果您的User類在數組中“包含”了各種特權實現,該怎么辦?
將其視為“是A”和“具有A”問題; 當AdminUser是管理員時,它具有特權,因此priv應當作為成員變量存儲。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.