Антипаттерны: God Interfaces и принцип разделения интерфейсов
God Interfaces — распространённый антипаттерн в объектно-ориентированном программировании. Он возникает, когда интерфейс берёт на себя слишком много ответственности и нарушает принцип разделения интерфейсов.
Что такое God Interfaces?
God Interfaces — это перегруженные интерфейсы, определяющие слишком много методов и объединяющие разнородные, логически не связанные между собой обязанности. Они заставляют классы-реализации предоставлять функциональность, которая им вообще не нужна.
Проблемы God Interfaces
- Нарушение принципа единственной ответственности
- Ненужные зависимости
- Сложность тестирования
- Высокая связанность кода
- Трудности при поддержке
Примеры God Interface
Плохой дизайн: God Interface
// Anti-Pattern: God Interface
public interface UniversalSystemManager {
// Benutzer-Management
void createUser(String username, String password);
void deleteUser(String userId);
User getUser(String userId);
List<User> getAllUsers();
boolean authenticateUser(String username, String password);
// Datenbank-Management
void connectToDatabase(String url, String user, String password);
void executeQuery(String sql);
void closeConnection();
void backupDatabase();
void restoreDatabase(String backupFile);
// Dateisystem-Management
void createFile(String path, String content);
void deleteFile(String path);
File readFile(String path);
void createDirectory(String path);
// Netzwerk-Management
void sendEmail(String to, String subject, String body);
void makeHttpRequest(String url);
void downloadFile(String url, String localPath);
// Logging
void logInfo(String message);
void logError(String message);
void logWarning(String message);
// Konfiguration
void setProperty(String key, String value);
String getProperty(String key);
Map<String, String> getAllProperties();
// Sicherheit
void encryptFile(String filePath);
void decryptFile(String filePath);
String generateHash(String input);
}
Проблемы при реализации
// Problem: Klasse muss alles implementieren, auch was nicht benötigt wird
public class SimpleUserManager implements UniversalSystemManager {
@Override
public void createUser(String username, String password) {
// Eigentliche Funktionalität
System.out.println("Creating user: " + username);
}
@Override
public void deleteUser(String userId) {
System.out.println("Deleting user: " + userId);
}
@Override
public User getUser(String userId) {
return new User(userId, "user" + userId);
}
@Override
public List<User> getAllUsers() {
return Arrays.asList(new User("1", "Alice"), new User("2", "Bob"));
}
@Override
public boolean authenticateUser(String username, String password) {
return "admin".equals(username) && "password".equals(password);
}
// Unnötige Implementierungen - werden nicht benötigt!
@Override
public void connectToDatabase(String url, String user, String password) {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public void executeQuery(String sql) {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public void closeConnection() {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public void backupDatabase() {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public void restoreDatabase(String backupFile) {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public void createFile(String path, String content) {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public void deleteFile(String path) {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public File readFile(String path) {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public void createDirectory(String path) {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public void sendEmail(String to, String subject, String body) {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public void makeHttpRequest(String url) {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public void downloadFile(String url, String localPath) {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public void logInfo(String message) {
System.out.println("INFO: " + message);
}
@Override
public void logError(String message) {
System.err.println("ERROR: " + message);
}
@Override
public void logWarning(String message) {
System.out.println("WARNING: " + message);
}
@Override
public void setProperty(String key, String value) {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public String getProperty(String key) {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public Map<String, String> getAllProperties() {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public void encryptFile(String filePath) {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public void decryptFile(String filePath) {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
@Override
public String generateHash(String input) {
throw new UnsupportedOperationException("Not supported by SimpleUserManager");
}
}
Interface Segregation Principle (ISP)
Хороший дизайн: разделённые интерфейсы
// Gutes Design: Spezialisierte Interfaces nach Verantwortlichkeit
// 1. Benutzer-Management
public interface UserManager {
void createUser(String username, String password);
void deleteUser(String userId);
User getUser(String userId);
List<User> getAllUsers();
boolean authenticateUser(String username, String password);
}
// 2. Datenbank-Management
public interface DatabaseManager {
void connectToDatabase(String url, String user, String password);
void executeQuery(String sql);
void closeConnection();
void backupDatabase();
void restoreDatabase(String backupFile);
}
// 3. Dateisystem-Management
public interface FileSystemManager {
void createFile(String path, String content);
void deleteFile(String path);
File readFile(String path);
void createDirectory(String path);
}
// 4. Netzwerk-Management
public interface NetworkManager {
void sendEmail(String to, String subject, String body);
void makeHttpRequest(String url);
void downloadFile(String url, String localPath);
}
// 5. Logging
public interface Logger {
void logInfo(String message);
void logError(String message);
void logWarning(String message);
}
// 6. Konfigurations-Management
public interface ConfigurationManager {
void setProperty(String key, String value);
String getProperty(String key);
Map<String, String> getAllProperties();
}
// 7. Sicherheits-Management
public interface SecurityManager {
void encryptFile(String filePath);
void decryptFile(String filePath);
String generateHash(String input);
}
Реализация с конкретными интерфейсами
// Реализация только необходимых интерфейсов
public class SimpleUserManager implements UserManager, Logger {
private Map<String, User> users = new HashMap<>();
@Override
public void createUser(String username, String password) {
User user = new User(generateUserId(), username);
users.put(user.getId(), user);
logInfo("User created: " + username);
}
@Override
public void deleteUser(String userId) {
User user = users.remove(userId);
if (user != null) {
logInfo("User deleted: " + user.getUsername());
}
}
@Override
public User getUser(String userId) {
return users.get(userId);
}
@Override
public List<User> getAllUsers() {
return new ArrayList<>(users.values());
}
@Override
public boolean authenticateUser(String username, String password) {
return users.values().stream()
.anyMatch(user -> user.getUsername().equals(username) &&
user.getPassword().equals(password));
}
// Logger реализация
@Override
public void logInfo(String message) {
System.out.println("INFO: " + message);
}
@Override
public void logError(String message) {
System.err.println("ERROR: " + message);
}
@Override
public void logWarning(String message) {
System.out.println("WARNING: " + message);
}
private String generateUserId() {
return "user_" + System.currentTimeMillis();
}
}
// Сложная реализация с несколькими интерфейсами
public class EnterpriseSystemManager implements UserManager, DatabaseManager,
FileSystemManager, NetworkManager,
Logger, ConfigurationManager, SecurityManager {
private UserManager userManager;
private DatabaseManager databaseManager;
private FileSystemManager fileSystemManager;
private NetworkManager networkManager;
private Logger logger;
private ConfigurationManager configManager;
private SecurityManager securityManager;
public EnterpriseSystemManager() {
// Делегирование специализированным классам
this.userManager = new DatabaseBackedUserManager();
this.databaseManager = new PostgreSQLManager();
this.fileSystemManager = new LocalFileSystemManager();
this.networkManager = new HTTPClientManager();
this.logger = new FileLogger();
this.configManager = new PropertiesConfigManager();
this.securityManager = new AESecurityManager();
}
// Делегирование специализированным реализациям
@Override
public void createUser(String username, String password) {
userManager.createUser(username, password);
}
@Override
public void deleteUser(String userId) {
userManager.deleteUser(userId);
}
@Override
public User getUser(String userId) {
return userManager.getUser(userId);
}
@Override
public List<User> getAllUsers() {
return userManager.getAllUsers();
}
@Override
public boolean authenticateUser(String username, String password) {
return userManager.authenticateUser(username, password);
}
@Override
public void connectToDatabase(String url, String user, String password) {
databaseManager.connectToDatabase(url, user, password);
}
@Override
public void executeQuery(String sql) {
databaseManager.executeQuery(sql);
}
@Override
public void closeConnection() {
databaseManager.closeConnection();
}
@Override
public void backupDatabase() {
databaseManager.backupDatabase();
}
@Override
public void restoreDatabase(String backupFile) {
databaseManager.restoreDatabase(backupFile);
}
// ... дальнейшие делегирования
}
Рефакторинг God Interfaces
Пошаговый рефакторинг
// Шаг 1: Определение ответственности
public class RefactoringExample {
// Исходный God Interface
public interface GodService {
// Операции с пользователями
User findUser(String id);
void saveUser(User user);
// Операции с продуктами
Product findProduct(String id);
List<Product> searchProducts(String query);
// Операции с заказами
Order createOrder(Order order);
void updateOrderStatus(String orderId, String status);
// Операции платежей
Payment processPayment(Payment payment);
void refundPayment(String paymentId);
// Операции уведомлений
void sendEmail(String to, String subject, String body);
void sendSMS(String to, String message);
}
// Шаг 2: Разделение интерфейсов по ответственности
public interface UserService {
User findUser(String id);
void saveUser(User user);
}
public interface ProductService {
Product findProduct(String id);
List<Product> searchProducts(String query);
}
public interface OrderService {
Order createOrder(Order order);
void updateOrderStatus(String orderId, String status);
}
public interface PaymentService {
Payment processPayment(Payment payment);
void refundPayment(String paymentId);
}
public interface NotificationService {
void sendEmail(String to, String subject, String body);
void sendSMS(String to, String message);
}
// Шаг 3: Специализированные реализации
public class DatabaseUserService implements UserService {
private UserRepository userRepository;
@Override
public User findUser(String id) {
return userRepository.findById(id);
}
@Override
public void saveUser(User user) {
userRepository.save(user);
}
}
public class EmailNotificationService implements NotificationService {
private EmailSender emailSender;
private SMSSender smsSender;
@Override
public void sendEmail(String to, String subject, String body) {
emailSender.send(to, subject, body);
}
@Override
public void sendSMS(String to, String message) {
smsSender.send(to, message);
}
}
// Шаг 4: Фасад для упрощённого использования (необязательно)
public class ECommerceFacade {
private UserService userService;
private ProductService productService;
private OrderService orderService;
private PaymentService paymentService;
private NotificationService notificationService;
public ECommerceFacade(UserService userService, ProductService productService,
OrderService orderService, PaymentService paymentService,
NotificationService notificationService) {
this.userService = userService;
this.productService = productService;
this.orderService = orderService;
this.paymentService = paymentService;
this.notificationService = notificationService;
}
// Высокоуровневые операции
public Order placeOrder(String userId, String productId, PaymentDetails paymentDetails) {
User user = userService.findUser(userId);
Product product = productService.findProduct(productId);
Order order = orderService.createOrder(new Order(user, product));
Payment payment = paymentService.processPayment(
new Payment(order, paymentDetails)
);
if (payment.isSuccessful()) {
orderService.updateOrderStatus(order.getId(), "PAID");
notificationService.sendEmail(user.getEmail(),
"Order Confirmation", "Your order " + order.getId() + " is confirmed");
}
return order;
}
}
}
Лучшие практики проектирования интерфейсов
1. Интерфейсы, основанные на ролях
// Interfaces basierend auf Rollen statt Implementierungen
public interface Readable {
String read();
}
public interface Writable {
void write(String content);
}
public interface Appendable {
void append(String content);
}
public interface Flushable {
void flush();
}
// Kombination von Interfaces
public interface FileOperations extends Readable, Writable, Appendable, Flushable {
String getPath();
long getSize();
}
public interface NetworkOperations extends Readable, Writable {
String getRemoteAddress();
int getPort();
}
Такой подход фокусирует внимание на том, что может делать объект, а не на его внутреннем устройстве. Каждый интерфейс описывает одну отдельную способность. Когда нужна комбинация функций, вы просто наследуете сразу несколько интерфейсов.
2. Маркирующие интерфейсы
// Marker Interfaces für spezielle Eigenschaften
public interface Serializable {
}
public interface Cloneable {
}
public interface Remote {
}
// Eigene Marker Interfaces
public interface Auditable {
}
public interface Cacheable {
}
public interface Secure {
}
// Verwendung
public class Document implements Serializable, Auditable, Cacheable {
// Implementierung
}
Маркирующие интерфейсы не содержат методов. Они служат меткой, которая говорит фреймворку или другому коду: “Этот класс поддерживает что-то специальное”. Например, класс, реализующий Serializable, можно сохранять в файл. Класс с меткой Cacheable можно добавлять в кэш.
3. Функциональные интерфейсы
// Spezialisierte Functional Interfaces
@FunctionalInterface
public interface UserPredicate {
boolean test(User user);
}
@FunctionalInterface
public interface UserFunction<T> {
T apply(User user);
}
@FunctionalInterface
public interface UserConsumer {
void accept(User user);
}
// Verwendung
public class UserProcessor {
public List<User> filterUsers(List<User> users, UserPredicate predicate) {
return users.stream()
.filter(predicate::test)
.collect(Collectors.toList());
}
public void processUsers(List<User> users, UserConsumer consumer) {
users.forEach(consumer::accept);
}
}
Функциональные интерфейсы содержат ровно один абстрактный метод. Благодаря аннотации @FunctionalInterface вы можете использовать их с лямбда-выражениями, что делает код компактнее и понятнее.
Выявление и предотвращение God Interfaces
Признаки God Interface
public class GodInterfaceDetector {
// 1. Слишком много методов в интерфейсе
public boolean hasTooManyMethods(Class<?> interfaceClass) {
return interfaceClass.isInterface() &&
interfaceClass.getMethods().length > 10;
}
// 2. Несколько разных ответственностей
public boolean hasMultipleResponsibilities(Class<?> interfaceClass) {
if (!interfaceClass.isInterface()) return false;
Method[] methods = interfaceClass.getMethods();
Set<String> responsibilities = new HashSet<>();
for (Method method : methods) {
String responsibility = extractResponsibility(method);
responsibilities.add(responsibility);
}
return responsibilities.size() > 3;
}
private String extractResponsibility(Method method) {
String name = method.getName().toLowerCase();
if (name.contains("user") || name.contains("customer")) return "user-management";
if (name.contains("product") || name.contains("item")) return "product-management";
if (name.contains("order") || name.contains("sale")) return "order-management";
if (name.contains("payment") || name.contains("transaction")) return "payment-management";
if (name.contains("email") || name.contains("notification")) return "notification";
if (name.contains("database") || name.contains("query")) return "database";
if (name.contains("file") || name.contains("directory")) return "filesystem";
if (name.contains("log") || name.contains("error")) return "logging";
if (name.contains("config") || name.contains("property")) return "configuration";
if (name.contains("security") || name.contains("encrypt")) return "security";
return "other";
}
// 3. Реализации с множеством UnsupportedOperationException
public boolean hasManyUnsupportedOperations(Class<?> implementationClass) {
Method[] methods = implementationClass.getMethods();
long unsupportedCount = Arrays.stream(methods)
.filter(this::isUnsupportedOperation)
.count();
return unsupportedCount > methods.length * 0.3; // > 30% unsupported
}
private boolean isUnsupportedOperation(Method method) {
try {
if (method.getDeclaringClass().isInterface()) {
return method.getAnnotation(Deprecated.class) != null ||
method.getName().startsWith("not") ||
method.getName().contains("Unsupported");
}
} catch (Exception e) {
return false;
}
return false;
}
}
Когда интерфейс становится слишком большим и охватывает много не связанных между собой задач, это верный признак того, что нужен рефакторинг. Если класс реализует интерфейс, но выбрасывает исключение UnsupportedOperationException для половины методов, значит интерфейс не соответствует своему назначению.
Стратегии рефакторинга
public class InterfaceRefactoringStrategies {
// 1. Выделение интерфейса
public interface UserManagement {
void createUser(User user);
void updateUser(User user);
void deleteUser(String userId);
User getUser(String userId);
List<User> getAllUsers();
}
public interface Authentication {
boolean login(String username, String password);
void logout(String userId);
boolean isAuthenticated(String userId);
String getCurrentUser();
}
public interface Authorization {
boolean hasPermission(String userId, String permission);
void grantPermission(String userId, String permission);
void revokePermission(String userId, String permission);
List<String> getPermissions(String userId);
}
// 2. Разделение интерфейса
public interface ReadOnlyRepository<T, ID> {
T findById(ID id);
List<T> findAll();
boolean existsById(ID id);
long count();
}
public interface WriteOnlyRepository<T, ID> {
<S extends T> S save(S entity);
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
void deleteById(ID id);
void delete(T entity);
void deleteAll();
}
public interface Repository<T, ID> extends ReadOnlyRepository<T, ID>, WriteOnlyRepository<T, ID> {
}
// 3. Template Method Pattern для интерфейсов
public interface DataAccessTemplate {
default <T> T executeInTransaction(Supplier<T> operation) {
beginTransaction();
try {
T result = operation.get();
commit();
return result;
} catch (Exception e) {
rollback();
throw new RuntimeException("Transaction failed", e);
}
}
void beginTransaction();
void commit();
void rollback();
}
}
Первая стратегия, выделение интерфейса, состоит в том, чтобы разбить God Interface на несколько более узких. Вторая, разделение интерфейса, отделяет операции чтения от записи. Третья использует методы по умолчанию для инкапсуляции общей логики.
Тестирование интерфейсов
Unit-тесты для проектирования интерфейсов
public class InterfaceDesignTest {
@Test
public void testUserServiceInterface() {
UserService userService = new MockUserService();
// Проверяем, что все методы работают
User user = new User("1", "test@example.com");
userService.saveUser(user);
User retrieved = userService.findUser("1");
assertNotNull(retrieved);
assertEquals("test@example.com", retrieved.getEmail());
}
@Test
public void testInterfaceSegregation() {
// Проверяем, что реализации имеют только нужные методы
UserService userService = new SimpleUserService();
// Должно работать
assertDoesNotThrow(() -> userService.saveUser(new User("1", "test")));
// Не должно быть
assertFalse(hasMethod(userService, "sendEmail"));
}
private boolean hasMethod(Object obj, String methodName) {
return Arrays.stream(obj.getClass().getMethods())
.anyMatch(m -> m.getName().equals(methodName));
}
// Mock-реализация для тестирования
private static class MockUserService implements UserService {
private Map<String, User> users = new HashMap<>();
@Override
public User findUser(String id) {
return users.get(id);
}
@Override
public void saveUser(User user) {
users.put(user.getId(), user);
}
@Override
public void updateUser(User user) {
users.put(user.getId(), user);
}
@Override
public void deleteUser(String userId) {
users.remove(userId);
}
@Override
public List<User> getAllUsers() {
return new ArrayList<>(users.values());
}
}
}
При тестировании интерфейсов важно проверить две вещи. Первое, что методы интерфейса действительно работают как задумано. Второе, что класс не имеет лишних методов, которые не нужны для его роли. Mock-объекты помогают тестировать интерфейсы в изоляции, не зависимости от конкретной реализации.
Концепции, актуальные для экзамена
Ключевые принципы SOLID
- S - Single Responsibility: каждый класс отвечает за одно
- O - Open/Closed: открыт для расширения, закрыт для изменений
- L - Liskov Substitution: подклассы могут заменять базовые классы
- I - Interface Segregation: никаких перегруженных интерфейсов
- D - Dependency Inversion: зависимость от абстракций
Типовые экзаменационные задачи
- Выявите перегруженные интерфейсы
- Отрефакторьте перегруженные интерфейсы
- Применяйте Interface Segregation Principle
- Проектируйте специализированные интерфейсы
- Тестируйте проектирование интерфейсов
Итоги
Перегруженные интерфейсы считаются опасным антипаттерном:
- Проблемы: высокая связанность, плохая тестируемость, лишние зависимости
- Решение: применение Interface Segregation Principle
- Подход: выявление и разделение ответственности
- Результат: небольшие, сфокусированные интерфейсы с четкой ответственностью
Хорошее проектирование интерфейсов лежит в основе поддерживаемых и тестируемых программных архитектур.
Рекомендуемая литература: Design Patterns
Keine Bücher für Kategorie "design-patterns" gefunden.

