大規模な密結合クラスを分割する方法は?


14

私は、2k行を超えるコード(および成長中)の巨大なクラスをいくつか持っており、可能であればリファクタリングして、より軽くてクリーンなデザインにしたいと考えています。

それが非常に大きい理由は、主にこれらのクラスがほとんどのメソッドがアクセスする必要があるマップのセットを処理し、メソッドが互いに非常に接続されているためです。

非常に具体的な例を挙げServerます。着信メッセージを処理するというクラスがあります。それはのようなメソッドを持っているjoinChatroomsearchUserssendPrivateMessage、これらの方法のなどすべてがマップする操作などuserschatroomsservers、...

チャットルームに関するメッセージを処理するクラス、ユーザーに関するすべてを処理するクラスなどがあればいいかもしれませんが、ここでの主な問題は、ほとんどのメソッドですべてのマップを使用する必要があることです。これらがすべてServerこれらの共通マップに依存しており、メソッドが互いに非常に関連しているため、今のところそれらはすべてクラスに固執している理由です。

クラスChatroomsを作成する必要がありますが、他の各オブジェクトへの参照があります。クラスは、他のすべてのオブジェクトへの参照などを使用して再びユーザーになります。

私は何か間違ったことをしているような気がします。


UserやChatroomのようなクラスを作成する場合、これらのクラスは共通のデータ構造への参照のみを必要としますか、それとも相互に参照しますか?

ここにはいくつかの満足できる答えがありますので、1つを選択する必要があります。
jeremyjjbrown 14年

@jeremyjjbrown質問は移動され、私はそれを失いました。答えを選んだ、thx。
マシュー

回答:


10

あなたの説明から、あなたの地図は純粋にデータの袋であり、Serverメソッドのすべてのロジックを持っていると思います。すべてのチャットルームロジックを別のクラスにプッシュすることにより、データを含むマップに固執しています。

代わりに、個々のチャットルーム、ユーザーなどをオブジェクトとしてモデル化してください。そうすれば、データの巨大なマップではなく、特定のメソッドに必要な特定のオブジェクトのみを渡すことになります。

例えば:

public class User {
  private String name;
  ...

  public void sendMessage(String message) {
    ...
  }
}

public class Chatroom {
  // users in this chatroom
  private Collection<User> users;

  public void add(User user) {
    users.add(user);
  }

  public void sendMessage(String msg) {
    for (User user : users)
      user.sendMessage(msg);
  }
}

public class Server {
  // all users on the server
  private Collection<User> users;

  // all chatrooms on the server
  private Collection<Chatroom> chatrooms;

  /* methods to handle incoming messages */
}

メッセージを処理するためのいくつかの特定のメソッドを呼び出すのは簡単です:

チャットルームに参加したいですか?

chatroom.add(user);

プライベートメッセージを送信したいですか?

user.sendMessage(msg);

公開メッセージを送信したいですか?

chatroom.sendMessage(msg);

5

各コレクションを保持するクラスを作成できるはずです。一方では、Serverそれが唯一のアクセスを必要とするか、根本的なコレクションを維持しませんロジックの最小量を必要とするこれらのコレクションのそれぞれへの参照が必要になります。これにより、サーバーが何をしているのかがより明確になり、その方法が分離されます。


4

このような大きなクラスを見たとき、そこに出ようとするクラス(またはそれ以上)がしばしばあることがわかりました。このクラスに関連していないと思われるメソッドを知っている場合は、それを静的にします。コンパイラは、このmethdが呼び出す他のメソッドを通知します。Javaは、それらも静的であると主張します。それらを静的にします。この場合も、コンパイラは、呼び出すメソッドを通知します。コンパイルの失敗がなくなるまで、これを何度も繰り返します。次に、大きなクラスに静的メソッドがたくさんあります。これらを新しいクラスに引き出し、メソッドを非静的にすることができます。その後、元の大きなクラスからこの新しいクラスを呼び出すことができます(これにより、行が少なくなります)。

その後、クラスの設計に満足するまでプロセスを繰り返すことができます。

Martin Fowlerの本は非常に良い読み物ですので、この静的なトリックを使用できない場合があるので、これもお勧めします。


1
このMartin Fowler氏の著書の martinfowler.com/books/refactoring.html
Arul

1

