математическое обоснование solid принципов. Конференция...
TRANSCRIPT
Математическое обоснование
SOLID принципов
Евгений Тюменцев
HWdTech, LLC
hwdtech.ru
10-я конференция .NET разработчиков
19 апреля 2015
dotnetconf.ru
2
Докладчик
Тюменцев Евгений
14 лет преподаю
ИМИТ, ФКН ОмГУ
ИТ-компании
Школа программиста
10 лет разрабатываю ПО
разработчик, архитектор,
PM, руководство до 70 человек
3
SOLID
5 архитектурных принципов
1988 г. Б. Мейер впервые описал
1995-96 г. Р. Мартин популяризировал, ввел
термин SOLID
4
The Open-Closed Principle
Программные объекты должны быть
открыты для расширения, но в тоже время
закрыты для модификации.
5
The Liskov Substitution Principle
Функции, которые используют ссылки на
объекты базовых классов, должны
использовать объекты производных
классов, ничего не зная о них.
6
The Dependency Inversion Principle
Высокоуровневые компоненты не должны
зависеть от низкоуровневых компонент. И
те, и те должны зависеть от абстракций
(оператора расширения).
7
The Interface Segregation Principle
Класс не должен зависеть от интерфейсов,
которые он не использует
11
Выводимость
Пусть L – множество формул, B – формула.
Тогда L ⊦ B, если ∃ B1, B2, …, Bn , что
1.Bn – это B,
2.Bi – это либо формула из L, либо аксиома,
либо общезначимая формула, либо формула полученная припомощи правила вывода
14
Логика Хоара
1969 г. An Axiomatic Basis for Computer Programming
1971 г. Procedures and Parameters: An Axiomatic Approach
1980 г. премия Тьюринга
1990 г. Медаль “Пионер компьютерной техники”
2000 г. рыцарский титул за заслуги в области образования и компьютерной
техники, премия Киото
Чарльз Хоар
17
Аксиома оператора присваивания
Пример: {(y+1)*3+w*(y+1+3) ==z} x=y+1;
{ x*3+ w*(x+3)==z}
{P[E/x]} x := E {P}
19
Аксиома условного оператора
{B ^ P} S {Q}, {B’ ^P} T {Q}╞ {P} if B then S else T endif {Q}
{B ^ P} S {Q}╞ {P} if B then S endif {Q}
21
Аксиомы вывода
P1 → P, {P} S {Q}, Q → Q1 ╞ {P1} S {Q1}
P1 → P, {P} S {Q} ╞ {P1} S {Q}{P} S {Q}, Q → Q1 ╞ {P} S {Q1}
22
Акиомы вывода согласуются с
условным оператором
Пусть L = {{P} f {Q}}, B – предикат.
P^B→P (A3 ИВ)
{P^B}f{Q} (аксиома вывода)
{P}if (B) then f endif {Q} (аксиома условного
оператора)
24
Упрощение кода и аксиома вывода
node* insert(node& c, int v) {node *n = new node;n -> val = v;
if(c.next) {n -> next = c.next;
}else {
n -> next = 0;}c.next = n;return n;
}
null
26
Как упростить операцию insert?
P = c – последний V с – непоследний
{P} insert(c, x) {Q}
Если B = {∀с с – непоследний} – инвариант
P^B→P, {P}insert(c,x){Q} ⊦ {P^B}insert(c,x){Q}
27
Как упростить операцию insert?
P = c – последний V с – непоследний
{P} insert(c, x) {Q}
Если B = {∀с с – непоследний} – инвариант
P^B→P, {P}insert(c,x){Q} ⊦ {P^B}insert(c,x){Q}
Но! P^B = c- непоследний
{c - непоследний} insert(c,x) {Q}
28
Закольц. список с буф. элементом
node* insert(node& c, int v) {node *n = new node;n -> val = v;
if(c.next) {n -> next = c.next;
}else {
n -> next = 0;}c.next = n;return n;
}
29
Закольц. список с буф. элементом
node* insert_before(node& c, int v) {node *n = new node;n -> val = v;
n -> next = c.next;c.next = n;
return n;}
34
Повторное использование
Повторное использование кода — методологияпроектирования компьютерных и других систем,заключающаяся в том, что система (компьютернаяпрограмма, программный модуль) частично либополностью должна составляться из частей, написанныхранее компонентов и/или частей другой системы, иэти компоненты должны применяться более одногораза (если не в рамках одного проекта, то хотя быразных).
https://ru.wikipedia.org/wiki/Повторное_использование_кода
36
Когда происходит повторное
использование?
Если нужно внести изменение в существующее
приложение, то мы пытаемся повторно
использовать свой же собственный код, чтобы
получить тоже приложение, но с новой
функциональностью.
39
The Open-Closed Principle
Программные объекты должны быть
открыты для расширения, но в тоже время
закрыты для модификации.
40
Пример: матрицы
class matrix {
int size;
double *body;
public:
matrix(int s): size(s) {
body = new double[s*s];
}
void transform() {
…
}
double det() const {
…
}
};
41
Матрицы: добавляем matrix()
class matrix {
int size;
double *body;
public:
matrix(int s): size(s) {
body = new double[s*s];
}
void transform() {
…
}
double det() const {
…
}
};
class matrix {
public:
matrix(): size(0), body(0) {
}
};
42
Придется менять методы
class matrix {
int size;
double *body;
public:
matrix(): size(0), body(0) {
}
matrix(int s): size(s) {
body = new double[s*s];
}
void transform() {
if(!body) throw exception();
…
}
double det() const {
if(!body) throw exception();
…
}
};
43
Что случилось?
Инвариант класса matrix: size > 0
Конструктор matrix() нарушил инвариант: size = 0
44
Что случилось?
Инвариант класса matrix: size > 0
Конструктор matrix() нарушил инвариант: size = 0
Предусловия методов изменились с size > 0 на size
≥ 0, но
45
Что случилось?
Инвариант класса matrix: size > 0
Конструктор matrix() нарушил инвариант: size = 0
Предусловия методов изменились с size > 0 на size
≥ 0, но
{size ≥ 0} det {Q} не выводится из {size > 0} det {Q}
46
Что случилось?
Инвариант класса matrix: size > 0
Конструктор matrix() нарушил инвариант: size = 0
Предусловия методов изменились с size > 0 на size
≥ 0, но
{size ≥ 0} det {Q} не выводится из {size > 0} det {Q}
Следовательно, надо изменять сами методы
47
Конструкторы по умолчанию
Конструктор должен устанавливать
инвариант класса
Конструкторы по умолчанию часто
расширяют допустимые значения
параметров
Стоит несколько раз подумать, прежде чем
использовать конструктор по умолчанию
49
Импликация на множестве
Пусть P1 → P,
B = { x | P1(x) = 1},
A = { x | P (x) = 1}.
Тогда B ⊂ A.
P ⊨ P1. Говорят, что P –
более слабое условие,
P1 – более сильное.
A
B
51
Построим вывод
{S} cb {P}, {P} c {Q}, {Q} ca {R} ⊦ {S} cb; c; ca {R}
Пусть P → P1, Q1 → Q
Известно, что
{S} cb {P}, {P1} c {Q1}, {Q} ca {R}
52
Построим вывод
{S} cb {P}, {P} c {Q}, {Q} ca {R} ⊦ {S} cb; c; ca {R}
Пусть P → P1, Q1 → Q
Известно, что
{S} cb {P}, {P1} c {Q1}, {Q} ca {R}
Тогда
{P} c {Q} (аксиома вывода)
{S} cb; c; ca {R}
53
The Dependency Inversion Principle
Высокоуровневые компоненты не должны
зависеть от низкоуровневых компонент. И
те, и те должны зависеть от абстракций
(оператора расширения).
55
Иначе
{S} cb {P}, {P} c {Q}, {Q} ca {R} ⊦ {S} cb; c; ca {R}
Пусть P ↛ P1, но P ^ P1 → P1
Известно, что
{S} cb {P}, {P1} c {Q}, {Q} ca {R}
56
Иначе
{S} cb {P}, {P} c {Q}, {Q} ca {R} ⊦ {S} cb; c; ca {R}
Пусть P ↛ P1, но P ^ P1 → P1
Известно, что
{S} cb {P}, {P1} c {Q}, {Q} ca {R}
Тогда
{P1} c {Q} ⊦ {P} if (P1) then c endif {Q}
{S} cb; if(P1) then c endif; ca {R}
57
Правило оператора расширения
Оператор расширения должен допускать:1. Ослабление предусловий
2. Усиление постусловий
59
Как выглядит оператор расширения?
1. Статический полиморфизм
template <class It, class Op>
void for_each(It begin, It end, Op op)
{
for(; begin != end; ++begin)
op(*begin);
}
int arr[] = {1, 2, 3, 4, 5};
for_each(arr, arr+6, max<int>());
60
Как выглядит оператор расширения?
2. Во время выполнения
указатель на функцию
void (*f) (int i);
f pf;
pf(5);
динамический полиморфизм
class Shape {
public:
virtual void Draw() = 0;
};
shape->Draw();
61
Как выглядит оператор расширения?
3. Метапрограммирование
void Counter::setValue(int value)
{
if (value != m_value) {
m_value = value;
emit valueChanged(value);
}
}
63
The Liskov Substitution Principle
Функции, которые используют ссылки на
объекты базовых классов, должны
использовать объекты производных
классов, ничего не зная о них.
66
The Interface Segregation Principle
Класс не должен зависеть от интерфейсов,
которые он не использует
67
А не для классов?
bool (*validator) (Document const & doc);
std::vector<validator> rules = …;
for (int i = 0; i < n; ++i)
if(!rules[i](document))
return false;
return true;
70
Мы пишем книги
Нерассказанные факты об императивном
программировании
oopguide.ru
Разработка серверов и серверных приложений
actorsmodel.ru