Методы разработки ПО

Специальность "Программная инжерения"

Димитров Вячеслав Михайлович, старший преподаватель кафедры ИМО, dimitrov@cs.petrsu.ru

Темы

  1. ООП и проектирование кода. Принципы SOLID.
  2. Шаблоны проектирования.
  3. Архитектура пользовательского интерфейса.
  4. Многоуровневая архитектура.
  5. Предметно-ориентированное проектирование.
  6. Архитектура распределенных приложений.

Лабораторные работы

  1. Реализовать прикладную систему с использованием ООП. Язык не имеет значения. Срок: 01.03
  2. Привести к принципам SOLID. Срок: 15.03.
  3. Применить шаблоны проектирования. Срок: 29.03.
  4. Применить шаблон к пользовательскому интерфейсу. Срок: 19.04.
  5. Перевести на микросервисы. Срок: 15.05.
  6. Все задачи сданы - зачет.

Литература

  1. Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидс "Паттерны проектирования".
  2. Booch et al. Object-Oriented Analysis and Design with Applications (3rd Edition), 2007.

ООП по Алан Кей

  1. Все есть объект.
  2. Программа — совокупность объектов, указывающих друг другу что делать.
  3. Каждый объект имеет свою собственную «память» состоящую из других объектов.
  4. У каждого объекта есть тип.
  5. Все объекты одного типа могут получать одинаковые сообщения.

Класс.

  1. Имя — обычно существительное (возможно, полученное в результате номинализации).
  2. Экземпляры — объекты.
  3. Поля — состояние.
  4. Методы — поведение,
  5. Инварианты (непротиворечивое внутреннее состояние объекта).

Объект.

  1. Тип.
  2. Идентичность и состояние.
  3. Изменяемость.
  4. Логическое и физическое состояние объекта.

Объект-значение.

  1. Простейший объект.
  2. Идентификация по состоянию, а не по идентификатору.
  3. Переопределена операция сравнения:  два объекта равны, если их состояния равны.

Объект-значение. Пример.


package ru.nest.son;

import java.util.Objects;

public class Room {
    private String number;
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Room room = (Room) o;
        return number.equals(room.number);
    }

    @Override
    public int hashCode() {
        return Objects.hash(number);
    }
}

Объект-ссылка.

  1. Сложный объект
  2. Много логики и данных.
  3. Идентичность по ссылке.

Инкапсуляция.

  1. Возможность скрыть поля и реализацию от внешнего мира и классов наследников.
  2. Три модификатора: public, protected, private.
  3. В каждом языке свои особенности.

Наследование.

  1. Возможность использования полей и методов родителя(ей).
  2. Определение нового типа путем расширения или модификации существующего.
  3. Множественное наследование (семантическая неопределенность: проблема ромба).

Три вида наследования.

  1. Специализация - специфичная разновидность (другие свойства).
  2. Замещение - другое поведение.
  3. Реализация - совместное использование кода.

Плоскости объектов.

  1. Плоскость предметной области.
  2. Плоскость реализации (пример: сохранение в определенном формате).
  3. Стараться не пересекать плоскости при наследовании.

Принципы SOLID.

  1. ООП не гарантирует понятный и хорошо спроектированный код.
  2. Роберт Мартин разработал принципы для программирования и проектирования.

S: Single Responsibility Principle.

  1. Принцип единственной ответственности.
  2. Каждый класс должен решать лишь одну задачу.
  3. Если класс решает несколько проблем, то его подсистемы связаны.
  4. Изменения в одной ведут к изменениям в другой.

S: Пример.


package ru.nest.son;

public class Room {
    private String number;
    
    public String getNumber() {}
    public void setNumber(String newNumber) {}

    public int saveToDB() {
	// ... Код для сохранения в БД.
    }
}

S: Пример.


package ru.nest.son;

public class Floor {
    private int number;
    
    public int getNumber() {}
    public setNumber(int newNumber) {}

    public int saveToDB() {
	// ... Код для сохранения в БД.
    }
}

S: Как можно было бы решить проблему.


// Model - интерфейс.
public class Room extends Model {
    private String number;
    // ...
}

public class Floor extends Model {
    private int number;
    // ...
}

public class DBHellper {
    public int saveToDB(Model o) {
	// Код для сохранения в БД.
    }
}

S: или так.


// Model - интерфейс.
public class  Model {
    public int saveToDB() {
	// Код для сохранения в БД.
    }
}
public class Room extends Model {
    private String number;
    // ...
}

public class Floor extends Model {
    private int number;
    // ...
}

O: Open-Closed Principle

  1. Принцип открытости-закрытости.
  2. Программные сущности должны быть открыты для расширения, но не для модификации.
  3. Сущности могут менять свое поведение без изменения кода.
  4. ПО меняется не через изменение существующего кода, а через добавление нового.

