1

私は私を夢中にさせている深刻な設計上の問題に直面しています。多重継承か何かでしか解決できないと思います。これが私がやりたいことです:

次のように定義された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オブジェクトにしたい場合はどうなりますか?サブクラスをインスタンス化し、新しいオブジェクトのフィールドに同じデータを再入力する必要がないように、「オブジェクトを拡張する」方法はありますか?

別の関連する問題:後で定義される他の多くのカテゴリのユーザーがあり、それぞれが独自の動作を持っていますが、常に基本的なものであり、この場合、1つのタイプのオブジェクトが必要であるため、階層を作成することは意味がありません。他のタイプからメソッドを継承しないでください。ただし、一方のタイプからもう一方のタイプに動的に「インポート」される追加機能があることが望ましい場合があります。

4

4 に答える 4

3

現在、これはPHPではすぐには不可能です。実装が容易な順に、いくつかの選択肢を示します。

まず、関連するクラス間で可能なすべての変換がわかっている場合は、現在のオブジェクトを取得し、新しいクラスのクリーンなインスタンスにデータを入力して返すメソッドを簡単に作成できます。これは、元の質問で言及したアイデアです。それはあなたができる最も簡単で安全なことです。

次に、十分に最新バージョンのPHPを使用している場合は、Serializableインターフェースを使用して巧妙なトリックを行うことができます。そのインターフェースを実装する場合、__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

従わなかった場合、このコードはシリアル化されたデータのクラス名を置き換えます。これを行うことで、すべて同じプロパティを持つaをに変換しBarました。Bazこれは、プロパティが同一である場合にのみ実際に機能します。プロパティが一致しない場合にPHPが何をするかはわかりません。また、Serializableを実装する場合は、serializeメソッドとunserializeメソッドで変換を処理する必要があります。

これはまた、あなたのコードの将来のメンテナがあなたを追跡してあなたを傷つけたいと思うかもしれない途方もないハックです。おそらくマイナーなパフォーマンスのペナルティもあります。最終的に使用する場合は、必ずベンチマークを行ってください。そしてボディーガードを雇う。または、少なくとも将来のメンテナがあなたの住所を見つけることができる殺人的な精神病質者にならないようにしてください。

第3に、オブジェクトベースの構築ではなくクラスベースの構築に戻ります。PHP5.4(または現在のトランクが最終的になるもの)を待つことができれば、無名関数をプロパティに配置して、あたかもそれらを呼び出すことができます。それらはメソッドでした。以前のバージョンのプロパティに無名関数を配置することはできますが、少なくともPHP 5.3の時点では、これらの関数は参照できない$thisため、オブジェクトの一部としてはまったく役に立ちません。この制限は、現在同じことを達成するために魔法の方法を使用する__callことを妨げるものでもあります。

第四に、これは完全に答えではありません。この種のクラスを曲げる体操が必要な場合は、RubyまたはPerlを検討してください。Pythonでも同様のことができると思いますが、私はPythonで作業したことがなく、確信が持てません。PHPはOOに関しては柔軟な言語ではなく、内部リストの人々はOOを他の言語のより興味深い標準に引き上げることに関心がありません。


関連する問題に関しては、インスタンスレベルの特性が本当に必要なようです。PHP 5.4にも特性がありますが、インスタンスレベルではなく、クラスレベルです。それについての私の解説については#4を参照してください。

于 2011-03-01T06:09:58.513 に答える
1

デコレータパターンのように聞こえるかもしれません

<?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
于 2011-03-01T08:44:01.633 に答える
1

あなたのシナリオはファクトリパターンを必要とするかもしれないと思います

そもそもOrdinaryUserをインスタンス化するべきではなかったのかもしれません〜

于 2011-03-01T04:37:47.440 に答える
0

あなたが説明することは不可能です、あなたは別のアプローチを検討しましたか?Userクラスに配列内のさまざまな特権実装が含まれている場合はどうなりますか。

これを「IsA」および「HasA」の問題と見なします。AdminUserは「管理者」ですが、「特権」を持っているため、privはメンバー変数として格納する必要があります。

于 2011-03-01T04:25:55.817 に答える