あなたのコードの大部分は既存なので、ヘルパークラスを利用してメソッドを移動することをお勧めします。それは簡単なリファクタリングに役立ちます。したがって、サーバークラスにはマップが含まれています。ただし、join(Map chatrooms、String user)、List getUsers(Map chatrooms)、Map getChatrooms(String user)などのメソッドを持つChatroomHelperというヘルパークラスを使用します。

サーバークラスはChatroomHelper、UserHelperなどのインスタンスを保持するため、メソッドを論理ヘルパークラスに移動します。これにより、サーバーのパブリックメソッドをそのまま残すことができるため、呼び出し元を変更する必要はありません。


1

casablancaの洞察力のある答えに追加するには、複数のクラスが何らかのタイプのエンティティ(コレクションへのユーザーの追加、メッセージの処理など)で同じ基本的なことを行う必要がある場合、それらのプロセスも別々に保持する必要があります。

これを行うには複数の方法があります-継承または構成によって。以下のように継承するために、あなたは、例えばハンドルユーザーやメッセージにフィールドや機能を提供する具体的な方法で抽象基底クラスを使用して、エンティティを持つことができますchatroomし、user(または他の)これらのクラスを拡張します。

さまざまな理由で、継承よりも合成を使用することをお勧めします。構成を使用して、さまざまな方法でこれを行うことができます。ユーザーまたはメッセージの処理はクラスの責任の中心的な機能であるため、コンストラクター注入が最も適切であると主張できます。このように、依存関係は透過的であり、必要な機能がなければオブジェクトを作成できません。ユーザーまたはメッセージの処理方法が変更または拡張される可能性がある場合は、戦略パターンのようなものの使用を検討する必要があります。

どちらの場合も、柔軟性のための具体的なクラスではなく、インターフェイスに向けてコーディングしてください。

そうは言っても、そのようなパターンを使用する場合は、複雑さが増すコストを常に考慮してください-必要ない場合は、コーディングしないでください。ユーザー/メッセージの処理方法を変更しない可能性が高い場合は、戦略パターンの構造的な複雑さは必要ありませんが、懸念を分離して繰り返しを避けるために、一般的な機能を離婚する必要がありますそれを使用する具体的なインスタンスから-そして、反対の優先する理由が存在しない場合、そのような処理機能のユーザー(チャットルーム、ユーザー)を処理を行うオブジェクトで構成します。

だから、要約すると:

  1. casablancaが書いたように:チャットルーム、ユーザーなどを分離してカプセル化します。
  2. 共通の機能を分離する
  3. 個々の機能を分離して、データ表現(およびアクセスと突然変異)を離婚させることを、データまたはその集合体の個々のインスタンス上のより複雑な機能(たとえば、searchUsersコレクションクラス、またはリポジトリ/アイデンティティマップに行くことができるもの)から分離することを検討してください)

0

問題の完全な説明が実際にはないので、あなたの質問は一般的すぎて答えることができないと思います。単なる例として、より良いデザインの無益さについてのあなたの懸念の一つに対処することができます。

サーバークラスと将来のチャットルームクラスはユーザーに関するデータを共有すると言いますが、このデータは異なるはずです。サーバーにはおそらくユーザーのセットが接続されていますが、サーバーに属するチャットルームには、特定のチャットルームに現在ログインしているユーザーのみの異なるユーザーのセット、最初のセットのサブセットがあります。

データ型が同一であっても、これは同じ情報ではありません。
優れた設計には多くの利点があります。

前述のFowlerによる本はまだ読んでいませんが、Folwerによる他の本を読んだことがあり、信頼できる人から勧められたので、他の人と同意するのに十分快適です。


0

マップにアクセスする必要性は、メガクラスを正当化するものではありません。いくつかのクラスでロジックを分離する必要があり、各クラスにはgetMapメソッドが必要であるため、他のクラスはマップにアクセスできます。


0

他の場所で提供したのと同じ答えを使用ます。モノリシッククラスを取り、その責任を他のクラスに分けます。DCIと訪問者パターンの両方は、そうするための良いオプションを提供します。


-1

ソフトウェアメトリックの観点から見ると、大きなクラスはカバンです。この声明を証明する無制限の論文があります。何故ですか ?なぜなら、大きなクラスは小さなクラスよりも理解が難しく、修正するのにより多くの時間が必要だからです。さらに、テストを行う場合、Bigクラスは非常に困難です。また、大きなクラスは、望ましくないものが含まれている可能性が高いため、再利用する場合は非常に困難です。

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