SOLID: Object-Oriented Design Principles

Кирилл Корняков (Itseez, ННГУ)
Октябрь 2015

Объектно-ориентированный дизайн

Зависимости — корень многих проблем!

Болезни дизайна

Примеры проблем

SOLID principles

Single Responsibility Principle

Single Responsibility Principle

Программные сущности должны иметь только одну ответственность.

Не должно быть больше одной причины для изменения класса.

Пример

Пример

SOLID principles

Open/Closed Principle

"Программные сущности должны быть открыты для расширения,
но закрыты для модификации."

Б. Мейер, 1988 / Р. Мартин, 1996

Нет ли здесь противоречия? Как этого можно добиться?

Метафора OCP

Пример c явной зависимостью

ReportFormat format = TXT;
Reporter reporter(format);
...
reporter.getAnnualReport();
class Reporter {
    private TxtPrinter txtPrinter;
    private XmlPrinter xmlPrinter;

    Reporter(ReportFormat format) {
        if (format == TXT)
            txtPrinter = new TxtPrinter();
        else if (format == XML)
            xmlPrinter = new XmlPrinter();
    }

    void getAnnualReport() {
        AnnualReport report = new AnnualReport();

        if (txtPrinter)
            txtPrinter.print(report);
        else if (xmlPrinter)
            xmlPrinter.print(report);
    }
}

Пример с неявной зависимостью

IPrinter printer = new TxtPrinter();
Reporter reporter(printer);
...
reporter.getAnnualReport();
class Reporter {
    IPrinter printer;

    Reporter(IPrinter printer) {
        this.printer = printer;
    }

    void getAnnualReport() {
        AnnualReport report = new AnnualReport();
        printer.print(report);
    }
}

Стратегическое закрытие

"Ни одна значительная программа не может быть на 100% закрытой"

Р. Мартин, 1996

OCP: полезные советы

SOLID principles

Dependency Inversion Principle

Абстракции не должны зависеть от деталей.
Детали должны зависеть от абстракций.

Procedural Architecture

Procedural Architecture

Object-Oriented Architecture

Object-Oriented Architecture

Пример

enum OutputDevice { PRINTER, DISK };

void Copy(OutputDevice device) {
    int c;
    while ((c = ReadKeyboard()) != EOF)
        if (device == PRINTER)
            WritePrinter(c);
        else
            WriteDisk(c);
}

Пример

void Copy(Writer writer) {
    int c;
    while ((c = ReadKeyboard()) != EOF) {
        writer.Write(c);
    }
}

Пример


public interface IReader {
   char Read();
}
public interface IWriter {
   void Write(char c);
}

void Copy(IReader reader, IWriter writer) {
    int c;
    while ((c = reader.Read()) != EOF) {
        writer.Write(c);
    }
}

В чем преимущества такого подхода?

Внедрение зависимости (Dependency Injection)

Wikipedia

Пример (создание вручную)

public class MyApplication
{
    public static void main(String[] args)
    {
        // Явное конструирование
        ICar car = new DefaultCarImpl();

        // Использование
        car.setPedalPressure(5);
        float speed = car.getSpeed();
    }
}

Пример с использованием DI-фреймворка

public class MyApplication
{
    public static void main(String[] args)
    {
        // Неявное конструирование
        Service service = (Service)DependencyManager.get("CarBuilderService");
        ICar car = (ICar)service.getService(Car.class);

        // Использование
        car.setPedalPressure(5);
        float speed = car.getSpeed();
    }
}

Конфигурационный файл

    <service-point id="CarBuilderService">
        <invoke-factory>
            <construct class="Car">
                <service>DefaultCarImpl</service>
                <service>DefaultEngineImpl</service>
            </construct>
        </invoke-factory>
    </service-point>

SOLID principles

Interface Segregation Principle

Interface Segregation Principle

"Клиенты не должны зависеть от интерфейсов, которые они не используют"

Р. Мартин, 1996

Пример

Пример namespace utils.

Пример

Cлишком «толстые» интерфейсы необходимо разделять на более маленькие и специфические.

SOLID principles

Liskov Substitution Principle

Композиция vs Наследование

Liskov Substitution Principle

"Наследование должно гарантировать,
что любое свойство, справедливое для супертипа,
должно быть справедливо и для наследников."

Б. Лисков, 1987

Пример

Базовый класс

class Bird {                    // Есть клюв, крылья...
    public virtual void fly();  // Птица может летать
};

Наследник 1: Попугай

class Parrot : Bird {     // Попугай – птица
    public override void fly() { ... }
};

Метод, полиморфно использующий птицу

void PlayWithBird (Bird bird) {
    bird.Fly();
}

Пример

Метод, полиморфно использующий птицу

void PlayWithBird (Bird bird) {
    bird.Fly();
}

Наследник 2: Пингвин

class Penguin : Bird {
    public override void fly() {
        throw "Пингвины не летают!";
    }
};

Пример — решение

class Bird {
    // есть клюв, крылья...
};

class FlyingBird : Bird {
    public virtual void fly();  // птица может летать
};
class Parrot : FlyingBird {     // Попугай – летающая птица
 public override void fly() { ... }
};
class Penguin : Bird {
    // ...
};

Задача: квадрат — это прямоугольник?

class Rectangle {
    private double width;
    private double height;

    public void SetWidth(double w);
    public void SetHeight(double h);

    public double GetArea();
}
class Square : Rectangle {...}
void Adjust(Rectangle rect)
{
  rect.SetWidth(5);
  rect.SetHeight(4);

  // Чему будет равна площадь?
  double area = rect.GetArea();
}

Закон Деметры

Закон Деметры

Wikipedia

Следование принципам

Контрольные вопросы

План ответа про каждый из SOLID принципов:

  1. Формулировка, раскрыть все слова названия.
  2. Как достигается.
  3. Негативные последствия нарушения. К каким "болезням" дизайна может привести нарушение принципа?

Вопросы:

  1. Термины: жесткость, хрупкость, неподвижность, вязкость
  2. SRP
  3. OCP
  4. LSP
  5. ISP
  6. DIP
  7. Закон Деметры (что можно, чего нельзя)

Дополнительные вопросы

Следуй SOLID!

Спасибо!

Вопросы?