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


9

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

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つの現在のアーキテクチャを前提にして進む必要がある場合」)実装クラスは以前から存在し、インターフェースの論理的な配置から「1つ上」に存在します。実装クラスは、論理的にme.my.pkgのどこにも属していません。

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

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

回答:


6

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

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

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

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


3

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

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

必要ですか?

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

プログラマーは、必要と思われるまで機能を追加すべきではありません...「実際にそれらが必要なときに常に実装し、必要があると予見しているときは決して実装しないでください。」

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

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

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

しかし、JDKの開発者によって実装された設計を見ると、サブパッケージAPIをより高いレベルのAPIに「漏らす」ことが実践されていないことに気付くでしょう。例として、並行utilパッケージとそのサブパッケージ-locksatomicを見てください

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

さて、コアAPI開発者はそれを避けているようで、なぜそれがそうであるかを知るために、なぜあなたはそれをそのようにパッケージ化したのでしょうか?そのための自然な理由は、ユーザーがソートの階層のいくつかの並べ替え、パッケージと通信する願望であろう層をサブパッケージ相当レベル/より特化/狭い使用概念を低下させるように、。

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

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

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

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

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

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

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

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


具体的なケースでは、最初に頭に浮かぶのは、継承をコンポジションに変更することを検討することです。つまり、をSomeClass直接実装する代わりに、クラス内にSomethingそのインターフェース実装するオブジェクトを含めます。

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、計画外の方法で動作するようにします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.