1

我正面臨一個令我發瘋的嚴重設計問題。我認爲它只能用多重繼承或其他東西來解決。所以這是我想做的事:是否可以擴展對象的功能或將方法導入到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對象,那麼該怎麼辦?有沒有一種「擴展對象」的方法,以避免實例化子類,並且必須用相同的數據重新填充新對象的字段?

另一個相關的問題:我可能有許多其他的類別,則後面定義的用戶,每個都具有自己獨特的bahaviour,但總是一個基本的,它是沒有意義在這種情況下,因爲大多數製造等級次,一種類型的對象不應該繼承另一種類型的方法,儘管可能需要將一種類型的附加功能「導入」另一種類型的對象。

回答

1

聽起來就像是Decorator Pattern可以幫助你

<?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 
+0

這種模式真的很棒!之前我曾聽說過這個消息,但從來沒有想過它能解決這類問題。這個解決方案正是我所需要的,謝謝!我將UserDecorator看作某種適配器,對吧?此外,我個人不喜歡使用魔法方法,但認爲__call魔術方法在這裏很合適,所以我絕對接受你的答案。所以似乎可以通過這種所謂的裝飾器模式即時添加方法並擴展對象。查爾斯的回答也很好,我很難在你和他之間做出選擇。 – fabio 2011-03-01 17:27:39

+0

我也不喜歡使用魔術方法,但在這個實現中'__call'增加了一些不是'經典'裝飾器模式真正的一部分。在經典模式中,每個裝飾器都必須實現相同的接口並將方法調用轉發給它所包裝的對象,並可選擇對參數執行自己的操作。通過使用一點魔力,我們允許裝飾者向它包裝的對象添加新方法,並且這些方法可以從構造的外層獲得。這種靈活性非常方便,但也有其缺點,即弱化了OO合同。 – meouw 2011-03-01 20:31:58

0

你描述的是不可能的,你有沒有考慮過不同的方法?如果你的User類在數組中包含了各種特權實現會怎麼樣?

將其視爲「是A」和「有A」問題;而AdminUser'是'管理員,它'具有'特權,所以所述priv應該被存儲爲成員變量。

1

我覺得您的情況可能需要進行Factory Pattern

也許你不應該實例擺在首位〜

3

一個OrdinaryUser這不是立即PHP可能現在。爲了易於實施,以下是一些替代方案。

首先,如果你知道所有相關的類之間的可能的轉換,你可以輕鬆地創建,是以當前對象,填充類新的清潔實例,並返回它的方法。這是你在原始問題中提到的想法。這是你可以做的最直接,最安全的事情。

,如果您使用的是足夠現代的PHP版本,則可以使用the Serializable interface作爲一個簡潔的技巧。如果您實現該接口,則從不調用__sleep/__wakeup,構造函數也不會。這意味着你可以使用這些方法來製作一個便宜的伎倆。這裏是沒有界面的一些愚蠢的演示代碼來演示:

[[email protected] ~]$ 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,那麼您的序列化和反序列化方法將需要處理轉換。

這也是一個巨大的黑客可能會導致您的代碼未來的維護人員想跟蹤你並傷害你。也有可能是一個小小的表現懲罰。如果您最終使用它,請務必進行基準測試。並聘請保鏢。或者至少確保未來的維護者不會是可以找到你的地址的殺人精神病患者。

,回到基於類的建築,而不是基於對象的結構:如果你能等待PHP 5.4(或任何當前Trunk最終會被),你將能夠把匿名函數屬性並將它們作爲方法調用。儘管您可以在早期版本的屬性中放置匿名函數,但至少從PHP 5.3開始,這些函數不能引用$this,因此作爲對象的一部分是無用的。此限制也阻止您使用the __call magic method來實現相同的事情。

第四,這是一個完全沒有答案,如果你想要這些類彎曲體操,可以考慮Ruby或Perl。我認爲Python可以做類似的事情,但我沒有在裏面工作,也無法確定。就面向對象而言,PHP不是一種靈活的語言,而內部列表中的人員不會將OO帶到更有趣的其他語言標準。


關於您的相關問題,這聽起來像你真正想要的實例級特性。 PHP 5.4也會有特性,但是在類級而不是實例級。關於我的評論,請參閱#4。

+0

+1 - 你的答案是對我很有啓發。當然,我不打算使用序列化/反序列化的欺騙手段,但確實非常有效,我喜歡遵循這一點。並感謝你的新版本的PHP的可能性的其他信息(我已經閱讀了有關特質已經和喜歡這個想法的東西;)。儘管我很難決定哪個答案是最好的,但我肯定會留在這裏由meouw提出的修飾器模式,因爲它似乎是一種「乾淨」的解決方案。 – fabio 2011-03-01 17:19:51

相關問題