オブジェクト指向のベストプラクティス-継承v構成vインターフェイス
-
03-07-2019 - |
質問
単純なオブジェクト指向の設計問題にどのようにアプローチするかについて質問したいと思います。このシナリオに取り組む最善の方法について、私自身のアイデアがいくつかありますが、Stack Overflowコミュニティから意見を聞くことに興味があります。関連するオンライン記事へのリンクも歓迎します。 C#を使用していますが、質問は言語固有ではありません。
データベースに Person
テーブルがあり、 PersonId
、 Name
、 DateOfBirth を含むビデオストアアプリケーションを作成しているとしますcode>および
Address
フィールド。また、 PersonId
へのリンクを持つ Staff
テーブル、および PersonId へのリンクを持つ
Customer
テーブルもあります。 code>。
単純なオブジェクト指向のアプローチは、 Customer
が「ある」と言うことです。 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; }
}
これで、すべての顧客にメールを送信する関数sayを作成できます。
static void SendEmailToCustomers(IEnumerable<Person> everyone) {
foreach(Person p in everyone)
if(p is Customer)
SendEmail(p);
}
このシステムは、顧客でありスタッフである誰かがいるまで問題なく機能します。 everyone
リストに同じ人物が2回(1回は Customer
として、1回は Staff
として)なりたくないと仮定すると、以下の間で任意の選択を行いますか
class StaffCustomer : Customer { ...
and
class StaffCustomer : Staff { ...
明らかに、この2つのうち最初の2つだけが SendEmailToCustomers
関数を中断しません。
では、どうしますか?
-
Person
クラスにStaffDetails
およびCustomerDetails
クラスへのオプションの参照を設定しますか? -
Person
に加えて、オプションのStaffDetails
およびCustomerDetails
を含む新しいクラスを作成しますか? - すべてをインターフェース(例:
IPerson
、IStaff
、ICustomer
)にし、適切なインターフェースを実装する3つのクラスを作成しますか? - 別のまったく異なるアプローチを取りますか?
解決
マーク、これは興味深い質問です。これについて多くの意見があります。 「正しい」答えがあるとは思わない。これは、システムが構築された後に、堅固な階層オブジェクトの設計が実際に問題を引き起こす可能性のある素晴らしい例です。
たとえば、「顧客」に行ったとしましょう。および「スタッフ」クラス。システムを展開すると、すべてが満足です。数週間後、誰かが「スタッフ」と「顧客」の両方であり、顧客からのメールを受け取っていないことを指摘します。この場合、多くのコードを変更する必要があります(リファクタリングではなく、再設計)。
すべての順列と人とその役割の組み合わせを実装する派生クラスのセットを作成しようとすると、保守が非常に複雑で困難になると思います。上記の例は非常に単純であるため、これは特に当てはまります。ほとんどの実際のアプリケーションでは、事態はより複雑になります。
ここでの例では、「まったく別のアプローチを取ります」に進みます。 Personクラスを実装し、その中に&quot; roles&quot;のコレクションを含めます。各ユーザーは、「顧客」、「スタッフ」、「ベンダー」などの1つ以上の役割を持つことができます。
これにより、新しい要件が発見されたときにロールを簡単に追加できます。たとえば、単純に「ロール」というベースを作成できます。クラスを作成し、それらから新しいロールを派生させます。
他のヒント
このようにして、Personには、CustomerまたはStaffタイプの説明責任のコレクションがあります。
後から関係タイプを追加すると、モデルはより単純になります。
純粋なアプローチは、すべてをインターフェイスにすることです。実装の詳細として、さまざまな形式の構成または実装継承のいずれかをオプションで使用できます。これらは実装の詳細であるため、パブリックAPIには関係ないため、人生を最もシンプルにするものを自由に選択できます。
人は人間であり、顧客は人が時々採用する単なる役割です。男と女は人を継承する候補者になりますが、顧客は別の概念です。
リスコフ置換の原則では、基本クラスへの参照がある場合、それを知らなくても派生クラスを使用できる必要があるとされています。顧客にPersonを継承させると、これに違反します。顧客は、おそらく組織が果たす役割かもしれません。
Foredeckerの答えを正しく理解したかどうかを教えてください。ここに私のコードがあります(Pythonで;すみません、私はC#を知りません)。唯一の違いは、ある人が「顧客」である場合は何も通知せず、彼の役割の「興味がある」場合は通知します。その事。 これで十分ですか?
# --------- 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)
&quot; is&quot;は避けたいチェック(Javaの「instanceof」)。 1つの解決策は、デコレーターパターンを使用することです。 EmailablePersonは構成を使用してPersonのプライベートインスタンスを保持し、すべての非メールメソッドをPersonオブジェクトに委任するPersonを装飾するEmailablePersonを作成できます。
昨年、この問題を大学で研究しました。エッフェルを学んでいたので、多重継承を使用しました。とにかく、Foredeckerの役割の選択肢は十分に柔軟性があるようです。
スタッフメンバーである顧客にメールを送信することの何が問題になっていますか?彼が顧客である場合、彼は電子メールを送信することができます。そう考えるのは間違っていますか? そして、なぜ「みんな」を取るべきなのかあなたのメーリングリストとして?私たちは&quot; sendEmailToCustomer&quot;を扱っているため、顧客リストを作成する方が良いでしょう「sendEmailToEveryone」ではなくメソッド方法? 「全員」を使用したい場合でもリスト内の重複を許可することはできません。
これらの多くが多くの再発見で達成できない場合、私は最初のForedeckerの答えに行きます。各人にいくつかの役割を割り当てる必要があるかもしれません。
クラスは単なるデータ構造です。どのクラスにも動作はなく、ゲッターとセッターのみです。ここでは継承は不適切です。
まったく別のアプローチを取ります:StaffCustomerクラスの問題は、スタッフのメンバーがスタッフとしてスタートし、後で顧客になる可能性があるため、スタッフとしてそれらを削除してStaffCustomerクラスの新しいインスタンスを作成する必要があることです。おそらく、 'isCustomer'のStaffクラス内の単純なブール値により、全員リスト(おそらくすべての顧客とすべてのスタッフを適切なテーブルから取得することでコンパイルされます)は、既に顧客として含まれていることがわかるため、スタッフメンバーを取得できません。
その他のヒントを次に示します。 のカテゴリから&#8220;これを行うことさえ考えないでください&#8221;遭遇したコードのいくつかの悪い例は次のとおりです。
Finderメソッドはオブジェクトを返します
問題:見つかったオカレンスの数に応じて、finderメソッドはオカレンスの数を表す数値を返します&#8211;または!見つかったものが1つだけの場合、実際のオブジェクトが返されます。
これをしないでください!これは最悪のコーディングプラクティスの1つであり、あいまいさをもたらし、別の開発者が遊びに来たときに、あなたがこれを行うことを嫌う方法でコードを台無しにします。
解決策:そのような2つの機能が必要な場合:インスタンスのカウントとフェッチは、カウントを返すメソッドとインスタンスを返すメソッドの2つのメソッドを作成しますが、両方の方法で1つのメソッドを作成することはありません。
問題:派生した悪い習慣は、finderメソッドが、見つかった1つの単一のオカレンスまたは複数のオカレンスが見つかった場合のオカレンスの配列を返す場合です。この怠programmingなプログラミングスタイルは、以前のスタイルを一般的に行うプログラマーによってよく行われます。
解決策:これを手にすると、1つのオカレンスが見つかった場合は長さ1(1)の配列を返し、さらにオカレンスが見つかった場合は長さ&gt; 1の配列を返します。さらに、オカレンスがまったく見つからないと、アプリケーションに応じてnullまたは長さ0の配列が返されます。
インターフェイスへのプログラミングと共変の戻り値型の使用
問題:インターフェイスへのプログラミング、共変の戻り値型の使用、呼び出しコードのキャスト。
解決策:代わりに、戻り値を指す変数を定義するためのインターフェースで定義された同じスーパータイプを使用します。これにより、プログラミングがインターフェイスアプローチになり、コードがクリーンになります。
1000行を超えるクラスは潜んでいる危険です 100行を超えるメソッドも潜在的な危険です!
問題:一部の開発者は、1つのクラス/メソッドに多くの機能を詰め込みすぎて、機能を壊すには遅すぎます&#8211;これにより、凝集力が低くなり、カップリングが大きくなる可能性があります&#8211; OOPの非常に重要な原則の逆!
解決策:内部/ネストされたクラスを使いすぎないようにします&#8211;これらのクラスは、必要に応じてのみ使用されるため、習慣を使用する必要はありません!それらを使用すると、継承の制限などの問題が発生する可能性があります。コードの重複に注意してください!同じまたは類似のコードは、スーパータイプの実装または別のクラスにすでに存在する可能性があります。スーパータイプではない別のクラスにある場合は、凝集ルールにも違反しています。静的メソッドに注意してください&#8211;追加するにはユーティリティクラスが必要な場合があります。
詳細:
http://centraladvisor.com/it/oop-what -are-the-the-best-practices-in-oop
おそらくこれには継承を使用したくないでしょう。代わりにこれを試してください:
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; }
}