小さなアプリの設計中に、どちらも機能する2つの可能な設計案ができましたが、次の点で、どちらかが他の方法よりも優れていると感じていますOOP原則、コードを回避するにおいなど.
ここでのシナリオは、次のように、親クラスにオブジェクトが埋め込まれている場合です。
_class Balance {
private amount: number;
}
class Wallet {
private balance: Balance; // the embedded 'Balance' object
}
_
そして、私の主な関心事は、埋め込まれたBalance
オブジェクトを含む操作を管理する方法です。これはWallet
インターフェイスを介して実行するか、Balance
インスタンスに直接アクセスして実行する必要があります(たとえば、getter関数を介して)。
最初の選択肢:
残高はWallet
クラスインターフェースを介して管理されます。
_class Balance {
private amount: number;
// constructor
public getAmount(): number {
return this.amount;
}
public setAmount(amount: number): void {
this.amount = amount;
}
}
class Wallet {
private balance: Balance;
// constructor
public add(amount): void {
if (amount > 0) {
this.balance.setAmount(amount);
}
}
public remove(amount): void {
const total = this.balance.getAmount();
if (total >= amount) {
this.balance.setAmount(total - amount);
}
}
public getBalance(): number {
return this.balance.getAmount();
}
}
_
使用法:
_const wallet: Wallet = new Wallet(0);
wallet.add(12);
wallet.remove(10);
const total: number = wallet.getBalance(); // total = 2
_
2番目の選択肢:
天びんは、独自のクラスインターフェイスを介して直接管理されます(最初にWallet
クラスのgetter関数を使用して天びんにアクセスする必要があります:wallet.getBalance()
)。
_class Balance {
private amount: number;
// constructor
public getAmount(): number {
return this.amount;
}
public addAmount(amount: number): void {
if (amount > 0) {
this.amount = this.amount + amount;
}
}
public removeAmount(amount: number): void {
if (amount >= this.amount) {
this.amount = this.amount - amount;
}
}
}
class Wallet {
private balance: Balance;
// constructor
public getBalance(): Balance {
return this.balance;
}
}
_
使用法:
_const wallet: Wallet = new Wallet(0);
wallet.getBalance().addAmount(12);
const walletBalance: Balance = wallet.getBalance();
walletBalance.removeAmount(10);
const total: number = wallet.getBalance().getAmount(); // total = 2
_
考え:
さて、ここでの最初の選択肢が進むべき道であることは明らかです:Wallet
は内部に存在するものをかなりうまくカプセル化します(バランスの詳細はこのクラスの背後に隠されています)、2番目の選択肢と比較しても、使用法はよりクリーンで簡単に見えます。
しかし、このような1つ以上のオブジェクトをオブジェクトに埋め込んでしまうような場合に、この種のシナリオに取り組む方法について少し混乱する傾向があります。埋め込まれたオブジェクトのいずれかでいくつかのアクションを実行できます。これはどのように管理する必要がありますか?ホルダーのインターフェースを介してそれを行う必要がありますか?オブジェクトインスタンスに直接?
私がクールではないと思うのは、間違いなく次のようなものになることです。
_class B {
foo();
bar();
baz();
}
class A {
b: B;
boo() {
this.b.foo();
}
Zoo() {
this.b.bar();
this.b.baz();
}
}
const a = new A();
a.boo();
a.Zoo();
_
その結果、機能の羨望コードの臭いが発生します...
この種のシナリオに取り組むために不可欠だと思われる設計上のアドバイス、コメント、ヒントなどはありますか?
実際、その最後の例は正当です。そのおそらくアダプター。コマンド、ファサード、または...でもかまいません。
別の実装をカプセル化することが理にかなっている場合があります。といった:
B
のコピーを作成し、不変の操作のみを公開/サポートできます。これを処理するために何が必要ですか? 目標。
真剣に何を目指していますか?
Wallet
は古いBalance
指向のコードで動作する必要がありますか?Wallet
を使用せずにBalance
を実装することには意味がありますか?最適な設計は1つではなく、トレードオフだけです。トレードオフは後で役立つ場合がありますが、後で妨げられることがよくありますが、すべて目標に到達するのに役立ちます。
したがって、最初の要件は次のとおりです。目標は何ですか。
そうは言っても、物事がどのように移動できるかを理解し、意図を説明するときに実装を指示しないことは助けになります。
いくつかの用語で明確にするために:
インターフェイスは、オブジェクトに対する操作を説明するコントラクトです。操作の入力、出力、およびオブジェクトの状態への影響を含みます。インターフェースは、返されたオブジェクトの操作、さらにはグローバル操作またはグローバル状態にまで拡張できます。それらはシステムの意図と考えることができます。
実装は、ある種のデータ構造と、1つ以上のインターフェースをサポートするそのデータ構造に対する操作を記述します。
Wallet
の実装を検討してください。内部状態を完全に制御する必要があります。そうでなければ、それは最も些細な契約のみをサポートすることができます:何かが起こる可能性があります...。
Wallet
インターフェースがBalance
が返されることを示している場合、Wallet
実装は制御を維持できます。
Balance
が不変の場合。制御は主にプラットフォームによって実施されます。 Balance
へのサードパーティアクセスは変更できないため、インターフェースを介して公開しても安全です。
Balance
を簡単に実装できる場合、Wallet
はBalance
ではなくWalletBalance
を返します。このように、操作が発生したときに、Wallet
の他の部分を適切に更新することにより、Wallet
のコントラクトを維持できます。
Balance
が簡単に作成できる場合、Wallet
は必要に応じて簡単に作成できます。これは、不変の状態、または回答として返される可変オブジェクトには問題ありません。ただし、Balance
に対する操作がWallet
に影響を与えることを意図している場合、Balance
がその状態を維持できるようにするには、Wallet
が何らかの形式のコールバックをサポートする必要があります。 。
Balance
がコピーしやすい場合は、コピーを作成してそれを返すことにより、制御を維持できます。これは、不変の状態、または回答として返される可変オブジェクトには問題ありません。ただし、Balance
に対する操作がWallet
に影響を与えることを意図している場合、Balance
がその状態を維持できるようにするには、Wallet
が何らかの形式のコールバックをサポートする必要があります。 。
そうしないと、制御を維持し、Balance
(不変、拡張、構築、またはコピー)の実装を公開することが難しくなります。この場合、実際に要件を実装するのは困難/不可能であるため、インターフェースは設計上かなり壊れている可能性があります。
おそらく、インターフェースがBalanceLite
を公開する可能性がありますか?不変、拡張可能、構築可能、またはコピー可能であるという上記のプロパティの1つが必要になります。
BalanceLite
に拡張された不変のBalance
は、内部状態の一部である場合、直接返されるべきではないということです。通常、サードパーティのコードがそれをキャストしてBalance
インターフェイスに戻し、bad things tmを実行する可能性があるためです。おそらく、インターフェースはBalance
インターフェースを完全に放棄する必要があります。その後、他の操作を通じて十分な能力を獲得する必要があります。
Wallet
コントラクトを制約して、不適切な可能性のあるBalance
インターフェイスを組み込む必要がないため、これが最善の解決策になる可能性があります。Balance
が必要な場合は、最後の例を使用してWallet
をラップすることで提供できます。