O: пример.


					
public class Floor extends Model {
    private int number;
    // ...
    private String up;
    public String getUp() {
	return up;
    }
}

					

O: пример.


public function handleUp(floors: ArrayList) {
    for(Floor f: floors) {
	if (r.getUp().equals('stairs')) {
	    calculateTime('slow', 'hard');
	} else if (r.getUp().equals('escalator')) {
	    calculateTime('slow', 'easy');
	}
    }
}
					

O: пример.


public function handleUp(floors: ArrayList) {
    for(Floor f: floors) {
	if (r.getUp().equals('stairs')) {
	    handleUp('slow', 'hard');
	} else if (r.getUp().equals('escalator')) {
	    handleUp('slow', 'easy');
	} else if (r.getUp().equals('elevator')) {
	    handleUp('quickly', 'easy');
	}
    }
}
					

S: Пример.


package ru.nest.son;

public class Floor {
    private Up up;
}

abstract class Up {
    abstract public String getSpeed();
    abstract public String getComplexity();
}

S: Пример.


package ru.nest.son;

public class Stairs extends Up {
    public String getSpeed() {
	return "slow";
    }
    public String getComplexity() {
	return "hard";
    }
}

S: Пример.


package ru.nest.son;

public class Escalator extends Up {
    public String getSpeed() {
	return "slow";
    }
    public String getComplexity() {
	return "easy";
    }
}

O: пример.


public function handleUp(floors: ArrayList) {
    for(Floor f: floors) {
	calculateTime(f.getUp().getSpeed(), f.getUp().getComplexity());
    }
}
					

L: Liskov Substitution Principle

  1. Принцип подстановки Барбары Лисков
  2. Функции, использующие базовый тип, должны иметь возможность использовать подтипы не зная об этом.
  3. Подтипы должны корректно реализовывать поведение базового типа.
  4. Если идет проверка типа, то принцип LSP нарушается.

L: пример.


public function handleUp(floors: ArrayList) {
    for(Floor f: floors) {
	if (f.getUp() instanceof Stairs) {
	    calculateSafity(0.5);
	} else if (r.getUp() instanceof Escalator) {
	    calculateSafity(0.1);
    }
}
					

L: Пример.


abstract class Up {
    abstract public String getSpeed();
    abstract public String getComplexity();
    abstract public float getSafity();
}

L: пример.


public function handleUp(floors: ArrayList) {
    for(Floor f: floors) {
	calculateSafity(f.getUp().getSafity());
    }
}
					

I: Interface Segregation Principle

  1. Принцип разделения интерфейса.
  2. Интерейсы должны быть предназначены для конкретного клиента.
  3. Клиенты не должны зависеть от интерфейсов, которые они не используют.

I: пример.


abstract class Shape {
    abstract public void drawCircle();
    abstract public void drawSquare();
    abstract public void drawRectangle();
}
					

I: пример.


public class Circle implements Shape {
    public void drawCircle() {
	// ...
    }

    public void drawSquare() {
	// ...
    }

    public void drawRectangle() {
	// ...
    }
}
					

I: пример.


public class Square implements Shape {
    public void drawCircle() {
	// ...
    }

    public void drawSquare() {
	// ...
    }

    public void drawRectangle() {
	// ...
    }
}
					

I: пример.


abstract class Shape {
    abstract public void draw();
}
abstract class ICircle {
    abstract public void drawCircle();
}
abstract class ISquare {
    abstract public void drawSquare();
}
abstract class IRectangle {
    abstract public void drawRectangle();
}
					

I: пример.


public class Circle implements ICircle {
    public void drawCircle() {
	// ...
    }
}
					

I: пример.


public class Square implements ISquare {
    public void drawSquare() {
	// ...
    }
}
					

D: Dependency Inversion Principle

  1. Принцип инверсии зависимостей
  2. Объектом зависимости должна быть абстракция, а не что-то конкретное.
  3. Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
  4. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
  5. Упрощает замену компонент.

D: пример.


public class Saver {
    XMLModel model = null;
    public Saver(XMLModel model) {
	this.model = model;
    }
    public void saveFloor(Floor f) {
	save(model.transform(f));
    }
}
					

D: пример.


abstract class IModel {
    abstract function View transform(Model);
}
public class XMLModel extends IModel {
    function View transform(Model) {
	// ...
    }
}
public class JSONModel extends IModel {
    function View transform(Model) {
	// ...
    }
}
					

D: пример.


public class Saver {
    IModel model = null;
    public Saver(IModel model) {
	this.model = model;
    }
    public void saveFloor(Floor f) {
	save(model.transform(f));
    }
}