2. Объектно ориентированное проектирование
• проектирование - суть программирования
• ваш код - проектная документация продукта
• задача проектирования - управление сложностью
проекта
8. Ароматы дизайна
• непрозрачность
- сложность для понимания, запутанность
• жесткость
- сложность внесения изменений
9. Ароматы дизайна
• непрозрачность
- сложность для понимания, запутанность
• жесткость
- сложность внесения изменений
- простое изменение вызывает каскад зависимых
правок
10. Ароматы дизайна
• непрозрачность
- сложность для понимания, запутанность
• жесткость
- сложность внесения изменений
- простое изменение вызывает каскад зависимых
правок
• хрупкость
11. Ароматы дизайна
• непрозрачность
- сложность для понимания, запутанность
• жесткость
- сложность внесения изменений
- простое изменение вызывает каскад зависимых
правок
• хрупкость
- исправление одной проблемы влечет появление
множества новых
14. Ароматы дизайна
• неподвижность
- сложно выделить части дизайна в отдельные
компоненты
15. Ароматы дизайна
• неподвижность
- сложно выделить части дизайна в отдельные
компоненты
• вязкость
16. Ароматы дизайна
• неподвижность
- сложно выделить части дизайна в отдельные
компоненты
• вязкость
- сложно сохранить дизайн при изменении
17. Ароматы дизайна
• неподвижность
- сложно выделить части дизайна в отдельные
компоненты
• вязкость
- сложно сохранить дизайн при изменении
- решить задачу правильно сложнее, чем сделать
“хак”
20. Ароматы дизайна
• ненужная сложность
- наблюдается overhead, наличие множества
неиспользуемых элементов
21. Ароматы дизайна
• ненужная сложность
- наблюдается overhead, наличие множества
неиспользуемых элементов
- возникает, когда разработчик пытается
предугадать последующие изменения
22. Ароматы дизайна
• ненужная сложность
- наблюдается overhead, наличие множества
неиспользуемых элементов
- возникает, когда разработчик пытается
предугадать последующие изменения
• ненужные повторения
23. Ароматы дизайна
• ненужная сложность
- наблюдается overhead, наличие множества
неиспользуемых элементов
- возникает, когда разработчик пытается
предугадать последующие изменения
• ненужные повторения
- один и тот же код с минимальными изменениями
копируется снова и снова
28. SRP
Принцип единственной обязанности
• у класса должна быть только одна причина для
изменения
29. SRP
Принцип единственной обязанности
• у класса должна быть только одна причина для
изменения
• то есть у класса должна быть только одна
обязанность
30. SRP
Принцип единственной обязанности
• у класса должна быть только одна причина для
изменения
• то есть у класса должна быть только одна
обязанность
• сильное сцепление (high cohesion, см. GRASP)
31. SRP
Принцип единственной обязанности
class Modem
def dial(number)
end 1-я обязанность
def hangup
управление соединением
end
def send(char)
end
2-я обязанность
def recv передача данных
end
end
32. SRP
Принцип единственной обязанности
module ConnectionInterface
def dial(number)
end
def hangup class Modem
end include ConnectionInterface
end include DataChannelInterface
end
module DataChannelInterface
def send(char)
end
def recv
end
end
33. SRP
Принцип единственной обязанности
class Modem
def dial(number)
end 1-я обязанность
управление соединением
def hangup
end
def send(char)
end 2-я обязанность
передача данных
def recv
end
end
Если необходимо изменить только процесс соединения - то дизайн
становится жестким, и нужно разделять обязанности
Если изменяется одновременно и процесс соединения, и процесс
отправки - то разделение повлечет за собой излишнюю сложность
34. SRP
Принцип единственной обязанности
class Basket
def calculate бизнес-логика
end
def store
end логика приложения
end
36. SRP
Принцип единственной обязанности
• не совмещайте бизнес-логику с логикой
приложения
37. SRP
Принцип единственной обязанности
• не совмещайте бизнес-логику с логикой
приложения
• если вам очень-очень нужно совместить
обязанности - не создавайте зависимостей
40. OCP
Принцип открытости/закрытости
• программные сущности должны быть открыты для
расширения, но закрыты для модификации
41. OCP
Принцип открытости/закрытости
• программные сущности должны быть открыты для
расширения, но закрыты для модификации
• открыты для расширения - мы можем изменить состав
модуля, добавив новое поведение
42. OCP
Принцип открытости/закрытости
• программные сущности должны быть открыты для
расширения, но закрыты для модификации
• открыты для расширения - мы можем изменить состав
модуля, добавив новое поведение
• закрыты для модификации - расширение модуля не
связано с изменением его исходного кода (уменьшаем
жесткость)
43. OCP
Принцип открытости/закрытости
Задача: необходимо рисовать геометрические фигуры
(круги и квадраты) в графическом интерфейсе.
44. OCP
Принцип открытости/закрытости
wrong
(высокая жесткость)
Circle = Struct.new(:radius, :center)
# рисуем круг
def draw_circle
#
end
Square = Struct.new(:side, :top_left)
# рисуем квадрат
def draw_square
#
end
# рисуем список фигур
def draw_all_shapes(shapes)
shapes.each do |shape|
case shape.class
when Circle; draw_circle(shape)
when Square; draw_square(shape)
end
end
end
45. OCP
Принцип открытости/закрытости
wrong better
(высокая жесткость)
Circle = Struct.new(:radius, :center) class Shape
def draw
# рисуем круг # interface
def draw_circle end
# end
end
class Circle < Shape
Square = Struct.new(:side, :top_left) def draw
# implement
# рисуем квадрат end
def draw_square end
#
end class Square < Shape
def draw
# рисуем список фигур # implement
def draw_all_shapes(shapes) end
shapes.each do |shape| end
case shape.class
when Circle; draw_circle(shape) def draw_all_shapes(shapes)
when Square; draw_square(shape) shapes.each do |shape|
end shape.draw
end end
end end
47. OCP
Принцип открытости/закрытости
• не существует моделей, универсальных во всех
контекстах
48. OCP
Принцип открытости/закрытости
• не существует моделей, универсальных во всех
контекстах
• защищайся только от наиболее вероятных изменений
49. OCP
Принцип открытости/закрытости
• не существует моделей, универсальных во всех
контекстах
• защищайся только от наиболее вероятных изменений
• не беги вперед и не перегружай код абстракциями
(помни про ненужную сложность)
50. OCP
Принцип открытости/закрытости
• не существует моделей, универсальных во всех
контекстах
• защищайся только от наиболее вероятных изменений
• не беги вперед и не перегружай код абстракциями
(помни про ненужную сложность)
• “Обманул меня раз - позор тебе. Обманул два - позор
мне”
51. OCP
Принцип открытости/закрытости
• не существует моделей, универсальных во всех
контекстах
• защищайся только от наиболее вероятных изменений
• не беги вперед и не перегружай код абстракциями
(помни про ненужную сложность)
• “Обманул меня раз - позор тебе. Обманул два - позор
мне”
• отказ от абстракции так же важен, как и ее ввод
54. LSP
Принцип подстановки Лисков
• должна быть возможность вместо базового типа
подставить любой его подтип
55. LSP
Принцип подстановки Лисков
• должна быть возможность вместо базового типа
подставить любой его подтип
• поведение наследуемых классов не должно
противоречить поведению, заданному базовым классом
56. LSP
Принцип подстановки Лисков
• должна быть возможность вместо базового типа
подставить любой его подтип
• поведение наследуемых классов не должно
противоречить поведению, заданному базовым классом
• LSP - важнейший критерий оценки качества при
построении иерархий наследования
58. LSP
Принцип подстановки Лисков
class Rectangle
def width
return @width
end
def height
return @height
end
def width=(number)
@width = number
end
def height=(number)
@height = number
end
end
59. LSP
Принцип подстановки Лисков
class Rectangle class Square < Rectangle
def width def width=(number)
return @width @width = number
end @height = number
end
def height
return @height def height=(number)
end @width = number
@height = number
end
def width=(number) end
@width = number
end
def height=(number)
@height = number
end
end
60. LSP
Принцип подстановки Лисков
class Rectangle class Square < Rectangle
def width def width=(number)
return @width @width = number
end @height = number
end
def height
return @height def height=(number)
end @width = number
@height = number
end
def width=(number) end
@width = number
end
def height=(number)
@height = number
end def foo(rectangle)
end rectangle.width = 5
rectangle.height = 7
end
61. LSP
Принцип подстановки Лисков
class Rectangle class Square < Rectangle
def width def width=(number)
return @width @width = number
end @height = number
end
def height
return @height def height=(number)
end @width = number
@height = number
# assert((width == number) && (height = old.height)) end
def width=(number) end
@width = number
end
def height=(number)
@height = number
end def foo(rectangle)
end rectangle.width = 5
rectangle.height = 7
end
63. LSP
Принцип подстановки Лисков
• проектирование по контракту (пред- и пост- условия)
64. LSP
Принцип подстановки Лисков
• проектирование по контракту (пред- и пост- условия)
• производный класс не должен исключать
функциональность базового класса
65. LSP
Принцип подстановки Лисков
• проектирование по контракту (пред- и пост- условия)
• производный класс не должен исключать
функциональность базового класса
• если классы имеют некоторую общую обязанность -
возможно имеет смысл провести факторизацию
68. ISP
Принцип разделения интерфейсов
• клиент не должен вынужденно зависеть от элементов
интерфейса, которые он не использует
69. ISP
Принцип разделения интерфейсов
• клиент не должен вынужденно зависеть от элементов
интерфейса, которые он не использует
• “жирные” интерфейсы - плохо
70. ISP
Принцип разделения интерфейсов
• клиент не должен вынужденно зависеть от элементов
интерфейса, которые он не использует
• “жирные” интерфейсы - плохо
• интерфейс считается “жирным”, когда между его
методами слабая сцепленность (см. GRASP)
71. ISP
Принцип разделения интерфейсов
class ServiceClient
def send_data; end
def flush; end
end
class HttpServiceClient < ServiceClient
def send_data
# implement
end
def flush
# пустой метод, остаток от интерфейса
end
end
class BufferingHttpServiceClient < ServiceClient
def send_data
# another implement
end
def flush
# implement
end
end
72. ISP
Принцип разделения интерфейсов
class ServiceClient class ServiceClient
def send_data; end def send_data; end
def flush; end end
end
class BufferingServiceClient < ServiceClient
class HttpServiceClient < ServiceClient def flush; end
def send_data end
# implement
end
class HttpServiceClient < ServiceClient
def flush def send_data
# пустой метод, остаток от интерфейса # implement
end end
end end
class BufferingHttpServiceClient < ServiceClient class BufferingHttpServiceClient < BufferingServiceClient
def send_data def send_data
# another implement # another implement
end end
def flush def flush
# implement # implement
end end
end end
73. ISP
Принцип разделения интерфейсов
Как разделять?
74. ISP
Принцип разделения интерфейсов
Как разделять?
• делегация
75. ISP
Принцип разделения интерфейсов
Как разделять?
• делегация
• множественное наследование
78. DIP
Принцип инверсии зависимости
• модули верхнего уровня не должны зависеть от
модулей нижнего уровня. И те и другие должны
зависеть от абстракций
79. DIP
Принцип инверсии зависимости
• модули верхнего уровня не должны зависеть от
модулей нижнего уровня. И те и другие должны
зависеть от абстракций
• абстракции не должны зависеть от деталей. Детали
должны зависеть от абстракций
80. DIP
Принцип инверсии зависимости
• модули верхнего уровня не должны зависеть от
модулей нижнего уровня. И те и другие должны
зависеть от абстракций
• абстракции не должны зависеть от деталей. Детали
должны зависеть от абстракций
• если коротко: зависеть надо от абстракций
81. DIP
Принцип инверсии зависимости
до
class Program
def self.run
reporter = Reporter.new
reporter.send_reports()
end
end
class Reporter
def initialize
@builder = ReportBuilder.new
@sender = EmailReportSender.new
end
def send_reports
reports = @builder.create_reports()
reports.each do |report|
@sender.send(report)
end
end
end
Program.run
82. DIP
Принцип инверсии зависимости
до
class Program
class Program def self.run
def self.run
# builder, sender - реализации соответствующих интерфейсов
reporter = Reporter.new
builder = ReportBuilder.new
reporter.send_reports()
sender = EmailReportSender.new
end
reporter = Reporter.new(builder, sender)
end
reporter.send_reports()
end
class Reporter
end
def initialize
@builder = ReportBuilder.new
class Reporter
@sender = EmailReportSender.new
def initialize(builder, sender)
end
@builder = builder
@sender = sender
def send_reports
end
reports = @builder.create_reports()
reports.each do |report|
def send_reports
@sender.send(report)
reports = @builder.create_reports()
end
reports.each do |report|
end
@sender.send(report)
end
end
end
Program.run
end
Program.run
83. DIP
Принцип инверсии зависимости
до после
class Program
class Program def self.run
def self.run
# builder, sender - реализации соответствующих интерфейсов
reporter = Reporter.new
builder = ReportBuilder.new
reporter.send_reports()
sender = EmailReportSender.new
end
reporter = Reporter.new(builder, sender)
end
reporter.send_reports()
end
class Reporter
end
def initialize
@builder = ReportBuilder.new
class Reporter
@sender = EmailReportSender.new
def initialize(builder, sender)
end
@builder = builder
@sender = sender
def send_reports
end
reports = @builder.create_reports()
reports.each do |report|
def send_reports
@sender.send(report)
reports = @builder.create_reports()
end
reports.each do |report|
end
@sender.send(report)
end
end
end
Program.run
end
Program.run
88. Советую прочитать
• GRASP - General Responsibility Assignment Software
Patterns
• Принципы, паттерны, и методики гибкой разработки на
языке C#. // Р. С. Мартин, М. Мартин.
89. Советую прочитать
• GRASP - General Responsibility Assignment Software
Patterns
• Принципы, паттерны, и методики гибкой разработки на
языке C#. // Р. С. Мартин, М. Мартин.
• Шаблоны корпоративных приложений. // М. Фаулер
90. Советую прочитать
• GRASP - General Responsibility Assignment Software
Patterns
• Принципы, паттерны, и методики гибкой разработки на
языке C#. // Р. С. Мартин, М. Мартин.
• Шаблоны корпоративных приложений. // М. Фаулер
• Предметно-ориентированное проектирование (DDD).
Структуризация сложных систем. // Э. Эванс
91. Советую прочитать
• GRASP - General Responsibility Assignment Software
Patterns
• Принципы, паттерны, и методики гибкой разработки на
языке C#. // Р. С. Мартин, М. Мартин.
• Шаблоны корпоративных приложений. // М. Фаулер
• Предметно-ориентированное проектирование (DDD).
Структуризация сложных систем. // Э. Эванс
• Паттерны проектирования. // GoF
92. That’s all
Вопросы?
Щербинин Александр
undev.ru
twitter.com/realmyst
github.com/realmyst