Як розділити великі, щільно сполучені класи?


14

У мене є величезні класи з більш ніж 2-х рядків коду (і зростаючого), які я хотів би, якщо можливо, переробляти, щоб мати ще легкий і чистий дизайн.

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

Наведу дуже конкретний приклад: у мене є клас, який називається, Serverякий обробляє вхідні повідомлення. Він має такі методи , як joinChatroom, searchUsers, sendPrivateMessageі т.д. Всі ці методи маніпулювання картами типу users, chatrooms, servers, ...

Можливо, було б непогано, якби я міг мати повідомлення про обробку класу, що стосується чат-кімнати, інше обробку всього про користувачів тощо. Але тут головна проблема полягає в тому, що мені потрібно використовувати всі карти в більшості методів. Ось чому зараз усі вони дотримуються Serverкласу, оскільки всі вони покладаються на ці загальні карти і методи дуже пов'язані між собою.

Мені потрібно створити класні кімнати, але з посиланням на кожен з інших об'єктів. Користувачі класу знову з посиланням на всі інші об'єкти тощо.

Я відчуваю, що я б щось робив не так.


Якщо ви робите такі класи, як «Користувач» та «Чат», чи потребуватимуть ці класи лише посилання на загальну структуру даних або вони б посилалися один на одного?

Тут є кілька задовільних відповідей, вам слід вибрати один.
jeremyjjbrown

@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

Коли я бачив такі великі класи, я виявив, що там часто є клас (або більше), який намагається вийти. Якщо ви знаєте метод, який, на вашу думку, не може бути пов'язаний з цим класом, тоді зробіть його статичним. Потім компілятор розповість вам про інші методи, які викликає цей метод. Java буде наполягати, що вони теж статичні. Ви робите їх статичними. Знову компілятор розповість вам про будь-який метод, який він викликає. Ви продовжуєте робити це знову і знову, поки у вас більше не буде відмовок компіляції. Тоді у вас великий набір статичних методів у вашому великому класі. Тепер ви можете вивести їх у новий клас та зробити метод нестатичним. Потім ви можете назвати цей новий клас зі свого початкового великого класу (який тепер повинен містити менше рядків)

Потім ви можете повторювати процес, поки не будете задоволені дизайном класу.

Книга Мартіна Фаулера справді добре прочитана, тому я б рекомендував це також, оскільки є випадки, коли ви не можете використовувати цей статичний трюк.


1
Книга цього Мартіна Фаулера martinfowler.com/books/refactoring.html
Arul

1

Оскільки більшість вашого коду існує, я б запропонував використовувати допоміжні класи для переміщення ваших методів. Це допоможе легко рефакторингу. Отже, ваш серверний клас все ще буде містити в ньому карти. Але він використовує клас помічників, наприклад, ChatroomHelper, з такими методами, як приєднатись (карта чатів, користувач String), список getUsers (карта чатів), карта getChatrooms (користувач String).

Клас сервера міститиме екземпляр ChatroomHelper, UserHelper тощо, таким чином переміщуючи методи до своїх логічних класів помічників. Якщо це, ви можете залишити загальнодоступні методи на сервері недоторканими, тому будь-якому абоненту не потрібно змінювати.


1

Щоб додати до глибокої відповіді casablanca - якщо для декількох класів потрібно робити одні й ті ж основні речі з певним типом сутності (додавання користувачів до колекції, обробка повідомлень тощо), ці процеси також повинні зберігатись окремо.

Існує кілька способів зробити це - за спадщиною або складом. Для успадкування ви можете використовувати абстрактні базові класи конкретними методами, які надають поля та функціональність для обробки користувачів або повідомлень, наприклад, і мають такі об'єкти, як chatroomі user(або будь-які інші) розширення цих класів.

З різних причин є хорошим загальним правилом використовувати склад над успадкуванням. Ви можете використовувати композицію для цього різними способами. Оскільки обробка користувачів або повідомлень є основними функціями, відповідальними за класи, можна стверджувати, що введення конструктора є найбільш підходящим. Таким чином, залежність прозора і об'єкт не може бути створений без необхідної функціональності. Якщо спосіб обробки користувачів або повідомлення, можливо, зміниться або розшириться, вам слід розглянути можливість використання стратегії .

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

Все, що говориться, завжди враховуйте вартість додаткової складності при використанні таких шаблонів - якщо вам це не потрібно, не кодуйте. Якщо ви знаєте, що напевно ви не збираєтесь змінювати способи обробки користувачів / повідомлень, вам не потрібна структурна складність стратегічного шаблону - але для того, щоб відокремити проблеми та уникнути повторення, все одно слід розлучитися із загальною функціональністю з конкретних випадків, які використовують його, і, якщо немає суттєвих причин, що протилежні, складіть користувачів такої функціональності обробки (чати, користувачі) з об'єктом, який виконує обробку.

Отже, підводячи підсумки:

  1. Як писав casablanca: Окремі та інкапсулюйте чати, користувачів тощо.
  2. Окремий загальний функціонал
  3. Розглянемо розділення окремих функціональних можливостей для розлучення представлення даних (а також доступу та мутації) від більш складних функціональних можливостей щодо окремих екземплярів даних або їх агрегатів (наприклад, щось на зразок searchUsersмогло б перейти до колекційного класу чи сховища / ідентифікаційної карти )

0

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

Ви говорите, що ваш серверний клас та ваш майбутній клас Chatroom діляться даними про користувачів, але ці дані повинні відрізнятися. Сервер, ймовірно, має набір користувачів, підключених до нього, тоді як в кімнаті чатів, яка сама належить до Сервера, є інший набір користувачів, підмножина першого набору, лише ті користувачі, які в даний час увійшли до певної кімнати чату.

Це не та сама інформація, навіть якщо типи даних однакові.
Є багато переваг хорошого дизайну.

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


0

Необхідність доступу до карт не виправдовує мега-клас. Ви повинні розділити логіку на кілька класів, і кожен клас повинен мати метод getMap, щоб інші класи мали доступ до карт.


0

Я використовував би ту саму відповідь, яку я надав в іншому місці: взяти монолітний клас і розділити його обов'язки між іншими класами. І DCI, і шаблон відвідувачів надають хороші варіанти для цього.


-1

З точки зору програмної метрики великий клас - це сумка. Існують необмежені документи, що підтверджують це твердження. Чому так ? тому що великі класи важче зрозуміти, ніж малі, і для модифікації потрібно більше часу. Більше того, великі класи настільки жорсткі, коли ви робите тестування. А великі класи дуже важкі для вас, коли ви хочете використовувати його повторно, тому що це, ймовірно, містить небажані речі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.