ук 03.001.02 2011

Preview:

DESCRIPTION

 

Citation preview

УК 03.001.01-2011 Учебный курс. Обучение. ООП.

Базовые понятия

Объектно-ориентированное программирование (ООП) – это методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию наследования.

(КН 02.002-1995, стр. 52)

• Основным элементом абстракции являются объекты

• Каждый объект является экземпляром определенного класса

• Классы образуют иерархию наследования

Ключевые характеристики ООП

Абстракция выделяет существенные характеристики некоторого объекта, отличающие его от всех других видов объектов и, таким образом, четко определяет его концептуальные границы с точки зрения наблюдателя.

(КН 02.002-1995, стр. 55)

• Внешнее поведение

• Барьер абстракции

• Неоднозначность выделения абстракции

• Абстракция и объект реального мира – не одно и тоже (пример: вещественные числа и числа с плавающей точкой)

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

Инкапсуляция – это процесс отделения друг от друга элементов объекта, определяющих его устройство и поведение; инкапсуляция служит для того, чтобы изолировать контрактные обязательства абстракции от их реализации.

(КН 02.002-1995, стр. 63)

• Сокрытие данных лишь частный случай инкапсуляции

• Поведение представляется только с помощью методов

• Задача о множествах объектов

Модульность – это свойство системы, которая может была разложена на внутренне связные, но слабо связанные между собой модули.

(КН 02.002-1995, стр. 69)

• Что первично класс или модуль?

Иерархия – это упорядочивание абстракций, расположение их по уровням.

(КН 02.002-1995, стр. 71)

• “is a” (наследование)

• “part of” (агрегация, композиция)

Принципы ООП

Открыто-замкнутый принцип

• Инкрементная разработка;

• Количество строк кода, которые пишет программист, в единицу времени ограничено;

• Если изменяется существующий код, то, скорее всего, прироста функционала не происходит;

• Повторное использование.

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

(СТ 02.003-1995, стр. 1)

Наследование ООП позволяет задавать фиксированную абстракцию, но описывающую неограниченную группу возможных поведений.

Поведение – это то, как объект действует и реагирует; поведение выражается в терминах состояния объекта и передачи сообщений.

(КН 02.002-1995, стр. 96) Необходимо избегать конструкций, которые создают закрытое

множество поведений: • switch (ПР 02.001) • Цепочка if/else (ПР 02.002) • Информация о типе во время выполнения (ПР 02.003) • Перечислимые типы (ПР 02.004) • Дублирование кода (ПР 01.001) • Все поля должны быть private (ПР 02.005) • Избегать использование глобальных переменных (ПР 01.002)

Пример 1: Геометрические фигуры

class Shape

{

private ShapeType itsType;

private void DrawSquare();

private void DrawCircle();

public static void DrawAllShapes(Shape[] list)

{

for(Shape s : list)

{

switch (s.itsType)

{

case square:

DrawSquare();

break;

case circle:

DrawCircle();

break;

}

}

}

}

interface Shape

{

void Draw();

}

class ShapeUtils

{

public static void DrawAllShapes(Shape[] list)

{

for(Shape s: list)

{

s.Draw();

}

}

}

class Square implements Shape

{

public void Draw()

{

}

}

Пример 2: Рубрикатор

switch (article.Rubric) { case Auto: case Realty: filterPanels.Add(ucSearchOptions); break; case Job: switch (article.TypeID) { case Resume: filterPanels.Add(ucSeniorityPanel); filterPanels.Add(ucPricePanel); filterPanels.Add(ucWorkSchedulePanel); break; case Vacancy: filterPanels.Add(ucSeniorityPanel); filterPanels.Add(ucPricePanel); filterPanels.Add(ucWorkSchedulePanel); break; case Education: filterPanels.Add(ucPricePanel); break; } break; }

siteRubricHelper.SetFilterOptions(panel);

public interface IRubricHelper

{

void SetFilterOptions(FilterPanel panel);

}

public class DefaultRubricHelper implements IRubricHelper

