Как разбить большие, тесно связанные классы?

14

У меня есть несколько огромных классов из более чем 2 тыс. Строк кода (и их число растет), которые я хотел бы реорганизовать, если это возможно, чтобы получить более легкий и чистый дизайн.

Причина, по которой он так велик, заключается главным образом в том, что эти классы обрабатывают набор карт, к которым нужно получить доступ большинству методов, и методы очень связаны друг с другом.

Я приведу очень конкретный пример: у меня есть класс, Serverкоторый обрабатывает входящие сообщения. Он имеет такие методы , как joinChatroom, searchUsers, sendPrivateMessageи т.д. Все эти методы манипулирования картами типа users, chatrooms, servers, ...

Может быть, было бы неплохо, если бы у меня был класс, обрабатывающий сообщения, касающиеся чатов, другой, который обрабатывал бы все о пользователях и т. Д., Но главная проблема здесь в том, что мне нужно использовать все карты в большинстве методов. Вот почему на данный момент все они привязаны к Serverклассу, поскольку все они полагаются на эти общие карты, а методы очень связаны друг с другом.

Мне нужно было бы создать класс чатов, но со ссылкой на каждый из других объектов. Класс снова пользователей со ссылкой на все другие объекты и т. Д.

Я чувствую, что буду делать что-то не так.

Мэтью
источник
Если бы вы создавали классы, такие как User и Chatroom, понадобятся ли этим классам только ссылки на общую структуру данных или они будут ссылаться друг на друга?
Здесь есть несколько удовлетворительных ответов, вы должны выбрать один.
jeremyjjbrown
@jeremyjjbrown вопрос был перенесен, и я потерял его. Выбрал ответ, спасибо.
Мэтью

Ответы:

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

Когда я видел большие классы, подобные этому, я обнаружил, что там часто есть класс (или больше), пытающийся выбраться. Если вам известен метод, который, по вашему мнению, не имеет отношения к этому классу, сделайте его статическим. Затем компилятор расскажет вам о других методах, которые вызывает этот метод. Java будет настаивать на том, что они тоже статичны. Вы делаете их статичными. Опять же, компилятор скажет вам о любом методе, который он вызывает. Вы продолжаете делать это снова и снова, пока у вас не будет больше ошибок компиляции. Тогда у вас есть множество статических методов в вашем большом классе. Теперь вы можете извлечь их в новый класс и сделать метод не статичным. Затем вы можете вызвать этот новый класс из вашего исходного большого класса (который теперь должен содержать меньше строк)

Затем вы можете повторить процесс, пока не будете удовлетворены дизайном класса.

Книга Мартина Фаулера - действительно хорошее чтение, поэтому я бы порекомендовал это, поскольку бывают случаи, когда вы не можете использовать этот статический прием.

RNJ
источник
1
Книга этого Мартина Фаулера martinfowler.com/books/refactoring.html
Arul
1

Поскольку большая часть вашего кода существует, я бы предложил использовать вспомогательные классы для перемещения ваших методов. Это поможет легко рефакторинг. Таким образом, ваш класс сервера будет по-прежнему содержать карты в нем. Но он использует вспомогательный класс, например ChatroomHelper, с такими методами, как join (Карта чатов, String user), List getUsers (Map chatrooms), Map getChatrooms (String user).

Класс сервера будет содержать экземпляр ChatroomHelper, UserHelper и т. Д., Таким образом, перемещая методы в его классы логического помощника. При этом вы можете оставить общедоступные методы на сервере нетронутыми, так что любой вызывающий объект не нуждается в изменении.

техасер сома
источник
1

Чтобы добавить к проницательному ответу Касабланки - если нескольким классам нужно делать одни и те же базовые вещи с каким-либо типом сущности (добавление пользователей в коллекцию, обработка сообщений и т. Д.), Эти процессы также должны быть разделены.

