SOLID — это набор из пяти принципов объектно-ориентированного программирования и проектирования, которые помогают создавать гибкие, поддерживаемые и расширяемые системы. Эти принципы были введены Робертом Мартином и являются фундаментальными для написания качественного кода. Давайте рассмотрим каждый из них и приведем примеры их применения.
Определение: Класс должен иметь только одну причину для изменения, то есть выполнять только одну задачу.
class Report {
public void generateReport() {
// Генерация отчета
}
public void saveToFile(String filePath) {
// Сохранение отчета в файл
}
}
В этом примере класс Report
отвечает и за генерацию отчета, и за его сохранение, что нарушает SRP.
class Report {
public void generateReport() {
// Генерация отчета
}
}
class ReportSaver {
public void saveToFile(Report report, String filePath) {
// Сохранение отчета в файл
}
}
Теперь каждый класс отвечает только за одну задачу: Report
— за генерацию, а ReportSaver
— за сохранение.
Определение: Программные сущности (классы, модули, функции) должны быть открыты для расширения, но закрыты для модификации.
class Rectangle {
public double width;
public double height;
}
class AreaCalculator {
public double calculateArea(Object shape) {
if (shape instanceof Rectangle) {
Rectangle rect = (Rectangle) shape;
return rect.width * rect.height;
}
// Добавление нового типа фигуры требует изменения этого метода
}
}
Этот код нарушает OCP, так как добавление новой фигуры требует изменения метода calculateArea
.
interface Shape {
double calculateArea();
}
class Rectangle implements Shape {
public double width;
public double height;
@Override
public double calculateArea() {
return width * height;
}
}
class Circle implements Shape {
public double radius;
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
class AreaCalculator {
public double calculateArea(Shape shape) {
return shape.calculateArea();
}
}
Теперь добавление новой фигуры не требует изменения класса AreaCalculator
.
Определение: Объекты в программе должны быть заменяемы экземплярами их подтипов без изменения правильности работы программы.
class Bird {
public void fly() {
System.out.println("Flying");
}
}
class Ostrich extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Ostrich can't fly");
}
}
Этот код нарушает LSP, так как Ostrich
не может заменить Bird
без изменения поведения программы.
class Bird {
}
class FlyingBird extends Bird {
public void fly() {
System.out.println("Flying");
}
}
class Ostrich extends Bird {
}
Теперь Ostrich
не нарушает поведение базового класса Bird
.
Определение: Клиенты не должны зависеть от интерфейсов, которые они не используют.
interface Worker {
void work();
void eat();
}
class HumanWorker implements Worker {
public void work() {
System.out.println("Working");
}
public void eat() {
System.out.println("Eating");
}
}
class RobotWorker implements Worker {
public void work() {
System.out.println("Working");
}
public void eat() {
throw new UnsupportedOperationException("Robots don't eat");
}
}
Этот код нарушает ISP, так как RobotWorker
вынужден реализовывать метод eat
, который ему не нужен.
interface Workable {
void work();
}
interface Eatable {
void eat();
}
class HumanWorker implements Workable, Eatable {
public void work() {
System.out.println("Working");
}
public void eat() {
System.out.println("Eating");
}
}
class RobotWorker implements Workable {
public void work() {
System.out.println("Working");
}
}
Теперь интерфейсы разделены, и каждый класс реализует только те методы, которые ему нужны.
Определение: Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба типа модулей должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
class LightBulb {
public void turnOn() {
System.out.println("LightBulb: On");
}
public void turnOff() {
System.out.println("LightBulb: Off");
}
}
class Switch {
private LightBulb bulb;
public Switch(LightBulb bulb) {
this.bulb = bulb;
}
public void operate() {
bulb.turnOn();
}
}
Этот код нарушает DIP, так как Switch
зависит от конкретного класса LightBulb
.
interface Switchable {
void turnOn();
void turnOff();
}
class LightBulb implements Switchable {
public void turnOn() {
System.out.println("LightBulb: On");
}
public void turnOff() {
System.out.println("LightBulb: Off");
}
}
class Fan implements Switchable {
public void turnOn() {
System.out.println("Fan: On");
}
public void turnOff() {
System.out.println("Fan: Off");
}
}
class Switch {
private Switchable device;
public Switch(Switchable device) {
this.device = device;
}
public void operate() {
device.turnOn();
}
}
Теперь Switch
зависит от абстракции Switchable
, что делает его более гибким.
Соблюдение принципов SOLID помогает создавать гибкие, поддерживаемые и расширяемые системы, что особенно важно в крупных проектах и при работе в команде.