{

private FilterOptions ucSearchOptions;

public void SetFilterOptions(FilterPanel panel)

{

panel.Add(ucSearchOptions); }

}

Стратегическая замкнутость

• Замкнутость не может быть 100%

• Замкнутость должна быть стратегической

• Проектирование ради стратегической замкнутости

• Паттерны проектирования – типовые примеры, обеспечивающие определенные виды замкнутости

Принцип подстановки Б. Лисков

Метод, получающий по ссылке объект, должен использовать этот объект без точного знания того, объектом какого класса в иерархии наследования он является.

(СТ 02.004-1995, стр. 2)

Пример 3: RTTI

void Draw(Shape s)

{

if (s instanceof Point)

{

DrawPoint(s as Point);

}

else if (s instanceof Circle)

{

DrawCircle(s as Circle);

}

else if(s instanceof Square)

{

DrawSquare(s as Square);

}

}

Пример 4: Rectangle и Square class Rectangle

{

private double height;

private double width;

public double getHeight() { return height; }

public void setHeight(int value) { height = value; }

public double getWidth() { return width; }

public void setWidth(int value) { width = value; }

}

….

void f(Rectangle r)

{

r.setHeight (5);

r.setWidth (4);

Debug.Assert(r.getHeight() * r.getWidth() == 20);

}

class Square extends Rectangle

{

public void setHeight(int value)

{

super.setHeight(value);

super.setWidth(value);

}

public void setWidth(int value)

{

super.setHeight(value);

super.setWidth(value);

}

}

• Построенные абстракции нельзя проверить на корректность сами по себе. Такую проверку можно выполнить лишь в контексте клиентов, использующих данные абстракции.

• Заранее построить очень гибкую модель “про запас” нельзя!

• Лучше использовать прототипирование

Прототип – самый простой вариант чего-либо, но содержащий самый сложный компонент.

Принцип обращения зависимостей

Программа Copy void Copy()

{

int ch;

while ((ch = Keyboard()) != EOF)

{

WritePrinter(c);

}

}

enum OutputDevice

{

printer,

disk

};

void Copy(OutputDevice dev)

{

int c;

while ((c = ReadKeyboard()) != EOF)

{

if (dev == printer)

WritePrinter(c);

else

WriteDisk(c);

}

}

interface IReader

{

int Read();

}

interface IWriter

{

void Write(char) = 0;

}

void Copy(IReader r, IWriter w)

{

int c;

while((c=r.Read()) != EOF)

w.Write(c);

}

Схема зависимостей

• Высокоуровневые модули не должны зависеть от низкоуровневых модулей. И те, и те должны зависеть от абстракций.

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

(СТ 02.005-1995, 6)

СЛОИ

Пример 5: Лампочка

class Lamp

{

public void TurnOn() {…}

public void TurnOff() {…}

}

class Button

{

private Lamp lamp;

public Button(Lamp lamp) {this.lamp = lamp;}

public void Detect()

{

if(GetPhisicalState())

{

lamp.TurnOn();

}

else

{

lamp.TurnOff();

}

}

}

interface IButtonClient

{

void TurnOn();

void TurnOff();

}

class Button

{

private IButtonClient client;

public Button(IButtonClient client)

{

this.client = client;

}

public void Detect()

{

if (GetPhisicalState())

{

client.TurnOn();

}

else

{

client.TurnOff();

}

}

}

class Lamp

{

public void SwitchOn() {…}

public void SwitchOff() {…}

}

class LampAdapter implements IButtonClient

{

private Lamp lamp;

public LampAdapter(Lamp lamp)

{

this.lamp = lamp;

}

public void TurnOn()

{

lamp.SwitchOn();

}

public void TurnOff()

{

lamp.SwitchOff();

}

}

• Сторонний код должен быть скрыт за обертками, реализующими собственные интерфейсы (ПР 04.001)

• Обертки можно строить к старому наследуемому коду

• Переписывание не всегда самый лучший путь

Принцип соответствия интерфейсов

interface IButtonClient

{

void TurnOn();

void TurnOff();

}

Interface ITimerClient

