私はいくつかのJavaコードの実践を行って、単一責任の原則をよりよく理解しています。現在、データ構造のネットワークを使用しています。ここで、各構造はLocationというインターフェイスを実装しています。
public interface Location {
void removeObject(Object o);
void addObject(Object o);
}
場所によってオブジェクトの保存と取得方法が異なるため、オブジェクトを追加/削除するには、それぞれ独自のメソッドが必要です。目標は、オブジェクトをロケーション間で自由に移動し、オブジェクトが現在どこにあるかを調べることができるようにすることです。オブジェクトを移動するために呼び出される2つのクラスがあります。1つはオブジェクトの実際の移動(ムーバー)のため、もう1つはオブジェクトの場所を追跡してムーバークラス(フェッチャー)にフェッチできるようにするためです。
public class Database {
private Mover mover;
private Tracker tracker;
//Contains methods for moving specific Objects to specific Locations.
}
public class Mover {
void moveObject(Object o, Location a, Location b)
{
a.removeObject(o);
b.addObject(o);
}
}
public class Tracker {
Map<Object, Location> trackerMap;
void updateTracker(Object o, Location a, Location b)
{
trackerMap.remove(a);
trackerMap.put(b, o);
}
Object lookupObject(Object o)
{
return trackerMap.get(o);
}
}
しかし、私はこのデザインについていくつかの懸念があります。ムーバーとトラッカーは別々のクラスに分割されているため、データ構造は基本的に2回格納されます。トラッカーのマップによって、およびムーバーが変更している実際のデータ構造によって。エラーが発生しやすいようです。何らかの理由で2つが同期しなくなった場合、Moverは存在しないものを移動しようとする可能性があり、トラッカーはそれらを見つけられない可能性があります。ムーバーは、オブジェクトが移動するときにトラッカーのマップを直接変更して、それらが常に同期していることを保証する必要があるという感覚を覚えています。ただし、ムーバーがトラッカーの変更を開始する場合、トラッカーは独自のクラスである必要はありません。そして、私が理解している限り、2つを組み合わせるとSRPに違反します。
SRPを維持しながら、このデータベース設計をどのように改善できますか?
システムが環境とどのように相互作用するかについて考える必要があります。それはあなたがあなたのことを考える必要があるということです 範囲境界 API。開発者はこれをどのように使用することになっていますか?
まず第一に、Database
を使用するコードは、オブジェクトがどこにあるかを追跡する責任がありますか?いいえ、そうではありません。したがって、Database
には、どこにあるかに関係なく、Location
にオブジェクトを配置できるAPIが必要です。
次に、Database
はオブジェクトを見つけ、必要に応じて移動する必要があります。そのため、Database
にはTracker
が必要です。
ちょっと待ってください、lookupObject
APIは間違っているようです。 Location
がわかりません。見つけたいです。オブジェクトを受け取り、Location
を取得する必要があります。
わかりました、Tracker
の目的は何ですか?オブジェクトがLocation
であるかを知ることです。それは同じことではありません。反対方向への誘導性です。 Tracker
はMap<Object, Location>
のように機能します(実際、これがTracker
にラップされます)。
では、Database
に戻りましょう。 Database
はオブジェクトを見つけ、必要に応じて移動する必要があります。そのため、Database
にはTracker
が必要です。 Tracker
は、Database
に、オブジェクトがどこにあるかを示します。
オブジェクトは次のとおりです。
addObject
を呼び出すだけです)。Location
(移動する必要があります。つまり、removeObject
とaddObject
を呼び出します)。したがって、Mover
インターフェースは間違っています。データベースはaddObject
を単独で呼び出す必要がある場合がありますが、Mover
ではこれを許可していません。
Mover
は必要ないと思います。 Database
はLocation
を直接処理できます。
そしてもちろん、Database
はTracker
を更新する必要があります。
だから私たちは:
Location
、オブジェクトを追加、削除できます。 (それはおそらくそれが持っているオブジェクトを知っています)。 Set
です。Tracker
、それはオブジェクトがLocation
で何であるかを知っています。 Map<Object, Location>
です。Database
、オブジェクトの場所を設定できます。 内部的にTracker
を使用しますが、使用することを知っている必要はありません。Tracker
には明確な責任があります。それも良いことです。間違いありません。
Location
の実装は、システムの外部にある可能性があります。それらの実装が変更された場合、それを使用するコード(Database
)を変更する必要はありません。
Location
がDatabase
を変更する理由にはなりません。何らかの理由でLocation
の実装が変更された場合、最悪の場合、「場所」インターフェースへのアダプターが必要になります。それでも、Database
は変更されませんでした。実際、Location
インターフェースは、Database
から何か他のものが必要な場合に変更される可能性があります。
失敗する条件について心配する必要があります。設計どおり、Location
インターフェースは、失敗しないように装っています。
最後に、問題全体は次のとおりです。Database
は2つのことを行う必要があります。
Location
に話しかけるTracker
に話しかける上記を読むと、「場所」がDatabase
を変更する理由ではないことがわかります。したがって、これはSRPを壊すものではありません。
ああ、待って、ひねりがあります。 Database
はどのようにTracker
と通信しますか。基本的には、「場所」と同じことを行う必要があります。 操作を何らかの形でミラー化する必要があります。共通のインターフェースがあれば簡単になります。
仮に、Mover
の概念を復活させます。オブジェクトを追加したい場合、古いLocation
はnullであるとしましょう。 コードにnullチェックを追加する必要があります。
実際、同じAPIを使用して削除することができます。 これについては話しませんでした。この場合、新しいLocation
はnullになる可能性があります。
まあ、まったく同じAPIがTracker
にも役立ちます。実際、updateTracker
とmoveObject
の署名は同じであることに注意してください。そのシグネチャを持つメソッドを公開するインターフェイスを作成できます...
現在、Database
は、そのインターフェースを実装するオブジェクトのリストと通信しています。
もちろん、Tracker
は特別です。なぜなら、このおもちゃシステムの場合、オブジェクトがどこにあるかについての唯一の真の情報源だからです。
ちなみに、オブジェクトをファイルに永続化するLocation
の実装がある場合、実行の開始時にLocation
にはすでにオブジェクトがあります。それに応じてTracker
を設定する必要があります。つまり、どのオブジェクトがあるかを知ることはLocation
の懸念事項です。これは、Location
がオブジェクトのリストを提供することを意味します。これは、Location
がオブジェクトの作成を担当することになります...これは、設計にレンチを投げます。もう1つは、ファイルにアクセスする複数のプロセスからのものです。それは問題を超えているので、そこに行かないでください。しかし、これは、システムが環境とどのように相互作用するかについて考える必要があるということ、つまり、 インターフェース境界 システムの範囲。