Есть несколько способов сделать это - по наследству или по составу. Для наследования вы можете использовать абстрактные базовые классы с конкретными методами, которые предоставляют поля и функциональные возможности, например, для обработки пользователей или сообщений, и имеют такие объекты, как chatroomи user(или любые другие), расширяющие эти классы.

По разным причинам , это хорошее общее правило - использовать композицию вместо наследования. Вы можете использовать композицию, чтобы сделать это различными способами. Поскольку обработка пользователей или сообщений является функциями, отвечающими за ответственность классов, можно утверждать, что внедрение в конструктор является наиболее подходящим. Таким образом, зависимость прозрачна, и объект не может быть создан без необходимой функциональности. Если способ обработки пользователей или сообщений, вероятно, изменится или будет расширен, вам следует подумать об использовании чего-то вроде шаблона стратегии .

В обоих случаях обязательно используйте код для интерфейсов, а не для конкретных классов для обеспечения гибкости.

При этом всегда учитывайте стоимость дополнительной сложности при использовании таких шаблонов - если она вам не понадобится, не кодируйте ее. Если вы знаете, что вы, скорее всего, не собираетесь менять способ обработки пользователей / сообщений, вам не нужна структурная сложность шаблона стратегии - но для того, чтобы разделить проблемы и избежать повторений, вам все равно следует развести общую функциональность из конкретных экземпляров, которые его используют, и, если нет основополагающих причин для обратного, составьте пользователей такой функциональности обработки (чаты, пользователи) с объектом, который выполняет обработку.

Итак, подведем итог:

  1. Как писал Касабланка: Разделяйте и инкапсулируйте чаты, пользователей и т. Д.
  2. Отдельная общая функциональность
  3. Подумайте о том, чтобы отделить отдельные функциональные возможности, чтобы отделить представление данных (а также доступ и мутацию) от более сложных функциональных возможностей по отдельным экземплярам данных или их совокупностям (например, что-то подобное searchUsersможет идти в классе коллекции или в хранилище / карте идентичности). )
Майкл Бауэр
источник
0

Послушайте, я думаю, что ваш вопрос слишком общий, чтобы на него ответить, поскольку у нас нет полного описания проблемы, поэтому предложить хороший дизайн с таким небольшим знанием невозможно. Я могу, в качестве примера, решить одну из ваших проблем, связанных с возможной тщетностью улучшения дизайна.

Вы говорите, что ваш класс Server и ваш будущий класс Chatroom совместно используют данные о пользователях, но эти данные должны отличаться. К серверу, вероятно, подключен набор пользователей, в то время как чат-комната, которая сама принадлежит серверу, имеет другой набор пользователей, подмножество первого набора, только из тех пользователей, которые в данный момент вошли в конкретный чат-зал.

Это не та же информация, даже если типы данных идентичны.
У хорошего дизайна есть много преимуществ.

Я еще не читал вышеупомянутую книгу Фаулера, но я прочитал другие вещи Фолвера, и они были рекомендованы мне людьми, которым я доверяю, поэтому я чувствую себя достаточно комфортно, чтобы согласиться с другими.

Shrulik
источник
0

Необходимость доступа к картам не оправдывает мегакласс. Вы должны разделить логику на несколько классов, и у каждого класса должен быть метод getMap, чтобы другие классы могли получить доступ к картам.

Тулаинс Кордова
источник
0

Я бы использовал тот же ответ, который я дал в другом месте: взять монолитный класс и разделить его обязанности между другими классами. И DCI, и Шаблон посетителя предоставляют хорошие возможности для этого.

Марио Т. Ланца
источник
-1

С точки зрения метрики программного обеспечения, большой класс - сумка. Есть неограниченные документы, подтверждающие это утверждение. Это почему ? потому что большие классы труднее понять, чем маленькие, и для их изменения требуется больше времени. Более того, большие классы очень сложны, когда вы проводите тестирование. И большие классы очень трудны для вас, когда вы хотите использовать их повторно, потому что они, скорее всего, содержат нежелательные вещи.

cat_minhv0
источник