{

void OnTimeout();

}

class Lamp implements IButtonCLient, ITimerClient

{

public void TurnOn() {…}

public void TurnOff() {…}

public void OnTimeout() {…}

}

class Button

{

private IButtonClient;

public Button(IButtonClient client) {…}

public void Detect() {…}

}

class Timer

{

private ITimerClient client;

public void Timeout()

{

client.OnTimeout();

}

}

class EmergencyLamp extends Lamp

{

public override void OnTimeout()

{

// нельзя включать и отключать по таймеру,

// поэтому пустая реализация

}

}

class Lamp implements IButtonClient

{

}

class ButtonToTimerClientAdapter implements ITimerClient

{

// см. пример адаптера для IButtonClient

}

Interface IEmergencyClient

{

void OnAlert();

}

class ButtonToEmergencyClientAdapter implements IEmergencyClient

{

// см. пример адаптера для IButtonClient

}

• ITimerClient, IEmergencyClient, IButtonCLient – это разные множества объектов, но которые частично пересекаются

• Жирные интерфейсы сигнализируют об ошибках в проектировании

• Жирных интерфейсов следует избегать (ПР 02.006) Исключение: применение паттерна Compositor

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

(СТ 02.006-1996, стр. 5)

• Абстракции нельзя проверить сами по себе, вне контекста использования!!!

• Часто программисты делают предложения по функционалу, основываясь на текущих возможностях реализации

• Подход к программированию: программный код как результат решения конкретной задачи

• Подход к программированию: писать программный код как набор инструментов для решения задач

DI контейнеры как расширяемые фабрики

package examples.di;

public interface Greeting

{

String greet();

}

package examples.di.impl;

import examples.di.Greeting;

public class GreetingImpl implements Greeting

{

public String greet()

{

return "Hello World!";

}

}

package examples.di;

public interface GreetingClient

{

void execute();

}

package examples.di.impl;

import examples.di.Greeting;

import examples.di.GreetingClient;

public class GreetingClientImpl implements GreetingClient

{

private Greeting greeting;

public GreetingClientImpl(Greeting greeting) { this.greeting = greeting; }

public void execute() { System.out.println(greeting.greet()); }

}

package examples.di.main;

import examples.di.Greeting;

import examples.di.impl.GreetingClientImpl;

import examples.di.impl.GreetingImpl;

public class GreetingMain

{

public static void main(String[] args)

{

Greeting greeting = new GreetingImpl();

GreetingClientImpl greetingClient = new GreetingClientImpl(greeting);

greetingClient.execute();

}

}

package examples.di.main;

import org.seasar.framework.container.S2Container;

import org.seasar.framework.container.factory.S2ContainerFactory;

import examples.di.GreetingClient;

public class GreetingMain3

