Пример «плохого» класса, нарушающего принципы SOLID, кратко объяснение проблем и предложенный рефакторинг. Плохой класс (смешивает много обязанностей — чтение данных, форматирование, запись в файл, отправку почты): ``` public class ReportManager { public void generateAndSendReport() { // 1. Получить данные из БД List rows = new ArrayList(); try (Connection c = DriverManager.getConnection("jdbc:db://...")) { Statement s = c.createStatement(); ResultSet rs = s.executeQuery("SELECT name, value FROM items"); while (rs.next()) { rows.add(rs.getString("name") + "," + rs.getString("value")); } } catch (SQLException e) { e.printStackTrace(); } // 2. Сформировать CSV StringBuilder csv = new StringBuilder(); csv.append("name,value\n"); for (String r : rows) csv.append(r).append("\n"); // 3. Сохранить в файл try (FileWriter fw = new FileWriter("/tmp/report.csv")) { fw.write(csv.toString()); } catch (IOException e) { e.printStackTrace(); } // 4. Отправить по почте Properties props = new Properties(); props.put("mail.smtp.host", "smtp.example.com"); Session session = Session.getInstance(props); try { Message msg = new MimeMessage(session); msg.setFrom(new InternetAddress("noreply@example.com")); msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse("user@example.com")); msg.setSubject("Report"); msg.setText("See attached"); // прикрепление файла опущено Transport.send(msg); } catch (MessagingException e) { e.printStackTrace(); } } } ``` Какие принципы SOLID нарушены и почему: - Single Responsibility Principle: класс делает сразу несколько задач — доступ к данным, форматирование, сохранение, отправка почты. Изменение любого поведения (новая БД, новый формат, другой способ доставки) потребует правок в одном классе. - Open/Closed Principle: чтобы добавить новый формат (JSON, XML) или сменить доставку (SFTP), нужно менять код метода, а не расширять поведение через новые реализации. - Dependency Inversion: класс жестко зависит от конкретных механизмов (JDBC, FileWriter, JavaMail), а не от абстракций — тестирование и замена реализаций затруднены. - Interface Segregation (в косвенной форме): один класс вынужден «знать» и реализовывать всё, чего клиентам может не требоваться. Рефакторинг: разделить обязанности через интерфейсы, использовать внедрение зависимостей (constructor injection). Короткая схема. Интерфейсы: ``` public interface DataProvider { List fetch(); } public interface Formatter { byte[] format(List data); } public interface Storage { void save(String path, byte[] content); } public interface Notifier { void send(String subject, String body, String attachmentPath); } ``` Примеры реализаций (кратко): ``` public class DatabaseProvider implements DataProvider { public List fetch() { // реализация JDBC... } } public class CsvFormatter implements Formatter { public byte[] format(List data) { // собрать CSV и вернуть bytes } } public class FileStorage implements Storage { public void save(String path, byte[] content) { Files.write(Paths.get(path), content); } } public class SmtpNotifier implements Notifier { public void send(String subject, String body, String attachmentPath) { // реализация JavaMail } } ``` Высокоуровневый класс, зависящий от абстракций: ``` public class ReportService { private final DataProvider provider; private final Formatter formatter; private final Storage storage; private final Notifier notifier; public ReportService(DataProvider provider, Formatter formatter, Storage storage, Notifier notifier) { this.provider = provider; this.formatter = formatter; this.storage = storage; this.notifier = notifier; } public void generateAndSend(String path) { List data = provider.fetch(); byte[] content = formatter.format(data); storage.save(path, content); notifier.send("Report", "See attached", path); } } ``` Преимущества рефакторинга: - Соблюдается Single Responsibility: каждый класс — одна ответственность. - Open/Closed: добавить новый формат или способ доставки — реализовать новый Formatter или Notifier без правки ReportService. - Dependency Inversion: ReportService зависит от абстракций (интерфейсов), не от конкретных библиотек. - Легче тестировать (подменять мок-реализации интерфейсов), расширять и поддерживать. Краткая рекомендация: выделяйте границы ответственности сразу, применяйте интерфейсы и внедрение зависимостей — это значительно уменьшает связность и повышает гибкость.
Плохой класс (смешивает много обязанностей — чтение данных, форматирование, запись в файл, отправку почты):
```
public class ReportManager {
public void generateAndSendReport() {
// 1. Получить данные из БД
List rows = new ArrayList();
try (Connection c = DriverManager.getConnection("jdbc:db://...")) {
Statement s = c.createStatement();
ResultSet rs = s.executeQuery("SELECT name, value FROM items");
while (rs.next()) {
rows.add(rs.getString("name") + "," + rs.getString("value"));
}
} catch (SQLException e) {
e.printStackTrace();
}
// 2. Сформировать CSV
StringBuilder csv = new StringBuilder();
csv.append("name,value\n");
for (String r : rows) csv.append(r).append("\n");
// 3. Сохранить в файл
try (FileWriter fw = new FileWriter("/tmp/report.csv")) {
fw.write(csv.toString());
} catch (IOException e) {
e.printStackTrace();
}
// 4. Отправить по почте
Properties props = new Properties();
props.put("mail.smtp.host", "smtp.example.com");
Session session = Session.getInstance(props);
try {
Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress("noreply@example.com"));
msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse("user@example.com"));
msg.setSubject("Report");
msg.setText("See attached");
// прикрепление файла опущено
Transport.send(msg);
} catch (MessagingException e) {
e.printStackTrace();
}
}
}
```
Какие принципы SOLID нарушены и почему:
- Single Responsibility Principle: класс делает сразу несколько задач — доступ к данным, форматирование, сохранение, отправка почты. Изменение любого поведения (новая БД, новый формат, другой способ доставки) потребует правок в одном классе.
- Open/Closed Principle: чтобы добавить новый формат (JSON, XML) или сменить доставку (SFTP), нужно менять код метода, а не расширять поведение через новые реализации.
- Dependency Inversion: класс жестко зависит от конкретных механизмов (JDBC, FileWriter, JavaMail), а не от абстракций — тестирование и замена реализаций затруднены.
- Interface Segregation (в косвенной форме): один класс вынужден «знать» и реализовывать всё, чего клиентам может не требоваться.
Рефакторинг: разделить обязанности через интерфейсы, использовать внедрение зависимостей (constructor injection). Короткая схема.
Интерфейсы:
```
public interface DataProvider {
List fetch();
}
public interface Formatter {
byte[] format(List data);
}
public interface Storage {
void save(String path, byte[] content);
}
public interface Notifier {
void send(String subject, String body, String attachmentPath);
}
```
Примеры реализаций (кратко):
```
public class DatabaseProvider implements DataProvider {
public List fetch() {
// реализация JDBC...
}
}
public class CsvFormatter implements Formatter {
public byte[] format(List data) {
// собрать CSV и вернуть bytes
}
}
public class FileStorage implements Storage {
public void save(String path, byte[] content) {
Files.write(Paths.get(path), content);
}
}
public class SmtpNotifier implements Notifier {
public void send(String subject, String body, String attachmentPath) {
// реализация JavaMail
}
}
```
Высокоуровневый класс, зависящий от абстракций:
```
public class ReportService {
private final DataProvider provider;
private final Formatter formatter;
private final Storage storage;
private final Notifier notifier;
public ReportService(DataProvider provider, Formatter formatter, Storage storage, Notifier notifier) {
this.provider = provider;
this.formatter = formatter;
this.storage = storage;
this.notifier = notifier;
}
public void generateAndSend(String path) {
List data = provider.fetch();
byte[] content = formatter.format(data);
storage.save(path, content);
notifier.send("Report", "See attached", path);
}
}
```
Преимущества рефакторинга:
- Соблюдается Single Responsibility: каждый класс — одна ответственность.
- Open/Closed: добавить новый формат или способ доставки — реализовать новый Formatter или Notifier без правки ReportService.
- Dependency Inversion: ReportService зависит от абстракций (интерфейсов), не от конкретных библиотек.
- Легче тестировать (подменять мок-реализации интерфейсов), расширять и поддерживать.
Краткая рекомендация: выделяйте границы ответственности сразу, применяйте интерфейсы и внедрение зависимостей — это значительно уменьшает связность и повышает гибкость.