2

名前、年齢、場所などの基本的なユーザー情報の操作を担当する基本ユーザークラスがあります。グループ機能を使用してシステムを拡張し、後でプロジェクトや会議を使用してシステムを拡張したいと考えています。例えば:

class User
{
    public function getName() {
        ...
    }

    public function getAge() {
        ...
    }
}

そして、グループ機能で拡張します。

class GroupableUser extends User
{
    public function join($groupId) {
        ...
    }

    public function leave($groupId) {

    }

    public function requestGroupInvitation($groupId) {
        ...
    }

    public function acceptGroupInvitation($groupId) {
        ...
    }
}

GroupableUserという名前は私には奇妙に思えますが、グループに参加および離脱するユーザーはその逆ではないため、グループクラスにjoinおよびleaveメソッドを追加することは意味がありません。

そして後で、 UserThatCanHaveProjectsUserThatCanParticipateInMeetingsのようなクラスができます。これらのクラスに名前を付ける方法は?

そして、あなたは通常、これらの状況にどのように対処しますか?

4

4 に答える 4

7

階層があまり自然ではないため、クラスに名前を付ける方法を見つけることができないようです。実際には、ユーザーは複数のグループに参加でき、各グループには複数のユーザーがいる場合があります。ただし、ユーザーはグループなしで存在する可能性がありますが、ユーザーなしでグループは存在できません。これは、ユーザーがグループのメンバーであり、addUserremoveUser配置される必要があることを意味しますGroup。なんで?ユーザーがグループに参加またはグループから脱退して完全に満足していることを「知らない」場合もあれば、ユーザーがグループに参加できることを「知らない」グループは存在できないためです。

于 2013-01-22T13:45:59.183 に答える
4

グループ/会議に参加してプロジェクトを行う機能は、ユーザーが実行できる可能性があるものですが、ユーザー何であるかを定義するものではありません。これは、追加のクラスを使用してこれらのオプションをモデル化することは、適切な設計上の選択ではないことを示す非常に明確な兆候です。

静的アプローチ#1:インターフェース

静的に型付けされた言語では、単純な実装は次のようになります。

interface IGrouppableUser {
     public function join(...);
}

class GroupableUser implements IGrouppableUser {
     public function join(...) { /* implementation */ }
}

また、グループ化可能なユーザーのコンシューマーはこれを受け入れIGrouppableUser、必要な数のクラスを作成できるようにします。これはPHPでも実行できますが、前述のように、言語に関係なく、おそらく適切な設計ではありません。

脚注として、PHP 5.4以降の言語にトレイトを追加すると、上記のシナリオをもう少し便利に実装できることを追加する必要があります(クラスはインターフェイスを実装する代わりにトレイトを使用できます。つまり、joinコードベース全体の実装をコピーして貼り付けます)。しかし、概念的にはまったく同じアプローチです。

このアプローチの主な欠点は、拡張性がないことです。2つまたは3つのタイプのユーザーのみが必要な場合は、問題ない可能性があります。

静的アプローチ#2:「サポートされていない」例外

ほとんどのユーザーがグループ化可能でプロジェクトを持つことができる場合、クラスの地獄のような階層を作成することはあまり意味がありません。必要なメンバーをクラスに追加するだけUserで、ファットなインターフェースになります。

class GroupableUser implements IGrouppableUser {
     private $isGrouppable = true; // default, can be changed at runtime

     public function join(...) {
         if (!$this->isGrouppable) throw new Exception("User is not grouppable!");

         // real implementation
     }
}

このアプローチの主な欠点は、クラスUserが無条件に広範囲の操作をサポートしているように見えることですが、実際にはサポートしておらず、その結果、コーディングが面倒でエラーが発生しやすくなります(多くのtry/ catch)。大多数のユーザーが大多数の操作をサポートしていれば問題ないかもしれません。

動的アプローチ#1:行動

Userインスタンスがこれらの操作に参加できるように条件付きで許可する方がはるかに優れています。これは、オブジェクトに「動作」を動的にアタッチできる必要があることを意味しますUser。これは、幸い、動的に型指定された言語で非常に簡単に実行できます。