{

private static final String PATH = "examples/di/dicon/GreetingMain3.dicon";

public static void main(String[] args)

{

S2Container container = S2ContainerFactory.create(PATH);

GreetingClient greetingClient = new GreetingClientImpl( container.getComponent("greeting”)

);

greetingClient.execute();

}

}

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC"-//SEASAR//DTD S2Container 2.3//EN" "http://www.seasar.org/dtd/components23.dtd"> <components> <include path="aop.dicon"/> <component name="greeting” class="examples.di.impl.GreetingImpl"> <aspect>aop.traceInterceptor</aspect> </component> </components>

package examples.di.impl;

import examples.di.Greeting;

import examples.di.GreetingClient;

public class GreetingClientImpl implements GreetingClient

{

private Greeting greeting;

public GreetingClientImpl()

{

S2Container container = S2ContainerFactory.create(PATH);

this.greeting = container.getComponent("greeting”);

}

public void execute() { System.out.println(greeting.greet()); }

}

Принцип единой ответственности

Должна быть ровно одна причина для изменения класса

• Классы должны быть компактными (ПР 02.007)

• Следует избегать глубоких иерархий классов (ПР 02.008)

Компоновка программы

• Разделяй и властвуй

• Структурирование

• Разная структура для разных объемов

• План выполнения запросов

• Объявления

Модули

Каков критерий разбиения программы на модули?

Взаимосвязи между модулями? Принцип организации взаимосвязей?

Что первично: классы или модули?

Каким программным конструкциям соответствуют модули?

Какие цели преследуют модули?

Принципы сцепления модулей

Принцип эквивалентности единиц повторного использования и релиза

Единица повторного использования является единицей релиза. Только компоненты, которые реализуются через систему управления проектами, могут быть эффективно повторно использованы. Такой единицей является модуль.

(СТ 02.007-1996, стр. 4)

Обобщенный принцип повторного использования

Классы, входящие в состав модуля повторно используются все вместе. Если Вы повторно используете один, то Вам доступны все остальные.

(СТ 02.007-1996, стр. 5)

Обобщенный принцип замкнутости

Классы, входящие в состав пакета, должны быть закрыты по отношению к одним и тем же изменениям. Изменение, влияющее на пакет, оказывает воздействие на все классы, входящие в этот пакет (на затрагивая другие пакеты)

(СТ 02.007-1996, стр. 6)

• В .Net модулем является сборка

• Разнесение классов по модулям нетривиальная задача

• Методы разнесения классов по модулям носит динамический характер

• Сначала создаются классы, затем выделяются модули

• Движущая сила: смещения акцента от возможности разработки до возможности повторного использования

• Один класс в одном файле (ПР 05.001)

Принципы связывания пакетов

Принцип ациклических зависимостей.

Граф зависимостей между модулями должен быть ациклическим

(СТ 02.007-1996, стр. 6)

Пример 5: Ациклический граф

Пример 6: Циклический граф

Интеграционная штурмовщина

• Еженедельный билд

• Не успеваем вовремя его собрать, протестировать

• Большие накладные расходы на сборку билда

• Увеличиваются сроки между билдами

• Модули можно разрабатывать независимо друг от друга

• Релиз системы состоит из набора модулей, каждый из которых имеет свою версию

• Product Manager

Стабильность

• Какие цели преследуют модули?

• Пример: программа копирования

• “Хорошая” зависимость – это зависимость от чего-то, что не слишком часто меняется

• Как измерить насколько зависимость хорошая?

Стабильность – способность системы функционировать, не изменяя собственную структуру и находиться в равновесии в течение определенного промежутка времени.

(Wikipedia.org)

Принцип стабильных зависимостей

Модули должны зависеть только от более стабильных модулей.

(СТ 02.008-1996, стр. 8)

• Дизайн системы не может быть полностью стабильным

• Принцип обобщенной замкнутости

• Принцип единственной ответственности

Метрики стабильности

• Центростремительная связность (Ca) – количество классов за пределами модуля, которые зависят от классов внутри модуля

• Центробежная связность (Ce) – количество классов внутри модуля, которые зависят от классов за пределами модуля

• Коэффициент нестабильности (I): I = Ce/(Ce+Ca) – I = 0 – максимально стабильный пакет

– I = 1 – максимально нестабильный пакет

Принцип стабильных абстракций

Модули, которые максимально стабильны должны быть максимально абстрактны. Нестабильные модули должны быть конкретны. Абстрактность модуля должна быть обратно пропорциональна нестабильности.

(СТ 02.008-1996, стр. 11)

Абстрактность модуля = Абстрактные классы и интерфейсы/Общее число классов и интерфейсов

Итоги

• Изменения через написание нового без переписывания старого

• Полностью от переписывания отказаться нельзя, поэтому важно проектировать систему

• Цель проектирования – управлять замкнутостью

• Проектирование предшествует программированию

• Сначала классы, а потом модули

• Нет интеграционной штурмовщине

• Важно правильно разбивать систему на модули

• Интерфейсы рулят

• Проектирование – Design Patterns

– Прототипирование как средство уменьшения рисков

• Существующий код – Рефакторинг

– Фасады

– Автоматическое тестирование

Что дальше

• Контрактная модель программирования

• Design Patterns

• Рефакторинг

• Автоматическое тестирование

Вопросы?