39

単純なオブジェクト指向設計の問題にどのようにアプローチするかについて質問したいと思います。このシナリオに取り組む最善の方法について、私自身の考えがいくつかありますが、スタック オーバーフロー コミュニティから意見を聞くことに興味があります。関連するオンライン記事へのリンクも歓迎します。私は C# を使用していますが、質問は言語固有ではありません。

、、フィールドPersonを持つテーブルを持つデータベースのビデオ ストア アプリケーションを作成しているとします。また、 へのリンクを持つテーブルと、にもリンクするテーブルもあります。PersonIdNameDateOfBirthAddressStaffPersonIdCustomerPersonId

Customer単純なオブジェクト指向のアプローチは、a が"is a"であると言って、次のPersonようなクラスを作成することです。

class Person {
    public int PersonId { get; set; }
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string Address { get; set; }
}

class Customer : Person {
    public int CustomerId { get; set; }
    public DateTime JoinedDate { get; set; }
}

class Staff : Person {
    public int StaffId { get; set; }
    public string JobTitle { get; set; }
}

これで、すべての顧客にメールを送信する関数を作成できます。

static void SendEmailToCustomers(IEnumerable<Person> everyone) { 
    foreach(Person p in everyone)
        if(p is Customer)
            SendEmail(p);
}

このシステムは、顧客とスタッフの両方である誰かがいるまでは問題なく機能します。everyoneリストに同じ人物が 2 回 (1 回は a として、1 回は a として)Customer含まれることを本当に望んでいないと仮定すると、次Staffのいずれかを任意に選択しますか?

class StaffCustomer : Customer { ...

class StaffCustomer : Staff { ...

明らかに、これら 2 つのうち最初のものだけがSendEmailToCustomers機能を壊すことはありません。

それで、あなたは何をしますか?

