9

私が何度か遭遇したことの 1 つは、ヘルパーの内部クラスが原因で過度に大きくなったサービス クラス (JBoss サービスなど) です。クラスを分割する良い方法をまだ見つけていません。これらのヘルパーは通常スレッドです。次に例を示します。

/** Asset service keeps track of the metadata about assets that live on other
 * systems. Complications include the fact the assets have a lifecycle and their
 * physical representation lives on other systems that have to be polled to find
 * out if the Asset is still there. */
public class AssetService
{
  //...various private variables
  //...various methods

  public AssetService()
  {
    Job pollerJob = jobService.schedule( new AssetPoller() );
    Job lifeCycleJob = jobService.schedule( AssetLifecycleMonitor() );
  }

  class AssetPoller
  {
    public void run()
    { 
      // contact remote systems and update this service's private variables that
      // track the assets.
    }
  }

  class AssetLifecycleMonitor
  {
    public void run()
    {
      // look for assets that have meet criteria for a lifecycle shift
      // and update this service's private variables as relevant.
    }
  }
}

したがって、いくつかのヘルパーがあり、それらがまったく複雑な場合、クラス ファイル全体が非常に大きくなる可能性があります。クラスがサービスによって完全に所有されており、そのサービスを支援するためだけに存在することを明確にするという点で、私は内部クラスが好きです。クラスを分割して、親サービスを参照として渡すことを試みましたが、これはほとんど機能しますが、気に入らない点は次のとおりです。

  • パッケージ レベルのアクセサーを公開して、分割されたクラスが変数にアクセスできるようにすることになりますが、以前は、内部クラスが直接アクセスできるため、セッターをまったく公開していませんでした。
  • さらに、基になる変数ではなくアクセサーを常に呼び出しているため、話が少し長くなります。マイナーニット、付与。
  • 便利なメソッド (checkAssetIsValid() など) はパッケージ レベルで公開する必要があるため、ヘルパー クラスが呼び出せるようになりました。
  • さらに悪いことに、サービス実装クラスをヘルパー クラス コンストラクターに渡す必要があります。これは、サービスが実装するインターフェイスでこれらのヘルパー メソッドを公開したくないためです。これにより、単体テスト/モックの問題が発生する可能性があります。
  • さらに悪いことに、私がやりたかった同期は、外部の便利なメソッド (ポーラーの更新中の lockDownAssets() など) を介してリークされます。以前は、内部クラスはプライベート ロックにアクセスできました。

    要するに、クラスを分割すると、私が好むカプセル化の一部が失われます。ただし、それらをそのままにしておくと、いくつかの大きな Java ファイルが作成される可能性があります。私はまだこれに対処する良い方法を見つけていません。C++ には「友達」という概念がありましたが、私はこれを見逃したことはめったにありませんが、この場合は実際に役に立ちます。

    考え?

  • 4

    5 に答える 5

    7

    バイトコード レベルでは、内部クラスは単純な Java クラスです。Java バイトコード ベリファイアはプライベート メンバーへのアクセスを許可しないため、使用するプライベート フィールドごとに合成アクセサ メソッドを生成します。また、内部クラスをその外側のインスタンスにリンクするために、コンパイラは合成ポインタを外部の「this」に追加します。

    これを考慮すると、内部クラスは構文シュガーの層にすぎません。それらは便利であり、いくつかの良い点を挙げているので、考慮したいいくつかの否定的な側面を挙げます。

    • 内部クラスには、親クラス全体への隠れた依存関係があり、そのインバウンド インターフェイスが難読化されています。これをパッケージ プライベート クラスとして抽出すると、設計を改善して保守性を高めることができます。最初はもっと冗長ですが、多くの場合、次のことがわかります。
      • 10 個のアクセサーを公開する代わりに、実際には単一の値オブジェクトを共有したいと考えています。多くの場合、外部クラス全体への参照が実際には必要ないことに気付くでしょう。これは IoC でもうまく機能します。
      • 明示的なロックのためのメソッドを提供する代わりに、操作をそのコンテキストとともに別のクラスにカプセル化する (またはそれを 2 つのクラス (外部または以前の内部) のいずれかに移動する) 方が保守性が高くなります。
      • コンビニエンス メソッドは、パッケージのプライベート ユーティリティ クラスに属します。Java5 静的インポートを使用して、それらをローカルとして表示させることができます。
    • 外部クラスは保護レベルをバイパスし、内部クラスのプライベート メンバーに直接アクセスできます。これ自体は悪いことではありませんが、デザインを表現するための言語手段の 1 つを奪ってしまいます。
    • 内部クラスは厳密に 1 つの外部クラスに埋め込まれているため、それを再利用する唯一の方法は、外部クラスをサブクラス化することです。別の方法は、外部クラスが実装するパッケージ プライベート インターフェイスへの明示的な参照を渡すことです。これにより、外側のクラスをモックし、内側のクラスをより適切にテストできます。
    • 最近のデバッガーは非常に優れていますが、以前に内部クラスのデバッグで問題が発生したことがあります (条件付きブレークポイントのスコープの混乱、ブレークポイントで停止しないなど)。
    • プライベート クラスはバイトコードを肥大化させます。私の最初の段落を参照してください - 多くの場合、使用して合成クラフトの数を減らすことができる API があります。

    PS私は重要な内部クラス(特にインターフェースを実装していないクラス)について話している. 3 行のリスナーの実装は適切です。

    于 2008-10-20T23:36:10.290 に答える
    3

    大規模なクラスを分割しようとしている理由を考慮することを忘れないでください。ソフトウェアエンジニアリングの目的ですか?たとえば、プログラミングのホットスポットであり、開発チームで複雑なマージを引き起こすほど大きなファイルがありますか?

    大規模なクラスを避けたいというのは、一般的な欲求ですか?その場合、あなたの時間はあなたが持っているコードの改善に費やすほうがよいかもしれません.

    コードの管理が難しくなっていますか。たとえば、デバッグや、意図しない副作用の回避がますます難しくなっています。

    単体テストを使用して継続的な一貫した動作を保証することについての Rick のコメントは非常に価値があり、良い考えです。現在の設計ではリファクタリングが単に妨げられている可能性があり、元のインターフェイスから同じ動作を再実装する方がよいでしょう。十分な回帰テストの準備をしてください!

    于 2008-10-20T20:00:55.340 に答える
    3

    カプセル化と分離の間の線引きは難しい場合があります。ただし、ここでの主な問題は、クラスを分離するための基礎として使用する、ある種の堅固な対話モデルが必要だということだと思います。

    問題が発生しない限り、多くの場所で使用される外部ヘルパー ユーティリティ クラスを使用することは合理的だと思います。checkAssetIsValid() などの頻繁に使用されるメソッドを含む、適切に編成されている限り、静的ヘルパー クラスを使用することも合理的です。これは、checkAssetIsValid が、渡すオブジェクト以外の外部状態にアクセスする必要がないことを前提としています。

    分離のために最も重要なことは、これらのクラスの多くで永続的な参照を共有するオブジェクトを持たないことです。ガイダンスとして関数型プログラミングに目を向けるのが好きです。各クラスは、他のクラスの内臓に手を伸ばして状態を変更するべきではありません。代わりに、機能する各クラスがコンテナ オブジェクトを生成および消費する必要があります。

    視覚化も非常に役立ちます。Java 可視化ツールのトピックに関するスレッドがここにあることに気付きました。理想的には、クラス相互作用図はグラフよりもツリーに似ている必要があります。

    また、大きなクラスを小さなクラスにリファクタリングすることは非常に難しい場合があることに注意してください。何かを壊した場合にすぐに明らかになるように、少なくともパブリック インターフェイスの一連の単体テストを作成することをお勧めします。テストのおかげで、過去に数え切れないほどの時間を節約できました。

    うまくいけば、これのいくつかは役に立ちます。私はここでとりとめのないものです。

    于 2008-10-20T19:54:17.303 に答える
    1

    私は内部クラスの過度の使用のファンではありません。コードを通常のクラスに配置しても得られない利点は (極端に使用された場合) 実際には提供されず、クラス ファイルが不必要に大きくなり、追跡が困難になるだけだと思います。

    いくつかのメソッドの可視性を高めなければならない場合、どのような害がありますか? 抽象化やインターフェースを完全に壊してしまうのでしょうか? プログラマーは、デフォルトですべてを非公開にする傾向があると思います。他のクラスにメソッドを呼び出させてもそれほど害はありません。つまり、設計が本当にオブジェクト指向ベースである場合です。

    すべての「内部ヘルパー クラス」が同じメソッドの一部にアクセスする必要がある場合は、それらを基本クラスに配置して、継承によって共有できるようにすることを検討してください。

    于 2008-10-20T20:58:40.433 に答える
    0

    うん。おそらく、これらのヘルパーをすべてそのまま移動するのではなく、再リファクタリングする必要があります。サービスに属するものと、ヘルパーに属するものがあります。おそらく新しいクラスを使用してデータをカプセル化する必要があります。

    使用できる 1 つの可能性は、AOP を使用してきめ細かいアクセスを提供し、メソッドが「フレンド」クラスからのみ呼び出されるようにすることをポイント カットに含めることです。それでもあなたの方法は公開されます:(

    これには簡単な解決策はないと思います。

    于 2008-10-20T19:48:04.697 に答える