確立されたオープンソースプロジェクトから「動作」の実装を調べることをお勧めしますが、ここに簡単で汚い例があります。

動作の基本クラスとサンプルの実装

abstract class Behavior {
    public function provides($name) {
        return method_exists($this, $name);
    }

    public function invoke($target, $name, $arguments) {
        array_unshift($arguments, $target);
        return call_user_func_array(array($this, $name), $arguments);
    }
}

class GrouppableBehavior extends Behavior {
    public function join(User $user, $groupName) {
        echo "The user has joined group $groupName.";
    }
}

構成可能な(動作を使用できる)基本クラスとユーザー実装

class Composable {
    private $behaviors = array();

    public function __call($name, $arguments) {
        foreach ($this->behaviors as $behavior) {
            if ($behavior->provides($name)) {
                return $behavior->invoke($this, $name, $arguments);
            }
        }

        throw new Exception("No method $name and no behavior that implements it");
    }

    public function attach($behavior) {
        $this->behaviors[] = $behavior;
    }
}

class User extends Composable {}

テストドライバー

$user1 = new User;
$user2 = new User;

$user1->attach(new GrouppableBehavior);
$user1->join('Test Group'); // works
$user2->join('Test Group'); // throws

実際の動作をご覧ください

このアプローチの主な欠点は、より多くのランタイムリソースを消費し、動作publicがアタッチしているクラスのメンバーにしかアクセスできないことです。場合によっては、動作を機能させるために非公開にする必要がある実装の詳細を公開することを余儀なくされることがあります。

動的アプローチ#2:デコレータ

動作のバリエーションはデコレータパターンです:

interface IUser {}

interface IGrouppableUser extends IUser {
    public function join(...);
}

class User implements IUser {}

class UserGroupingDecorator implements IGrouppableUser {
    private $realUser;

    public function __construct(IUser $realUser) {
        $this->realUser = $realUser;
    }

    public function join(...) { /* implementation */ }

    /* now you need to implement all IUser methods 
       and forward the calls to $this->realUser */

    /* if IUser exposes bare properties we have a problem! */
}

このパターンを使用すると、自由にUserGroupingDecoratorラップするを作成し、またはのIUserいずれかを受け入れるものにデコレータを渡すことができます。IUserIGrouppableUser

このアプローチの主な欠点は、の非公開メンバーへのアクセスも提供しないことですUser。さらに、プロパティが前者でも定義されている場合、からベアプロパティアクセスを「転送」する方法がないため、からベアプロパティを公開することは除外されます。実際に定義されていない限り、実装することはできませIUserん。この状況は、プロパティを個別のゲッター/セッターメソッドとして公開することで回避できますが、それはさらに多くのコードを記述することを意味します。UserGroupingDecorator$realUserIGrouppableUser

于 2013-01-22T13:56:31.907 に答える
1

データベース駆動型のWebアプリの観点から考えると、これは5つのクラスで行うことができます。

1)ユーザー
2)グループ
3)GroupAssignment
4)GroupInvitation
5)GroupInvitationRequest

クラス3は、1人のユーザーを1つのグループにマップするだけです。クラス4と5はさらに明白です。この設定は、とにかくデータベースにあるものとほぼ一致します。これにより、ユーザーは多くのグループのメンバー(または1つ、またはなし)になる可能性があるため、かなりの柔軟性が得られます。

編集:問題の新しい情報に基づいて追加のクラスを追加しました。

于 2013-01-22T13:47:47.643 に答える
0

Groupableインターフェイスの適切な名前になります。インターフェイスは、クラスの側面を記述する必要があります。これは、いくつかの異なるクラスのインスタンスで必要になる場合があります。インターフェイスはクラス間のコントラクトです。

すべてにハンガリアン記法を使用しない限り、その前に「I」を付けないでください。クラス名には名詞を、インターフェースには形容詞を使用するだけです。

クラスの名前については、次のようにします。

class Participant extends User implements Groupable
{
    // methods that will be expected by from instance of this class
    // by any class that requires an object to have Groupable interfrace
}
于 2013-01-22T19:15:45.807 に答える