  • クラスにおよびクラスPersonへのオプションの参照を持たせますか?StaffDetailsCustomerDetails
  • Personに加えてオプションStaffDetailsCustomerDetails?を含む新しいクラスを作成します。
  • すべてをインターフェイス (例: IPersonIStaffICustomer) にして、適切なインターフェイスを実装する 3 つのクラスを作成しますか?
  • 別のまったく異なるアプローチをとりますか?
4

12 に答える 12

49

マーク、これは興味深い質問です。これについては、多くの意見が見られます。「正しい」答えはないと思います。これは、システムが構築された後に厳格な階層オブジェクト設計が実際に問題を引き起こす可能性がある場所の良い例です。

たとえば、「Customer」クラスと「Staff」クラスを使用したとしましょう。システムを展開すれば、すべてがうまくいきます。数週間後、ある人から、自分は「スタッフ」であると同時に「顧客」でもあるのに、顧客からのメールを受け取っていないと指摘されました。この場合、多くのコード変更を行う必要があります (リファクタリングではなく、再設計)。

人々とその役割のすべての順列と組み合わせを実装する一連の派生クラスを作成しようとすると、非常に複雑になり、保守が困難になると思います。上記の例が非常に単純であることを考えると、これは特に当てはまります。ほとんどの実際のアプリケーションでは、物事はより複雑になります。

ここでの例では、「別のまったく異なるアプローチを取る」とします。Person クラスを実装し、その中に「ロール」のコレクションを含めます。各人は、「顧客」、「スタッフ」、「ベンダー」などの 1 つ以上の役割を持つことができます。

これにより、新しい要件が発見されたときに役割を追加しやすくなります。たとえば、単純に基本の「ロール」クラスがあり、そこから新しいロールを派生させることができます。

于 2008-10-19T15:34:59.433 に答える
17

Party and Accountability パターンの使用を検討することをお勧めします。

このようにして、Person はタイプが Customer または Staff である可能性がある Accountability のコレクションを持ちます。

後で関係タイプを追加すると、モデルもより単純になります。

于 2008-10-19T15:51:32.647 に答える
10

純粋なアプローチは次のとおりです。すべてをインターフェイスにします。実装の詳細として、オプションでさまざまな形式の構成または実装継承を使用できます。これらは実装の詳細であるため、公開 API には関係ありません。

于 2008-10-19T15:29:04.453 に答える
7

Person は人間ですが、Customer は、Person が時々採用する役割にすぎません。Man と Woman は Person を継承する候補になりますが、Customer は別の概念です。

Liskov 置換の原則は、基本クラスへの参照がある場合、それを知らなくても派生クラスを使用できなければならないことを示しています。Customer を Person に継承させることは、これに違反します。顧客は、組織が果たす役割でもあるかもしれません。

于 2008-10-20T09:34:42.670 に答える
5

Foredeckerの答えを正しく理解したかどうか教えてください。これが私のコードです(Pythonで。申し訳ありませんが、C#はわかりません)。唯一の違いは、人が「顧客である」場合は通知しないことです。彼の役割の1つがそのことに「興味を持っている」場合は通知します。これは十分な柔軟性がありますか?

# --------- PERSON ----------------

class Person:
    def __init__(self, personId, name, dateOfBirth, address):
        self.personId = personId
        self.name = name
        self.dateOfBirth = dateOfBirth
        self.address = address
        self.roles = []

    def addRole(self, role):
        self.roles.append(role)

    def interestedIn(self, subject):
        for role in self.roles:
            if role.interestedIn(subject):
                return True
        return False

    def sendEmail(self, email):
        # send the email
        print "Sent email to", self.name

# --------- ROLE ----------------

NEW_DVDS = 1
NEW_SCHEDULE = 2

class Role:
    def __init__(self):
        self.interests = []

    def interestedIn(self, subject):
        return subject in self.interests

class CustomerRole(Role):
    def __init__(self, customerId, joinedDate):
        self.customerId = customerId
        self.joinedDate = joinedDate
        self.interests.append(NEW_DVDS)

class StaffRole(Role):
    def __init__(self, staffId, jobTitle):
        self.staffId = staffId
        self.jobTitle = jobTitle
        self.interests.append(NEW_SCHEDULE)

# --------- NOTIFY STUFF ----------------

def notifyNewDVDs(emailWithTitles):
    for person in persons:
        if person.interestedIn(NEW_DVDS):
            person.sendEmail(emailWithTitles)

于 2008-10-19T18:18:46.670 に答える
3

「is」チェック(Javaの「instanceof」)は避けます。1 つの解決策は、 Decorator パターンを使用することです。EmailablePerson がコンポジションを使用して Person のプライベート インスタンスを保持し、メール以外のすべてのメソッドを Person オブジェクトに委譲する場合に、Person をデコレートする EmailablePerson を作成できます。

于 2008-10-19T16:46:41.597 に答える
1

クラスは単なるデータ構造です。どのクラスにも動作はなく、ゲッターとセッターだけです。ここでは継承は不適切です。

于 2008-10-19T18:24:04.380 に答える
1

別の完全に異なるアプローチをとってください: クラス StaffCustomer の問題は、スタッフのメンバーが単なるスタッフとして開始し、後で顧客になる可能性があるため、それらをスタッフとして削除し、StaffCustomer クラスの新しいインスタンスを作成する必要があることです。おそらく、'isCustomer' の Staff クラス内の単純なブール値により、全員のリスト (おそらく、適切なテーブルからすべての顧客とすべてのスタッフを取得してコンパイルされたもの) が、スタッフ メンバーを取得しないようにすることができます。

于 2010-07-22T07:44:12.553 に答える
1

私たちは昨年、大学でこの問題を研究しました。私たちは eiffel を学んでいたので、多重継承を使用しました。とにかく、フォアデッカーの役割の代替は十分に柔軟であるようです.

于 2008-10-19T16:31:45.467 に答える
1

ここにいくつかのヒントがあります: 「これを行うことさえ考えないでください」のカテゴリから、遭遇したコードのいくつかの悪い例を以下に示します。

Finder メソッドはオブジェクトを返します

問題: 検出された出現回数に応じて、ファインダー メソッドは出現回数を表す数値を返します。1 つだけ見つかった場合は、実際のオブジェクトを返します。

これをしないでください!これは最悪のコーディング プラクティスの 1 つであり、あいまいさをもたらし、別の開発者が関与したときにコードを台無しにしてしまいます。

解決策: そのような 2 つの機能が必要な場合: インスタンスのカウントとフェッチでは、カウントを返すメソッドとインスタンスを返すメソッドの 2 つが作成されますが、両方の方法を実行する単一のメソッドは作成されません。

問題: 派生した悪い習慣は、ファインダ メソッドが見つかった 1 つのオカレンスを返すか、複数見つかった場合はオカレンスの配列を返す場合です。この怠惰なプログラミング スタイルは、以前のスタイルを一般的に行っているプログラマーによって多く行われています。

解決策: これを手元に置いて、オカレンスが 1 つだけ見つかった場合は長さ 1(one) の配列を返し、それ以上のオカレンスが見つかった場合は長さ >1 の配列を返します。さらに、出現がまったく見つからない場合は、アプリケーションに応じて null または長さ 0 の配列が返されます。

インターフェイスへのプログラミングと共変の戻り値の型の使用

問題: インターフェイスへのプログラミングと、共変の戻り値の型の使用と呼び出しコードでのキャスト。

解決策: 代わりに、インターフェースで定義されているものと同じスーパータイプを使用して、戻り値を指す必要がある変数を定義します。これにより、インターフェイス アプローチへのプログラミングとコードがクリーンに保たれます。

1000 行を超えるクラスには危険が潜んでいます 100 行を超えるメソッドにも危険が潜んでいます!

問題: 一部の開発者は、1 つのクラス/メソッドにあまりにも多くの機能を詰め込んでいます。怠惰すぎて機能を壊すことができません。これにより、凝集度が低くなり、結合度が高くなる可能性があります。これは、OOP の非常に重要な原則の逆です! 解決策: 内部/ネストされたクラスを使いすぎないようにしてください。これらのクラスは、必要に応じてのみ使用する必要があります。これらを使用する習慣を付ける必要はありません。それらを使用すると、継承の制限など、より多くの問題が発生する可能性があります。コードの重複にご注意ください!同じまたは非常に類似したコードが、一部のスーパータイプの実装または別のクラスに既に存在している可能性があります。スーパータイプではない別のクラスにある場合は、凝集規則にも違反しています。静的メソッドに注意してください。追加するユーティリティ クラスが必要になる場合があります。
詳細: http://centraladvisor.com/it/oop-what-are-the-best-practices-in-oop

于 2012-08-03T17:34:32.073 に答える
1

スタッフメンバーである顧客に電子メールを送信する際の何が問題になっていますか? 彼が顧客である場合、彼に電子メールを送信できます。そう考えるのは間違っていますか?そして、なぜ「全員」をメーリング リストとして使用する必要があるのでしょうか。「sendEmailToEveryone」メソッドではなく「sendEmailToCustomer」メソッドを扱っているため、顧客リストを用意した方がよいでしょうか? 「全員」リストを使用したい場合でも、そのリストで重複を許可することはできません。

これらのいずれも多くの再設計で達成できない場合は、最初の Foredecker の回答を使用します。各人にいくつかの役割を割り当てる必要があるかもしれません。

于 2008-10-19T16:50:35.397 に答える
-1

おそらく、これには継承を使用したくないでしょう。代わりにこれを試してください:

class Person {
    public int PersonId { get; set; }
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string Address { get; set; }
}

class Customer{
    public Person PersonInfo;
    public int CustomerId { get; set; }
    public DateTime JoinedDate { get; set; }
}

class Staff {
    public Person PersonInfo;
    public int StaffId { get; set; }
    public string JobTitle { get; set; }
}
于 2013-07-16T16:31:47.377 に答える