refactor: restructure services and filters architecture
- Remove deprecated service interfaces (CooldownService, IgnoreService, PMService, PlayerDataService, SpyService) - Add new unified service interfaces (MessagingService, PlayerService, PunishmentService) - Extract FilterResult as standalone class from AdvancedMessageFilter - Implement individual filter classes (CharacterFilter, FloodFilter, SpamFilter, SwearFilter, UrlFilter) - Add moderation commands (BanCommand, WarnCommand, SilentWarnCommand, UnbanCommand) - Introduce data models for punishment tracking (BanRecord, WarnEntry) - Refactor messaging service with new MessagingServiceImpl implementation - Add mute service utilities (MuteDataStorage, MutePermissionChecker) - Implement PunishmentServiceImpl with snapshot tracking - Add Discord integration components (DiscordConfig, DiscordMessageService) - Introduce command handlers for gradient module (ColorCommandHandler, PrefixCommandHandler) - Add renderer components (EmojiComponent, GradientBuilder, UrlProcessor) - Add utility classes (ColorConverter, MessageSender, PlaceholderProcessor) - Update configuration files and plugin metadata - Add documentation files (CODE_REVIEW_RU.md, REFACTORING_SERVICES.md) - Consolidate service architecture for improved maintainability and separation of concerns
This commit is contained in:
parent
2d79d2a91d
commit
bba480002f
181 changed files with 3455 additions and 2908 deletions
356
CODE_REVIEW_RU.md
Normal file
356
CODE_REVIEW_RU.md
Normal file
|
|
@ -0,0 +1,356 @@
|
||||||
|
# 🔍 Code Review: Проблемы архитектуры (SOLID, DRY, KISS)
|
||||||
|
|
||||||
|
## 🔴 Критические проблемы
|
||||||
|
|
||||||
|
### 1. DRY: Дублирование кода в конфигах
|
||||||
|
**Файлы:** `AppearanceConfig.java`, `MessagesConfig.java`, `FiltersConfig.java`
|
||||||
|
|
||||||
|
**Проблема:**
|
||||||
|
- Каждый конфиг содержит 30-50 геттеров с одинаковой структурой
|
||||||
|
- ~300 строк повторяющегося кода
|
||||||
|
- При изменении логики нужно править в 3 местах
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
```java
|
||||||
|
// Создать базовый класс с generic методами
|
||||||
|
public abstract class BaseConfig {
|
||||||
|
protected String getString(String path, String def) { ... }
|
||||||
|
protected int getInt(String path, int def) { ... }
|
||||||
|
protected boolean getBoolean(String path, boolean def) { ... }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Использовать в конфигах
|
||||||
|
public class MessagesConfig extends BaseConfig {
|
||||||
|
public String getNoPermission() {
|
||||||
|
return getString("general.no_permission", "&#CF6679Нет прав!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. SRP: ConfigManager делает слишком много
|
||||||
|
**Файл:** `ConfigManager.java`
|
||||||
|
|
||||||
|
**Проблема:**
|
||||||
|
- Управляет 5 конфигами
|
||||||
|
- Содержит логику обновления версий
|
||||||
|
- Имеет 30+ геттеров для настроек
|
||||||
|
- Нарушает Single Responsibility Principle
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
```java
|
||||||
|
// Разделить на отдельные классы
|
||||||
|
class ConfigManager {
|
||||||
|
private final AppearanceConfig appearance;
|
||||||
|
private final MessagesConfig messages;
|
||||||
|
// Только управление конфигами
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConfigUpdater {
|
||||||
|
// Логика обновления версий
|
||||||
|
void updateConfig(FileConfiguration config) { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. KISS: ColorConverter неэффективен
|
||||||
|
**Файл:** `ColorConverter.java`
|
||||||
|
|
||||||
|
**Проблема:**
|
||||||
|
```java
|
||||||
|
private static String convertAmpersandColors(String message) {
|
||||||
|
return message
|
||||||
|
.replaceAll("&0", "<black>")
|
||||||
|
.replaceAll("&1", "<dark_blue>")
|
||||||
|
// ... 16 вызовов replaceAll
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Каждый `replaceAll` компилирует regex
|
||||||
|
- O(n * 16) сложность
|
||||||
|
- Дублирование для `§` и `&`
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
```java
|
||||||
|
private static final Map<String, String> COLOR_MAP = Map.ofEntries(
|
||||||
|
Map.entry("&0", "<black>"),
|
||||||
|
Map.entry("§0", "<black>"),
|
||||||
|
Map.entry("&1", "<dark_blue>"),
|
||||||
|
Map.entry("§1", "<dark_blue>")
|
||||||
|
// ...
|
||||||
|
);
|
||||||
|
|
||||||
|
private static String convertColors(String message) {
|
||||||
|
for (Map.Entry<String, String> entry : COLOR_MAP.entrySet()) {
|
||||||
|
message = message.replace(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. DRY: Дублирование проверок permissions
|
||||||
|
**Файл:** `AdvancedMessageFilter.java`
|
||||||
|
|
||||||
|
**Проблема:**
|
||||||
|
```java
|
||||||
|
if (!player.hasPermission("lochat.bypass.swear") && !player.hasPermission("lochat.bypass.filter"))
|
||||||
|
if (!player.hasPermission("lochat.bypass.flood") && !player.hasPermission("lochat.bypass.filter"))
|
||||||
|
if (!player.hasPermission("lochat.bypass.spam") && !player.hasPermission("lochat.bypass.filter"))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
```java
|
||||||
|
private boolean canBypassFilter(Player player, String filterType) {
|
||||||
|
return player.hasPermission("lochat.bypass." + filterType)
|
||||||
|
|| player.hasPermission("lochat.bypass.filter");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Использование
|
||||||
|
if (!canBypassFilter(player, "swear")) {
|
||||||
|
FilterResult swear = swearFilter.filter(player, message);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. OCP: EnhancedChatRenderer не расширяем
|
||||||
|
**Файл:** `EnhancedChatRenderer.java`
|
||||||
|
|
||||||
|
**Проблема:**
|
||||||
|
```java
|
||||||
|
switch (placeholder) {
|
||||||
|
case "emoji" -> result = result.append(emoji);
|
||||||
|
case "prefix" -> result = result.append(prefix);
|
||||||
|
// Добавление нового плейсхолдера требует изменения кода
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
```java
|
||||||
|
// Strategy pattern для плейсхолдеров
|
||||||
|
interface PlaceholderResolver {
|
||||||
|
Component resolve(Player player, boolean isGlobal);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, PlaceholderResolver> resolvers = Map.of(
|
||||||
|
"emoji", (p, g) -> buildEmojiComponent(g),
|
||||||
|
"prefix", (p, g) -> buildPrefix(g),
|
||||||
|
"player", (p, g) -> buildPlayerComponent(p)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Использование
|
||||||
|
Component resolved = resolvers.getOrDefault(placeholder,
|
||||||
|
(p, g) -> Component.text("{" + placeholder + "}"))
|
||||||
|
.resolve(player, isGlobal);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. ISP: MuteServiceImpl не реализован полностью
|
||||||
|
**Файл:** `MuteServiceImpl.java`
|
||||||
|
|
||||||
|
**Проблема:**
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
public void save() {
|
||||||
|
// TODO: Implement save to file
|
||||||
|
}
|
||||||
|
|
||||||
|
private void load() {
|
||||||
|
// TODO: Implement load from file
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
- Либо реализовать методы
|
||||||
|
- Либо разделить интерфейс на `MuteService` и `PersistentMuteService`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Отсутствует зависимость VoiceChat
|
||||||
|
**Файл:** `MuteServiceImpl.java`
|
||||||
|
|
||||||
|
**Проблема:**
|
||||||
|
```java
|
||||||
|
import de.maxhenkel.voicechat.api.BukkitVoicechatService; // ❌ Не найден
|
||||||
|
```
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
```gradle
|
||||||
|
// build.gradle.kts
|
||||||
|
dependencies {
|
||||||
|
compileOnly("de.maxhenkel.voicechat:voicechat-api:2.5.0")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Или сделать опциональной зависимостью:
|
||||||
|
```java
|
||||||
|
private void initVoiceChat() {
|
||||||
|
try {
|
||||||
|
Class.forName("de.maxhenkel.voicechat.api.BukkitVoicechatService");
|
||||||
|
// Инициализация
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
plugin.getLogger().info("VoiceChat not found, voice mute disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. SRP: ChatEventListener слишком сложный
|
||||||
|
**Файл:** `ChatEventListener.java`
|
||||||
|
|
||||||
|
**Проблема:**
|
||||||
|
- Метод `onChat` содержит 80+ строк
|
||||||
|
- Обрабатывает: режим чата, фильтрацию, радиус, Discord, статистику
|
||||||
|
- Нарушает Single Responsibility
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
```java
|
||||||
|
class ChatEventListener {
|
||||||
|
private final ChatModeHandler modeHandler;
|
||||||
|
private final MessageFilterHandler filterHandler;
|
||||||
|
private final LocalChatHandler localHandler;
|
||||||
|
private final DiscordHandler discordHandler;
|
||||||
|
private final StatsHandler statsHandler;
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onChat(AsyncChatEvent event) {
|
||||||
|
Player sender = event.getPlayer();
|
||||||
|
String message = extractMessage(event);
|
||||||
|
|
||||||
|
// Делегируем обработку
|
||||||
|
ChatMode mode = modeHandler.determineMode(message);
|
||||||
|
if (!filterHandler.filter(sender, message)) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == ChatMode.LOCAL) {
|
||||||
|
localHandler.handleLocal(event, sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
discordHandler.sendToDiscord(sender, message, mode);
|
||||||
|
statsHandler.recordMessage(sender, mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. DRY: Дублирование конвертации цветов
|
||||||
|
**Файл:** `ColorConverter.java`
|
||||||
|
|
||||||
|
**Проблема:**
|
||||||
|
```java
|
||||||
|
private static String convertSectionColors(String message) { ... }
|
||||||
|
private static String convertAmpersandColors(String message) { ... }
|
||||||
|
```
|
||||||
|
Идентичная логика, только разные символы.
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
```java
|
||||||
|
private static String convertColors(String message, char symbol) {
|
||||||
|
return message
|
||||||
|
.replaceAll(symbol + "0", "<black>")
|
||||||
|
.replaceAll(symbol + "1", "<dark_blue>")
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String convertLegacyFormats(String message) {
|
||||||
|
message = convertColors(message, '§');
|
||||||
|
message = convertColors(message, '&');
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 10. DIP: Прямая зависимость от LoChat
|
||||||
|
**Файлы:** `EnhancedChatRenderer.java`, `ChatEventListener.java`, и др.
|
||||||
|
|
||||||
|
**Проблема:**
|
||||||
|
```java
|
||||||
|
com.loki.lochat.LoChat loChat = (com.loki.lochat.LoChat) plugin;
|
||||||
|
```
|
||||||
|
Нарушает Dependency Inversion Principle.
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
```java
|
||||||
|
// Создать интерфейс
|
||||||
|
interface ChatPlugin {
|
||||||
|
ConfigManager getConfigManager();
|
||||||
|
GradientModule getGradientModule();
|
||||||
|
DiscordIntegration getDiscordIntegration();
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoChat реализует интерфейс
|
||||||
|
class LoChat extends JavaPlugin implements ChatPlugin { ... }
|
||||||
|
|
||||||
|
// Использование
|
||||||
|
class EnhancedChatRenderer {
|
||||||
|
private final ChatPlugin plugin;
|
||||||
|
|
||||||
|
public EnhancedChatRenderer(ChatPlugin plugin, boolean isGlobal) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
// Нет каста
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Статистика проблем
|
||||||
|
|
||||||
|
| Принцип | Нарушений | Критичность |
|
||||||
|
|---------|-----------|-------------|
|
||||||
|
| DRY | 4 | 🔴 Высокая |
|
||||||
|
| SRP | 3 | 🔴 Высокая |
|
||||||
|
| KISS | 2 | 🟡 Средняя |
|
||||||
|
| OCP | 1 | 🟡 Средняя |
|
||||||
|
| DIP | 1 | 🟡 Средняя |
|
||||||
|
| ISP | 1 | 🟢 Низкая |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Что сделано хорошо
|
||||||
|
|
||||||
|
1. **ServiceRegistry** - правильная реализация DI
|
||||||
|
2. **Разделение на модули** - gradient, integrations, core
|
||||||
|
3. **Использование интерфейсов** - api.service.*
|
||||||
|
4. **PluginInitializer/PluginShutdown** - разделение логики инициализации
|
||||||
|
5. **BaseConfig** - базовый класс для конфигов (уже есть!)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Приоритеты исправления
|
||||||
|
|
||||||
|
### Высокий приоритет
|
||||||
|
1. Исправить отсутствующую зависимость VoiceChat
|
||||||
|
2. Упростить ColorConverter (KISS)
|
||||||
|
3. Вынести проверки permissions в отдельный метод (DRY)
|
||||||
|
|
||||||
|
### Средний приоритет
|
||||||
|
4. Разделить ChatEventListener на handler'ы (SRP)
|
||||||
|
5. Убрать касты к LoChat через интерфейс (DIP)
|
||||||
|
6. Сделать EnhancedChatRenderer расширяемым (OCP)
|
||||||
|
|
||||||
|
### Низкий приоритет
|
||||||
|
7. Реализовать save/load в MuteServiceImpl (ISP)
|
||||||
|
8. Упростить ConfigManager (SRP)
|
||||||
|
9. Объединить методы конвертации цветов (DRY)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Итого
|
||||||
|
|
||||||
|
Проект имеет хорошую базовую архитектуру (ServiceRegistry, модули, интерфейсы), но есть проблемы с:
|
||||||
|
- **Дублированием кода** (DRY) - особенно в конфигах и фильтрах
|
||||||
|
- **Слишком большими классами** (SRP) - ConfigManager, ChatEventListener
|
||||||
|
- **Неэффективными алгоритмами** (KISS) - ColorConverter
|
||||||
|
|
||||||
|
Рекомендую начать с исправления критических проблем (VoiceChat, ColorConverter, permissions), затем постепенно рефакторить остальное.
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
# План миграции команд на новую архитектуру
|
|
||||||
|
|
||||||
> **Статус (актуально для репозитория):** централизованная регистрация в `CommandManager` и раскладка по пакетам **уже внедрены**. Ниже — что сделано и что остаётся опциональной доработкой.
|
|
||||||
|
|
||||||
**Что осталось по плану:** миграция команд **завершена**. Дальше — по желанию: перевести оставшиеся `CommandExecutor` на `PlayerCommand`/`AdminCommand`, добавить тесты/ручной регресс, короткую доку «как добавить команду».
|
|
||||||
|
|
||||||
## Что уже сделано
|
|
||||||
|
|
||||||
### Архитектура
|
|
||||||
- `commands/base/BaseCommand`, `PlayerCommand`, `AdminCommand`
|
|
||||||
- `CommandManager` — единая точка регистрации (`reg` / `regTab` / `registerBaseCommand`)
|
|
||||||
|
|
||||||
### Пакеты (фактическая структура)
|
|
||||||
| Пакет | Назначение |
|
|
||||||
|--------|------------|
|
|
||||||
| `commands/chat/` | глобальный / локальный чат (`g`, `l`) |
|
|
||||||
| `commands/messaging/` | ЛС, игнор (`msg`, `reply`, `ignore`, `unignore`, `ignorelist`) |
|
|
||||||
| `commands/moderation/` | муты, варны, баны (`lmute`, `lunmute`, `lmutelist`, `lmutehistory`, `lmuteblame`, `warn`, `silentwarn`, `lban`, `lunban`) |
|
|
||||||
| `commands/nick/` | ник, инфо (`nick`, `playerinfo`) |
|
|
||||||
| `commands/admin/` | админ-утилиты (`announce`, `chatspy`, `clearchat`, `clearchatconfig`, `lochat`, `lochatreload`, `discordadmin`); в репозитории также есть `HubCommand.java` (проверьте `plugin.yml`, если нужна команда хаба) |
|
|
||||||
| `commands/rp/` | RP (`me`, `try`, `do`, `roll`) |
|
|
||||||
| Корень `commands/` | `CustomCommandsCommand`, `CustomCommand` |
|
|
||||||
|
|
||||||
Папки `social/` в проекте нет — социальные команды лежат в `messaging/` и `nick/`.
|
|
||||||
|
|
||||||
### Регистрация в `CommandManager` (выполнено)
|
|
||||||
- [x] Этап 1 — чат: `GlobalChatCommand`, `LocalChatCommand`
|
|
||||||
- [x] Этап 2 — ЛС: `MsgCommand`, `ReplyCommand`
|
|
||||||
- [x] Этап 3 — социальное: `IgnoreCommand`, `UnignoreCommand`, `IgnoreListCommand`, `NickCommand` (+ `PlayerInfoCommand`)
|
|
||||||
- [x] Этап 4 — модерация: все перечисленные в таблице пакета `moderation/`
|
|
||||||
- [x] Этап 5 — админ: `AnnounceCommand`, `ClearChatCommand`, `ClearChatConfigCommand`, `ChatSpyCommand`, `LoChatCommand`, `ReloadConfigCommand`, `DiscordCommand`
|
|
||||||
- [x] Этап 6 — `CustomCommandsCommand` и движок кастомных команд
|
|
||||||
|
|
||||||
## Что ещё можно сделать (не блокирует работу)
|
|
||||||
|
|
||||||
### Унификация базового класса
|
|
||||||
Многие команды по-прежнему реализуют `CommandExecutor` напрямую, а не `PlayerCommand` / `AdminCommand`. Имеет смысл постепенно переводить на базовые классы там, где это убирает дублирование проверок и сообщений.
|
|
||||||
|
|
||||||
### Тесты и ручная проверка
|
|
||||||
- [ ] Стабильные сценарии для приоритетных команд (чат, ЛС, муты) — ручные или автотесты
|
|
||||||
- [ ] Проверка TabCompleter на зарегистрированных командах с `regTab`
|
|
||||||
|
|
||||||
### Документация
|
|
||||||
- [ ] Короткий `CONTRIBUTING` или раздел в README: как добавить команду через `CommandManager` и `plugin.yml`
|
|
||||||
|
|
||||||
## Процесс миграции одной команды (если рефакторите дальше)
|
|
||||||
|
|
||||||
### 1. Новая команда на базовом классе
|
|
||||||
```java
|
|
||||||
public class NewCommand extends PlayerCommand {
|
|
||||||
public NewCommand(LoChat plugin) {
|
|
||||||
super(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean executePlayerCommand(Player player, Command command,
|
|
||||||
String label, String[] args) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Проверки
|
|
||||||
- Поведение и права
|
|
||||||
- Сообщения из `MessagesConfig` / конфигов
|
|
||||||
- TabCompleter при необходимости
|
|
||||||
|
|
||||||
### 3. Регистрация
|
|
||||||
В `CommandManager`: `reg("name", new NewCommand(plugin))` или `regTab`, плюс запись в `plugin.yml`.
|
|
||||||
|
|
||||||
### 4. Удаление старого кода
|
|
||||||
После замены — удалить старый класс и неиспользуемые импорты.
|
|
||||||
|
|
||||||
## Чек-лист для команды
|
|
||||||
|
|
||||||
- [ ] Корректный базовый класс или явный `CommandExecutor` с единым стилем обработки ошибок
|
|
||||||
- [ ] Права из `plugin.yml`
|
|
||||||
- [ ] Сообщения из конфигурации
|
|
||||||
- [ ] Валидация аргументов
|
|
||||||
- [ ] TabCompleter при необходимости
|
|
||||||
|
|
||||||
## Ожидаемые эффекты от доведения до единого стиля
|
|
||||||
|
|
||||||
- Меньше дублирования проверок отправителя и прав
|
|
||||||
- Проще добавлять новые команды
|
|
||||||
- Проще сопровождать код при едином паттерне
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Следующие шаги (по желанию):** выбрать 2–3 самых «шумных» по коду команды → перевести на `PlayerCommand`/`AdminCommand` → добавить минимальные тесты или чек-лист ручной регрессии.
|
|
||||||
287
REFACTORING_SERVICES.md
Normal file
287
REFACTORING_SERVICES.md
Normal file
|
|
@ -0,0 +1,287 @@
|
||||||
|
# 🔧 Рефакторинг сервисов - уменьшение количества файлов
|
||||||
|
|
||||||
|
## Текущая ситуация
|
||||||
|
- **11 интерфейсов** в `api/service/`
|
||||||
|
- **12 реализаций** в `core/service/`
|
||||||
|
- **23 файла** только для сервисов
|
||||||
|
- Некоторые сервисы по 30 строк кода
|
||||||
|
|
||||||
|
## Проблема
|
||||||
|
Слишком много мелких файлов, которые можно объединить по функциональности.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 План рефакторинга
|
||||||
|
|
||||||
|
### 1. Объединить простые сервисы в один
|
||||||
|
|
||||||
|
**Было:**
|
||||||
|
```
|
||||||
|
PMService.java (28 строк)
|
||||||
|
SpyService.java (69 строк)
|
||||||
|
IgnoreService.java (97 строк)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Станет:**
|
||||||
|
```java
|
||||||
|
// MessagingService.java - всё что связано с общением
|
||||||
|
public interface MessagingService {
|
||||||
|
// PM
|
||||||
|
void setLastConversation(UUID sender, UUID receiver);
|
||||||
|
UUID getLastConversation(UUID player);
|
||||||
|
|
||||||
|
// Spy
|
||||||
|
boolean toggleSpy(UUID player);
|
||||||
|
boolean isSpying(UUID player);
|
||||||
|
void broadcastPM(Player sender, Player receiver, String message);
|
||||||
|
|
||||||
|
// Ignore
|
||||||
|
void addIgnore(UUID player, UUID target);
|
||||||
|
void removeIgnore(UUID player, UUID target);
|
||||||
|
boolean isIgnoring(UUID player, UUID target);
|
||||||
|
Set<UUID> getIgnoreList(UUID player);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Экономия:** 3 интерфейса + 3 реализации = **6 файлов → 2 файла**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Объединить Mute + Punishment
|
||||||
|
|
||||||
|
**Было:**
|
||||||
|
```
|
||||||
|
MuteService.java + MuteServiceImpl.java (212 строк)
|
||||||
|
PunishmentService.java + PunishmentServiceImpl.java (173 строки)
|
||||||
|
MuteHistoryManager.java (77 строк)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Станет:**
|
||||||
|
```java
|
||||||
|
// ModerationService.java - всё что связано с модерацией
|
||||||
|
public interface ModerationService {
|
||||||
|
// Mutes
|
||||||
|
void mute(UUID uuid, String name, long duration, String reason, String by);
|
||||||
|
boolean unmute(UUID uuid, String by);
|
||||||
|
boolean isMuted(UUID uuid);
|
||||||
|
MuteData getMuteData(UUID uuid);
|
||||||
|
|
||||||
|
// Bans
|
||||||
|
void ban(UUID uuid, String name, long duration, String reason, String by);
|
||||||
|
boolean unban(UUID uuid, String by);
|
||||||
|
boolean isBanned(UUID uuid);
|
||||||
|
BanData getBanData(UUID uuid);
|
||||||
|
|
||||||
|
// Warns
|
||||||
|
void warn(UUID uuid, String name, String reason, String by);
|
||||||
|
List<WarnData> getWarns(UUID uuid);
|
||||||
|
|
||||||
|
// History
|
||||||
|
List<ModerationEntry> getHistory(UUID uuid);
|
||||||
|
List<ModerationEntry> getHistoryByModerator(String name);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Экономия:** 3 интерфейса + 3 реализации = **6 файлов → 2 файла**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Объединить Cooldown + PlayerData
|
||||||
|
|
||||||
|
**Было:**
|
||||||
|
```
|
||||||
|
CooldownService.java + CooldownServiceImpl.java (40 строк)
|
||||||
|
PlayerDataService.java + PlayerDataServiceImpl.java (102 строки)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Станет:**
|
||||||
|
```java
|
||||||
|
// PlayerService.java - всё что связано с данными игрока
|
||||||
|
public interface PlayerService {
|
||||||
|
// Cooldowns
|
||||||
|
boolean hasCooldown(UUID player, String type);
|
||||||
|
void setCooldown(UUID player, String type, long duration);
|
||||||
|
long getRemainingCooldown(UUID player, String type);
|
||||||
|
void clearCooldowns(UUID player);
|
||||||
|
|
||||||
|
// Statistics
|
||||||
|
void recordMessage(UUID player, String chatType);
|
||||||
|
PlayerStats getStats(UUID player);
|
||||||
|
void saveAll();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Экономия:** 2 интерфейса + 2 реализации = **4 файла → 2 файла**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Оставить как есть (нормальный размер)
|
||||||
|
|
||||||
|
```
|
||||||
|
ChatService.java + ChatServiceImpl.java (114 строк) ✅
|
||||||
|
MessageService.java + MessageServiceImpl.java (36 строк) ✅
|
||||||
|
MentionService.java + MentionServiceImpl.java (104 строки) ✅
|
||||||
|
NickService.java + NickServiceImpl.java (155 строк) ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
Эти сервисы имеют чёткую ответственность и нормальный размер.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Результат рефакторинга
|
||||||
|
|
||||||
|
| До | После | Экономия |
|
||||||
|
|----|-------|----------|
|
||||||
|
| 11 интерфейсов | 7 интерфейсов | -4 |
|
||||||
|
| 12 реализаций | 8 реализаций | -4 |
|
||||||
|
| **23 файла** | **15 файлов** | **-8 файлов** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔨 Пошаговая реализация
|
||||||
|
|
||||||
|
### Шаг 1: Создать MessagingService
|
||||||
|
|
||||||
|
```java
|
||||||
|
// api/service/MessagingService.java
|
||||||
|
public interface MessagingService {
|
||||||
|
// PM
|
||||||
|
void setLastConversation(UUID sender, UUID receiver);
|
||||||
|
UUID getLastConversation(UUID player);
|
||||||
|
|
||||||
|
// Spy
|
||||||
|
boolean toggleSpy(UUID player);
|
||||||
|
boolean isSpying(UUID player);
|
||||||
|
void broadcastPM(Player sender, Player receiver, String message);
|
||||||
|
void sendToSpies(Player sender, Component message, boolean isGlobal);
|
||||||
|
void removeSpy(UUID player);
|
||||||
|
|
||||||
|
// Ignore
|
||||||
|
void addIgnore(UUID player, UUID target);
|
||||||
|
void removeIgnore(UUID player, UUID target);
|
||||||
|
boolean isIgnoring(UUID player, UUID target);
|
||||||
|
Set<UUID> getIgnoreList(UUID player);
|
||||||
|
void save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// core/service/MessagingServiceImpl.java
|
||||||
|
public class MessagingServiceImpl implements MessagingService {
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
private final MessageConfig messageConfig;
|
||||||
|
|
||||||
|
// PM state
|
||||||
|
private final Map<UUID, UUID> lastConversation = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
// Spy state
|
||||||
|
private final Set<UUID> spyEnabled = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
|
// Ignore state
|
||||||
|
private final Map<UUID, Set<UUID>> ignoreMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public MessagingServiceImpl(JavaPlugin plugin, MessageConfig messageConfig) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.messageConfig = messageConfig;
|
||||||
|
loadIgnoreData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Реализация всех методов...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Шаг 2: Обновить ServiceRegistry
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Было
|
||||||
|
register(PMService.class, new PMServiceImpl());
|
||||||
|
register(SpyService.class, new SpyServiceImpl(messageConfig));
|
||||||
|
register(IgnoreService.class, new IgnoreServiceImpl(plugin));
|
||||||
|
|
||||||
|
// Стало
|
||||||
|
MessagingService messaging = new MessagingServiceImpl(plugin, messageConfig);
|
||||||
|
register(MessagingService.class, messaging);
|
||||||
|
|
||||||
|
// Для обратной совместимости (опционально)
|
||||||
|
register(PMService.class, messaging);
|
||||||
|
register(SpyService.class, messaging);
|
||||||
|
register(IgnoreService.class, messaging);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Шаг 3: Удалить старые файлы
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Удалить интерфейсы
|
||||||
|
rm src/main/java/com/loki/lochat/api/service/PMService.java
|
||||||
|
rm src/main/java/com/loki/lochat/api/service/SpyService.java
|
||||||
|
rm src/main/java/com/loki/lochat/api/service/IgnoreService.java
|
||||||
|
|
||||||
|
# Удалить реализации
|
||||||
|
rm src/main/java/com/loki/lochat/core/service/PMServiceImpl.java
|
||||||
|
rm src/main/java/com/loki/lochat/core/service/SpyServiceImpl.java
|
||||||
|
rm src/main/java/com/loki/lochat/core/service/IgnoreServiceImpl.java
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Альтернативный подход (если не хочешь ломать API)
|
||||||
|
|
||||||
|
Можно оставить старые интерфейсы, но сделать их делегатами:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Старый интерфейс остаётся
|
||||||
|
public interface PMService {
|
||||||
|
void setLastConversation(UUID sender, UUID receiver);
|
||||||
|
UUID getLastConversation(UUID player);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Но реализация делегирует в MessagingService
|
||||||
|
public class PMServiceAdapter implements PMService {
|
||||||
|
private final MessagingService messaging;
|
||||||
|
|
||||||
|
public PMServiceAdapter(MessagingService messaging) {
|
||||||
|
this.messaging = messaging;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLastConversation(UUID sender, UUID receiver) {
|
||||||
|
messaging.setLastConversation(sender, receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getLastConversation(UUID player) {
|
||||||
|
return messaging.getLastConversation(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Это позволит:
|
||||||
|
- Сохранить обратную совместимость
|
||||||
|
- Уменьшить количество реализаций (3 → 1)
|
||||||
|
- Постепенно мигрировать код на новый API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Рекомендация
|
||||||
|
|
||||||
|
**Начни с MessagingService** - это самое простое объединение:
|
||||||
|
1. PM, Spy, Ignore логически связаны (общение между игроками)
|
||||||
|
2. Все три сервиса маленькие (28-97 строк)
|
||||||
|
3. Объединение даст ~200 строк - нормальный размер
|
||||||
|
|
||||||
|
**Потом ModerationService** - это сложнее:
|
||||||
|
1. Mute + Punishment + History = ~460 строк
|
||||||
|
2. Нужно будет рефакторить внутреннюю структуру
|
||||||
|
3. Но логически это одна область (модерация)
|
||||||
|
|
||||||
|
**PlayerService оставь на потом** - там всё норм.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Итого
|
||||||
|
|
||||||
|
Объединение сервисов:
|
||||||
|
- ✅ Уменьшит количество файлов с 23 до 15
|
||||||
|
- ✅ Упростит навигацию по коду
|
||||||
|
- ✅ Сгруппирует связанную функциональность
|
||||||
|
- ⚠️ Потребует обновления кода, который использует старые сервисы
|
||||||
|
|
||||||
|
Начни с MessagingService - это быстро и безопасно.
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
bin/main/com/loki/lochat/api/service/MessagingService.class
Normal file
BIN
bin/main/com/loki/lochat/api/service/MessagingService.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
bin/main/com/loki/lochat/api/service/PlayerService.class
Normal file
BIN
bin/main/com/loki/lochat/api/service/PlayerService.class
Normal file
Binary file not shown.
BIN
bin/main/com/loki/lochat/api/service/PunishmentService.class
Normal file
BIN
bin/main/com/loki/lochat/api/service/PunishmentService.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
bin/main/com/loki/lochat/commands/moderation/BanCommand.class
Normal file
BIN
bin/main/com/loki/lochat/commands/moderation/BanCommand.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
bin/main/com/loki/lochat/commands/moderation/UnbanCommand.class
Normal file
BIN
bin/main/com/loki/lochat/commands/moderation/UnbanCommand.class
Normal file
Binary file not shown.
BIN
bin/main/com/loki/lochat/commands/moderation/WarnCommand.class
Normal file
BIN
bin/main/com/loki/lochat/commands/moderation/WarnCommand.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
bin/main/com/loki/lochat/core/filter/FilterResult.class
Normal file
BIN
bin/main/com/loki/lochat/core/filter/FilterResult.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
bin/main/com/loki/lochat/core/filter/filters/FloodFilter.class
Normal file
BIN
bin/main/com/loki/lochat/core/filter/filters/FloodFilter.class
Normal file
Binary file not shown.
BIN
bin/main/com/loki/lochat/core/filter/filters/SpamFilter.class
Normal file
BIN
bin/main/com/loki/lochat/core/filter/filters/SpamFilter.class
Normal file
Binary file not shown.
BIN
bin/main/com/loki/lochat/core/filter/filters/SwearFilter.class
Normal file
BIN
bin/main/com/loki/lochat/core/filter/filters/SwearFilter.class
Normal file
Binary file not shown.
BIN
bin/main/com/loki/lochat/core/filter/filters/UrlFilter.class
Normal file
BIN
bin/main/com/loki/lochat/core/filter/filters/UrlFilter.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
bin/main/com/loki/lochat/core/service/MessagingServiceImpl.class
Normal file
BIN
bin/main/com/loki/lochat/core/service/MessagingServiceImpl.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
bin/main/com/loki/lochat/core/service/PlayerServiceImpl.class
Normal file
BIN
bin/main/com/loki/lochat/core/service/PlayerServiceImpl.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
bin/main/com/loki/lochat/core/service/mute/MuteDataStorage.class
Normal file
BIN
bin/main/com/loki/lochat/core/service/mute/MuteDataStorage.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
bin/main/com/loki/lochat/data/model/BanRecord.class
Normal file
BIN
bin/main/com/loki/lochat/data/model/BanRecord.class
Normal file
Binary file not shown.
BIN
bin/main/com/loki/lochat/data/model/WarnEntry.class
Normal file
BIN
bin/main/com/loki/lochat/data/model/WarnEntry.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
bin/main/com/loki/lochat/gradient/service/GradientService.class
Normal file
BIN
bin/main/com/loki/lochat/gradient/service/GradientService.class
Normal file
Binary file not shown.
BIN
bin/main/com/loki/lochat/gradient/service/PrefixService.class
Normal file
BIN
bin/main/com/loki/lochat/gradient/service/PrefixService.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
bin/main/com/loki/lochat/listener/ModerationListener.class
Normal file
BIN
bin/main/com/loki/lochat/listener/ModerationListener.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
bin/main/com/loki/lochat/managers/commands/MessageSender.class
Normal file
BIN
bin/main/com/loki/lochat/managers/commands/MessageSender.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
bin/main/com/loki/lochat/renderer/components/UrlProcessor.class
Normal file
BIN
bin/main/com/loki/lochat/renderer/components/UrlProcessor.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue