web-dev-qa-db-ja.com

サブパッケージで定義されたインターフェースの実装はアンチパターンですか?

私が以下を持っているとしましょう:

package me.my.pkg;
public interface Something {
    /* ... couple of methods go here ... */
}

そして:

package me.my;
import me.my.pkg.Something;
public class SomeClass implements Something {
    /* ... implementation of Something goes here ... */
    /* ... some more method implementations go here too ... */
}

つまり、インターフェイスを実装するクラスは、実装するインターフェイスよりもパッケージ階層ルートの近くにありますが、どちらも同じパッケージ階層に属しています。

私が念頭に置いている特定のケースでこれが発生する理由は、Somethingインターフェイスが論理的に属している機能と論理的な(両方の「期待する」および「現在のアーキテクチャを前提として必要な場所」)実装クラスは以前から存在し、インターフェースの論理的な配置から1レベル「上」に存在します。 実装クラスは論理的にme.my.pkgのどこにも属していません。

私の特定のケースでは、問題のクラスはいくつかのインターフェースを実装していますが、それはここでは何の違いもない(または少なくとも重要な違いはない)ように感じます。

これが許容可能なパターンであるかどうかを判断できません。それがそうであるか、そうでないか、そしてなぜですか?

9
a CVn

はい、クラスとインターフェースの両方が正しいパッケージに確実に含まれている限り、問題ありません。

クラスをパッケージの階層セットに編成することは、クラスを継承階層に編成することに少し似ています。ほとんどの場合、問題なく機能しますが、適切でないケースがたくさんあります。

Javaには実際にはパッケージ階層さえありません-階層的な性質はフォルダー構造よりも拡張されないことに注意してください。 Java=言語デザイナーの代弁者になることはできませんが、彼らがこの決定を誤って行ったのではないかと思います!

プログラマーが探しているパッケージを見つけやすくするための補助機能として、「パッケージ階層」を考えると役立つ場合があります。

6
vaughandroid

正式には合法ですが、これは コードの匂い を示している可能性があります。これがあなたのケースでそうであるかどうかを確認するには、3つの質問を自分自身に尋ねてください:

  1. 必要ですか?
  2. なぜそのようにパッケージ化したのですか?
  3. パッケージAPIが使いやすいかどうかはどのようにしてわかりますか?

必要ですか?

コードに余分な労力を費やす理由が見当たらない場合は、 maintainability のままにしておくことを検討してください。このアプローチは YAGNI原則 に基づいています。

プログラマは、必要と思われるまで機能を追加してはなりません...「実際に必要なときは常に実装し、必要なときだけは実装しないでください。」

以下の考慮事項は、さらなる分析への投資努力が正当化されることを前提としています。たとえば、長期的にコードが維持されることを期待している、または保守可能なコードを書くスキルの練習と磨きに興味があるとします。これが当てはまらない場合は、残りをスキップして構いません。これは必要ないからです

なぜそのようにパッケージ化したのですか?

注目に値する最初のことは、公式の コーディング規約tutorial のどちらもそれに関するガイダンスを提供しないことであり、この種の決定はプログラマの裁量によると予想されることを強く示します。

しかし、 [〜#〜] jdk [〜#〜] の開発者によって実装された設計を見ると、サブパッケージAPIをより高いレベルのものに「漏らす」ことはそこで行われていないことに気付くでしょう。例として、 concurrent util package とそのサブパッケージ- locksatomic をご覧ください。

  • サブパッケージAPIinsideの使用は問題ないと見なされることに注意してください。たとえば ConcurrentHashMap実装 ロックをインポートし、ReentrantLockを使用します。ロックAPIをパブリックとして公開しないだけです。

さて、コアAPI開発者はそれを避けているようで、なぜそうなのかを理解するために、なぜyouがそのようにパッケージ化したのですか?その自然な理由は、ある種の階層、つまり layers のような種類のパッケージユーザーと通信して、サブパッケージがより低いレベル/より専門的/より狭い使用法の概念に対応するようにすることです。

これは多くの場合、APIをより使いやすく理解しやすくするために、APIユーザーが特定のレイヤー内で操作できるようにするために、他のレイヤーに属する問題に煩わされるのを避けるために行われます。この場合、サブパッケージから低レベルのAPIを公開すると、意図に反することになります。

  • 余談ですが、なぜこのようにパッケージ化するのかわからない場合は、設計に戻って、理解するまで徹底的に分析することを検討してください。考えてみてください。たとえあなたにさえ説明するのが難しいとしたら、今でも、将来的にパッケージを保守して使用する人にとってどれほど難しいでしょうか?

パッケージAPIが使いやすいかどうかはどのようにしてわかりますか?

そのため、パッケージから提供される APIを実行するテストを作成する を強くお勧めします。誰かに review を依頼しても問題はありませんが、経験豊富なAPIレビューアーは、とにかくそのようなテストを提供するように依頼します。実は、APIをレビューするとき、実際の使用例を見るのに勝るものはありません。

  • 単一のパラメーターと夢を持つ単一のメソッドでクラスを見ることができますすごく単純ですが、それを呼び出すテストで他の10個のオブジェクトの作成が必要な場合は、合計50のパラメーターを持つ20のメソッドのように、これは危険な錯覚を打ち破り、設計の真の複雑さを明らかにします。

テストを行うことのもう1つの利点は、設計を改善するために検討するさまざまなアイデアの評価を大幅に簡略化できることです。

比較的マイナーな実装の変更により、テストが大幅に簡素化され、インポート、オブジェクト、メソッドの呼び出しとパラメーターの量が減少することが時々ありました。テストを実行する前でも、これはさらに追求する価値のある方法の良い指標となります。

反対も私に起こりました-つまり、APIを簡略化することを目的とした私の実装の大規模で野心的な変更が、テストへのそれぞれのマイナーで取るに足らない影響によって間違っていることが証明されたとき、無益なアイデアを削除してコードをそのままにしておくのに役立ちます(おそらく単なるコメント付き)デザインの背景を理解し、他の人が間違った方法について学ぶのを助けるために追加されました)。


具体的なケースでは、最初に頭に浮かぶのは 継承から構成 の変更を検討することです。つまり、SomeClassSomethingを直接実装させる代わりに、オブジェクトを含めますそのインターフェースを実装するinsideクラス、

package me.my;
import me.my.pkg.Something;
public class SomeClass {
    private Something something = new Something() {
        /* ... implementation of Something goes here ... */
    }
    /* ... some more method implementations go here too ... */
}

このアプローチは将来的に見返りをもたらす可能性があり、SomeClassの一部のサブクラスが誤ってSomethingのメソッドをオーバーライドして、予期しない方法で動作するリスクを防ぎます。

3
gnat