Java でのコレクション クラスの継承とカプセル化
-
21-08-2019 - |
質問
次の種類のデータがあるとします。
class Customer {
String id; // unique
OtherCustData someOtherData;
}
class Service {
String url; // unique
OtherServiceData someOtherData;
}
class LastConnection {
Date date;
OtherConnData someOtherData; // like request or response
}
次に、各顧客がいつ各サービスに接続したかを思い出す必要があります。
私なら次のような構造を作ります。
Map<Customer, Map<Service, LastConnection>> lastConnections;
または、ID で検索できるようにするには、equal() と hashCode() をすべて記述する必要はありません。
Map<String, Map<String, LastConnection>> lastConnections;
これで、LastConnection データにアクセスできるようになりました。
LastConnection connection = lastConnections.get(custId).get(srvUrl);
これはすべて醜く見えるので、特に LastConnections のマップのマップを期待する数十のメソッドにパラメータとして渡さなければならないので、次のような独自のクラスを作成することを考えています。
class CustomerConnections extends HashMap<String, LastConnection> {
}
class AllConnections extends HashMap<String, CustomerConnections> {
public LastConnection get(String custId, String srvUrl) {
return get(custId).get(srvUrl);
}
}
さて、継承が 3v1l であることはすでに学習したので、合成を試してみましょう。
class CustomerConnections {
Map<String, LastConnection> customerConnections;
LastConnection get(String srvUrl) {
return customerConnections.get(srvUrl);
}
... // all other needed operations;
}
class AllConnections {
Map<String, CustomerConnections> allConnections;
public LastConnection get(String custId, String srvUrl) {
return get(custId).get(srvUrl);
}
public CustomerConnection get(String custId) {
return allConnections.get(custId);
}
... // all other needed operations;
}
問題は、SOLID 原則とすべてのベスト プラクティスを考慮した最善のアプローチが何かわからないことです。既存のコレクションを拡張すること以外は何もしないクラスを作成することは、エンティティを必要以上に増やすように思えますが、コードがより明確になります (特に月ごとの AllConnections のマップなど、次のレベルがある場合)。何か指示はありますか?
解決
何もしないクラスの作成 既存すでに拡張除きます コレクションは、乗算のように思えます 必要以上のエンティティ
私はカプセル化するために、拡張変化するであろう。あなたは、この情報が格納されている方法の詳細を隠しています。あなたのクラスのクライアントは、あなたが顧客の接続履歴とそれらを提供している方法を知っている必要はありません。私は、これはあなたがAPIのクライアントが自分のコードを変更せずに基礎となるモデルを変更することができますので、良いアイデアだと思います。
が、
私のコードがより明確になるだろう
これは素晴らしいですし、これを行うための十分な理由です。 YourClass.getCustomerConnection(CID)がyourCollection.get(ID)に.get(ID).getConnection()よりもはるかに明確です。あなたがその人であっても、簡単にこのコードを使用している人々の生活をする必要があります。
(次のレベルがある場合は特に - ように月別AllConnectionsの地図などが)
良い、あなたは前もって計画し、あなたのコードの拡張性を作っています。これは良いOOの習慣です。私の意見では、あなた自身に達しているconculsionは、私がどうなるのかです。
他のヒント
私は、この情報を格納するための専用のオブジェクトを作成します。あなたが作成していることのマネージャのオブジェクトではなく、シンプルなコレクションです。
私はのあなたは、この情報を保存する方法のセマンティクスは将来変更される可能性があるので、それは、地図または他のよく知られたコレクションクラスから派生することはないだろうを。
の代わりに、顧客との接続を結びつけるクラスを実装し、そのクラスの内側(インターフェイスとあなたのコードの残りの部分に影響を与えることなく、あなたは後で変更すること自由にしていることを)適切なコレクションクラスを使用します。
あなたの顧客/接続マネージャクラスは、単純なコンテナ以上のものです。これは、(例えば、この関係が確立されたときに)メタデータを格納することができます。これは、顧客情報(必要な場合)指定された接続上で検索を実行することができます。それはあなたが簡単に簡単に何が起こっているか理解するために、デバッグ/ログ/パフォーマンス監視のスロットすることができます。
などではなく根本的なコレクションクラスは、など、それらをどのように処理するかよりも、あなたが必要とどのように重複を処理することができますコレクションがどのように使用されるか、データがどのように取得されるかについても考慮する必要があると思います。
- それらが (たとえば) ページに表示される単純な結果セットである場合、標準コレクションを使用するのが合理的であると思われます。その場合、それらを操作するために多くの標準ライブラリを使用できます。
- 一方、それらが変更可能であり、変更を永続化する必要がある場合 (たとえば)、それらを独自のクラス内にカプセル化することをお勧めします (データベースなどに変更を書き込むことができます)。
データをどのように取得して保存するのでしょうか?データベースに保存されている場合は、SQL を使用して、データ構造を自分で管理するのではなく、顧客、サービス、月ごとに LastConnections を選択できる可能性があります (接続または接続数の単純なリストまたはマップを返すだけです)。 )。あるいは、リクエストごとにクエリを実行したくないため、データ構造全体をメモリ内に保持する必要がある場合もあります。
ただし、カプセル化は一般に良いことです。特に、 デメテルの法則 - コレクションに対して実行する操作の一部を AllConnections クラス (実質的には DAO) にプッシュして戻すことができるかもしれません。これは単体テストに役立つことがよくあります。
また、単純なヘルパー メソッドを追加するだけであることを考慮すると、なぜここで HashMap の拡張が悪とみなされるのでしょうか?AllConnections のコードでは、AllConnections は常に HashMap と同じように動作します。つまり、多態的に置換可能です。もちろん、HashMap (TreeMap などではなく) を使用するように制限される可能性がありますが、Map と同じパブリック メソッドがあるため、それはおそらく問題ではありません。ただし、実際にこれを行うかどうかは、コレクションをどのように使用するつもりであるかによって決まります。私は、実装の継承を次のように自動的に割り引くべきではないと思います。 いつも 悪い(通常はそうなのです!)
class AllConnections extends HashMap<String, CustomerConnections> {
public LastConnection get(String custId, String srvUrl) {
return get(custId).get(srvUrl);
}
}
なぜシンプルcustId+"#"+srvurl
でキーとしてMap<String, LastConnection>
を使わないのでしょうか?
またはタプルのか、2つのIDが含まれており、hashCode()
とequals()
を実装キーとしてペアクラスを使用して - 」きれいな」OOソリューションます。
なぜあなたはそれらのオブジェクトの外側のオブジェクト間の関係を維持している。
私は次のようなものをお勧めしたいです
public class Customer
{
public List<LastConnection> getConnectionHistory()
{
...
}
public List<LastConnection> getConnectionHistory(Service service)
{
...
}
public List<LastConnection> getConnectionHistory(Date since)
{
...
}
}
public class LastConnection
{
public Date getConnectionTime()
{
...
}
public Service getService()
{
...
}
}
あなたは、この情報を使用する方法にList<Customer>
を渡します。
は、を実装するクラスのMap<K,V>
を作成し、内部の容器マップに委任することができます:
class CustomerConnections implements Map<String,LastConnection> {
private Map<String, LastConnection> customerConnections;
@Override
public LastConnection get(Object srvUrl) {
return customerConnections.get(srvUrl);
}
// all other needed operations;
}
このアプローチの良いところは、あなたが、標準を持っているMap
の周りに明確に定義された契約書を渡すことができますが、頻繁に眉をひそめているライブラリのクラスを、拡張避けるということです。
編集:下記に指摘したように、これは、基になるコレクション
にデリゲートの短い何もしないメソッドの多くを実装するためにあなたを必要とする欠点を持っています