- •Макетирование.
- •Экстремальное программирование.
- •Структурирование системы. Архитектурные системные паттерны:
- •Моделирование управления. Паттерны управления.
- •Декомпозиция подсистем на модули.
- •Информационная закрытость.
- •Функциональная связность
- •Информационная связность
- •Коммуникативная связность
- •Процедурная связность
- •Временная связность
- •Логическая связность
- •Связность по совпадению
- •Определение связности модуля
- •Характеристики иерархической структуры программной системы
- •Четыре правила простой архитектуры Кента Бека (в порядке приоритета):
- •Фундаментальные паттерны. Шаблон делегирования.
- •Композиция и агрегирование.
- •Неизменяемый объект.
- •Шаблон единой ответственности.
- •Шаблон интерфейса.
- •Наследование.
- •Полиморфизм.
- •Как оо паттерны позволяют улучшить проектирование.
- •Проектирование с учетом будущих изменений.
Как оо паттерны позволяют улучшить проектирование.
Объектная декомпозиция.
Объектно-ориентированные программы состоят из объектов. Объект сочетает данные и процедуры для их обработки.Объект выполняет операцию, когда получает запрос (или сообщение) от клиента. Посылка запроса - это единственный способ заставить объект выполнить операцию. А выполнение операции - единственный способ изменить внутреннее состояние объекта. Имея в виду два эти ограничения, говорят, что внутреннее состояние объекта инкапсулировано: к нему нельзя получить непосредственный доступ, то есть представление объекта закрыто от внешней программы.
Паттерны проектирования помогают выявить не вполне очевидные абстракции и объекты, которые могут их использовать. Например, объектов, представляющих процесс или алгоритм, в действительности нет, но они являются неотъемлемыми составляющими гибкого дизайна. Паттерн стратегия описывает способ реализации взаимозаменяемых семейств алгоритмов. Паттерн состояние позволяет представить состояние некоторой сущности в виде объекта. Эти объекты редко появляются во время анализа и даже на ранних стадиях проектирования.
Проектирование в соответствии с интерфейсом, а не реализацией.
Наследование позволяет определять семейства объектов с идентичными интерфейсами. У манипулирования объектами строго через интерфейс абстрактного класса есть два преимущества:
клиенту не нужно иметь информации о конкретных типах объектов, которыми он пользуется, при условии, что все они имеют ожидаемый клиентом интерфейс;
клиенту необязательно «знать» о классах, с помощью которых реализованы объекты. Клиенту известно только об абстрактном классе (или классах), определяющих интерфейс.
Данные преимущества настолько существенно уменьшают число зависимостей между подсистемами, что можно даже сформулировать принцип объектно-ориентированного проектирования для повторного использования: программируйте в соответствии с интерфейсом, а не с реализацией. Не объявляйте переменные как экземпляры конкретных классов. Вместо этого придерживайтесь интерфейса, определенного абстрактным классом. Это одна из наших ключевых идей.
Конечно, где-то в системе вам придется инстанцировать конкретные классы, то есть определить конкретную реализацию. Как раз это и позволяют сделать порождающие паттерны: абстрактная фабрика, строитель, фабричный метод, прототип и одиночка. Абстрагируя процесс создания объекта, эти паттерны предоставляют вам разные способы прозрачно ассоциировать интерфейс с его реализацией в момент инстанцирования. Использование порождающих паттернов гарантирует, что система написана в терминах интерфейсов, а не реализации.
Использоваие механизмов повторного использования:
наследование
делегирование
параметризированные типы
Проектирование с учетом будущих изменений.
Благодаря паттернам систему всегда можно модифицировать определенным образом. Каждый паттерн позволяет изменять некоторый аспект системы независимо от всех прочих, таким образом, она менее подвержена влиянию изменений конкретного вида.
Вот некоторые типичные причины перепроектирования, а также паттерны, которые позволяют этого избежать:
* при создании объекта явно указывается класс. Задание имени класса привязывает вас к конкретной реализации, а не к конкретному интерфейсу. Это может осложнить изменение объекта в будущем. Чтобы уйти от такой проблемы, создавайте объекты косвенно.
Паттерны проектирования: абстрактная фабрика, фабричный метод, прототип;
* зависимость от конкретных операций. Задавая конкретную операцию, вы ограничиваете себя единственным способом выполнения запроса. Если же не включать запросы в код, то будет проще изменить способ удовлетворения запроса как на этапе компиляции, так и на этапе выполнения.
Паттерны проектирования: цепочка обязанностей, команда;
* зависимость от аппаратной и программной платформ. Внешние интерфейсы операционной системы и интерфейсы прикладных программ (API) различны на разных программных и аппаратных платформах. Если программа зависит от конкретной платформы, ее будет труднее перенести на другие. Даже на «родной» платформе такую программу трудно поддерживать. Поэтому при проектировании систем так важно ограничивать платформенные зависимости.
Паттерны проектирования: абстрактная фабрика, мост;
* зависимость от представления или реализации объекта. Если клиент «знает», как объект представлен, хранится или реализован, то при изменении объекта может оказаться необходимым изменить и клиента. Сокрытие этой информации от клиентов поможет уберечься от каскада изменений.
Паттерны проектирования: абстрактная фабрика, мост, хранитель, заместитель;
* зависимость от алгоритмов. Во время разработки и последующего использования алгоритмы часто расширяются, оптимизируются и заменяются. Зависящие от алгоритмов объекты придется переписывать при каждом изменении алгоритма. Поэтому алгоритмы, вероятность изменения которых высока, следует изолировать.
Паттерны проектирования: мост, итератор, стратегия, шаблонный метод, посетитель;
* сильная связанность. Сильно связанные между собой классы трудно использовать порознь, так как они зависят друг от друга. Сильная связанность приводит к появлению монолитных систем, в которых нельзя ни изменить, ни удалить класс без знания деталей и модификации других классов. Такую систему трудно изучать, переносить на другие платформы и сопровождать. Слабая связанность повышает вероятность того, что класс можно будет повторно использовать сам по себе. При этом изучение, перенос, модификация и сопровождение системы намного упрощаются. Для поддержки слабо связанных систем в паттернах проектирования применяются такие методы, как абстрактные связи и разбиение на слои.
Паттерны проектирования: абстрактная фабрика, мост, цепочка обязанностей, команда, фасад, посредник, наблюдатель;
* расширение функциональности за счет порождения подклассов. Специализация объекта путем создания подкласса часто оказывается непростым делом. С каждым новым подклассом связаны фиксированные издержки реализации (инициализация, очистка и т.д.). Для определения подкласса необходимо также ясно представлять себе устройство родительского класса. Например, для замещения одной операции может потребоваться заместить и другие. Замещение операции может оказаться необходимым для того, чтобы можно было вызвать унаследованную операцию. Кроме того, порождение подклассов ведет к комбинаторному росту числа классов, поскольку даже для реализации простого расширения может понадобиться много новых подклассов. Композиция объектов и делегирование - гибкие альтернативы наследованию для комбинирования поведений. Приложению можно добавить новую функциональность, меняя способ композиции объектов, а не определяя новые подклассы уже имеющихся классов. С другой стороны, при интенсивном использовании композиции объектов проект может оказаться трудным для понимания. С помощью многих паттернов проектирования удается построить такое решение, где специализация достигается за счет определения одного подкласса и комбинирования его экземпляров с уже существующими.
Паттерны проектирования: мост, цепочка обязанностей, компоновщик, декоратор, наблюдатель, стратегия;
* неудобства при изменении классов. Иногда нужно модифицировать класс, но делать это неудобно. Допустим, вам нужен исходный код, а его нет (так обстоит дело с коммерческими библиотеками классов). Или любое изменение тянет за собой модификации множества существующих подклассов. Благодаря паттернам проектирования можно модифицировать классы и при таких условиях.
Паттерны проектирования: адаптер, декоратор, посетитель.
ЛЕКЦИЯ 5. Порождающие паттерны.
Порождающие паттерны проектирования абстрагируют процесс инстанцирования. Они помогут сделать систему независимой от способа создания, композиции и представления объектов. Паттерн, порождающий классы, использует наследование, чтобы варьировать инстанцируемый класс, а паттерн, порождающий объекты, делегирует инстанцирование другому объекту.
Эти паттерны оказываются важны, когда система больше зависит от композиции объектов, чем от наследования классов. Получается так, что основной упор делается не на жестком кодировании фиксированного набора поведений, а на определении небольшого набора фундаментальных поведений, с помощью композиции которых можно получать любое число более сложных. Таким образом, для создания объектов с конкретным поведением требуется нечто большее, чем простое инстанцирование класса.
Для порождающих паттернов актуальны две темы. Во-первых, эти паттерны инкапсулируют знания о конкретных классах, которые применяются в системе. Во-вторых, скрывают детали того, как эти классы создаются и стыкуются. Единственная информация об объектах, известная системе, - это их интерфейсы, определенные с помощью абстрактных классов. Следовательно, порождающие паттерны обеспечивают большую гибкость при решении вопроса о том, что создается, кто это создает, как и когда. Можно собрать систему из «готовых» объектов с самой различной структурой и функциональностью статически (на этапе компиляции) или динамически (во время выполнения).
Abstract Factory (абстрактная фабрика)
Предоставляет интерфейс для создания семейств, связанных между собой, или независимых объектов, конкретные классы которых неизвестны.
Builder (строитель)
Отделяет конструирование сложного объекта от его представления, позволяя использовать один и тот же процесс конструирования для создания различных представлений.
Factory Method (фабричный метод)
Определяет интерфейс для создания объектов, при этом выбранный класс инстанцируется подклассами.
Prototype (прототип)
Описывает виды создаваемых объектов с помощью прототипа и создает новые объекты путем его копирования.
Singleton (одиночка)
Гарантирует, что некоторый класс может иметь только один экземпляр, и предоставляет глобальную точку доступа к нему.
Паттерн Аспект, который можно изменять
Абстрактная фабрика Семейства порождаемых объектов
Одиночка Единственный экземпляр класса
Прототип Класс, из которого инстанцируется объект
Строитель Способ создания составного объекта
Фабричный метод Инстанцируемый подкласс объекта
Пример задачи.
class Room : public MapSite { };
class Wall : public MapSite { };
class Door : public MapSite { … private: Room* _room1; Room* _room2; };
class Maze {};
Maze* MazeGame::CreateMaze () {
Maze* aMaze = new Maze;
Room* rl = new Room(l);
Room* r2 = new Room(2);
Door* theDoor = new Door(rl, r2);
aMaze->AddRoom(rl);
aMaze->AddRoom(r2);
rl->SetSide(North, new Wall);
…
r2->SetSide(North, new Wall);
…
return aMaze;
}
Самое серьезное препятствие лежит в жестко зашитой в код информации о том, какие классы инстанцируются. С помощью порождающих паттернов можно различными способами избавиться от явных ссылок на конкретные классы из кода, выполняющего их инстанцирование:
* если CreateMaze вызывает виртуальные функции вместо конструкторов для создания комнат, стен и дверей, то инстанцируемые классы можно подменить, создав подкласс MazeGame и переопределив в нем виртуальные функции. Такой подход применяется в паттерне фабричный метод;
* когда функции CreateMaze в качестве параметра передается объект, используемый для создания комнат, стен и дверей, то их классы можно изменить, передав другой параметр. Это пример паттерна абстрактная фабрика;
* если функции CreateMaze передается объект, способный целиком создать новый лабиринт с помощью своих операций для добавления комнат, дверей и стен, можно воспользоваться наследованием для изменения частей лабиринта или способа его построения. Такой подход применяется в паттерне строитель;
* если CreateMaze параметризована прототипами комнаты, двери и стены, которые она затем копирует и добавляет к лабиринту, то состав лабиринта можно варьировать, заменяя одни объекты-прототипы другими. Это паттерн прототип.
* последний из порождающих паттернов, одиночка, может гарантировать наличие единственного лабиринта в игре и свободный доступ к нему со стороны всех игровых объектов, не прибегая к глобальным переменным или функциям. Одиночка также позволяет легко расширить или заменить лабиринт, не трогая сущегтвующий код.
АБСТРАКТНАЯ ФАБРИКА.
class MazeFactory {
public:
MazeFactory();
virtual Maze* MakeMaze() const
{ return new Maze; }
virtual Wall* MakeWall() const
{ return new Wall; }
virtual Room* MakeRoom(int n) const
{ return new Room(n); }
virtual Door* MakeDoor(Room* rl, Room* r2) const
{ return new Door(rl, r2); }
};
Maze* MazeGame::CreateMaze (MazeFactory& factory) {
Maze* aMaze = factory.MakeMaze();
Room* rl = factory.MakeRoom(l);
Room* r2 = factory.MakeRoom(2);
Door* aDoor = factory.MakeDoor(rl, r2);
aMaze->AddRoom(rl.) ;
aMaze->AddRoom(r2);
… }
Конкретная фабрика:
class EnchantedMazeFactory : public MazeFactory { … };
Room* EnchantedMazeFactory::MakeRoom() const { return new EnchantedRoom; }
ФАБРИЧНЫЙ МЕТОД.
class MazeGame { public:
Maze* CreateMaze();
// фабричные методы:
virtual Maze* MakeMazeO const
{ return new Maze; }
virtual Room* MakeRoom(int n) const
…
};
Maze* MazeGame::CreateMaze (){
Maze* aMaze = MakeMaze();
Room* rl = MakeRoom(l);
Room* r2 = MakeRoom(2);
Door* theDoor = MakeDoor(rl, r2);
…
}
Параметризованный фабричный метод:
Product* MyCreator::Create (int id) {
if (id == 0) return new YourProduct;
if (id == 1) return new MyProduct;
return Creator::Create(id);
}
СТРОИТЕЛЬ.
class MazeBuilder {
public:
virtual void BuildMaze() { }
virtual void BuildRoom(int room) { }
virtual void BuildDoor(int roomFrom, int roomTo) { }
virtual Maze* GetMaze() { return 0; }
…
};
Maze* MazeGame::CreateMaze (MazeBuilder& builder) {
builder.BuildMaze();
builder.BuiIdRoom(l);
builder.BuiIdRoom(2) ;
builder.BuildDoor(1, 2);
return builder.GetMaze();
}
ОДИНОЧКА.
class MazeFactory {
public:
static MazeFactory* Instance();
…
private:
static MazeFactory* _instance;
};
Реализация:
MazeFactory* MazeFactory::_instance = 0;
MazeFactory* MazeFactory::Instance() {
if (_instance == 0) {
_instance = new MazeFactory;
}
return _instance;
}
ЛЕКЦИЯ 6. Структурные паттерны.
В структурных паттернах рассматривается вопрос о том, как из классов и объектов образуются более крупные структуры. Структурные паттерны уровня класса используют наследование для составления композиций из интерфейсов и реализаций.
Вместо композиции интерфейсов или реализаций структурные паттерны уровня объекта компонуют объекты для получения новой функциональности. Дополнительная гибкость в этом случае связана с возможностью изменить композицию объектов во время выполнения, что недопустимо для статической композиции классов.
Adapter (адаптер)
Преобразует интерфейс класса в некоторый другой интерфейс, ожидаемый клиентами. Обеспечивает совместную работу классов, которая была бы невозможна без данного паттерна из-за несовместимости интерфейсов.
Bridge (мост)
Отделяет абстракцию от реализации, благодаря чему появляется возможность независимо изменять то и другое.
Composite (компоновщик)
Группирует объекты в древовидные структуры для представления иерархий типа «часть-целое». Позволяет клиентам работать с единичными объектами так же, как с группами объектов.
Decorator (декоратор)
Динамически возлагает на объект новые функции. Декораторы применяются для расширения имеющейся функциональности и являются гибкой альтернативой порождению подклассов.
Facade (фасад)
Предоставляет унифицированный интерфейс к множеству интерфейсов в некоторой подсистеме. Определяет интерфейс более высокого уровня, облегчающий работу с подсистемой.
Flyweight (приспособленец)
Использует разделение для эффективной поддержки большого числа мелких объектов.
Proxy (заместитель)
Подменяет другой объект для контроля доступа к нему.
Паттерн Аспект, который можно изменять
Адаптер Интерфейс к объекту
Декоратор Обязанности объекта без порождения под класса
Заместитель Способ доступа к объекту, его местоположение
Компоновщик Структура и состав объекта
Мост Реализация объекта
Приспособленец Накладные расходы на хранение объектов
Фасад Интерфейс к подлистеме
АДАПТЕР КЛАССОВ.
class Shape {
public:
Shape();
virtual void BoundingBox(Point& bottomLeft, Point& topRight) const;
virtual Manipulator* CreateManipulator() const;
};
class TextView {
public:
TextView();
void GetOrigin(Coord& x, Coord& y) const;
void GetExtent(Coord& width, Coord& height) const;
virtual bool IsEmpty() const;
};
Класс TextShape адаптирует интерфейс TextView к интерфейсу Shape.
class TextShape : private TextView, public Shape {
public:
TextShape();
virtual void BoundingBox(Point& bottomLeft, Point& topRight) const;
virtual bool IsEmpty() const;
virtual Manipulator* CreateManipulator() const;
};
bool TextShape::IsEmpty () const { return TextView::IsEmpty(); }
АДАПТЕР ОБЪЕКТОВ.
class TextShape : public Shape {
public:
TextShape(TextView*);
virtual void BoundingBox(Point& bottomLeft, Point& topRight} const;
virtual bool IsEmpty() const;
virtual Manipulator* CreateManipulator() const;
private:
TextView* _text;
};
bool TextShape::IsEmpty () const { return _text->IsEmpty(); }
КОМПОНОВЩИК.
Определяет древовидную структуру простых и составных объектов.
class Component {
public:
//...
virtual Composite* GetComposite() { return 0; }
};
class Composite : public Component {
private:
vector<Component*> m_Children;
public:
void Add(Component*);
// ...
virtual Composite* GetComposite(} { return this; }
};
class Leaf : public Component {
// ...
};
Где-то в методе:
Composite* aComposite = new Composite;
Leaf* aLeaf = new Leaf;
Component * aComponent;
Composite* test;
aComponent = aComposite;
if (test = aComponent->GetComposite()) {
test->Add(new Leaf);
}
aComponent = aLeaf;
if (test = aComponent->GetComposite()) {
test->Add(new Leaf); // не добавит лист
}
ДЕКОРАТОР.
Динамическое добавление новой функциональности.
class VisualComponent {
public:
VisualComponent();
virtual void Draw();
virtual void Resize();
};
class BorderDecorator : public Decorator {
public:
BorderDecorator (VisualComponent*, int borderWidth) ;
virtual void Draw();
private:
void DrawBorder ( int ) ;
int _width;
};
void BorderDecorator::Draw () {
Decorator::Draw ( ) ;
DrawBorder (_width) ;
}
void Window::SetContents (VisualComponent* contents) { … }
Window* window = new Window;
TextView* textView = new TextView;
window->SetContents( new BorderDecorator( new ScrollDecorator(textView), 1 ) );
ФАСАД.
class Compiler {
public: Compiler();
virtual void Compile(istream&, BytecodeStream&); };
void Compiler::Compile (istream& input, BytecodeStream& output) {
Scanner scanner(input);
ProgramNodeBuilder builder;
Parser parser;
parser.Parse(scanner, builder);
RISCCodeGenerator generator(output);
ProgramNode* parseTree = builder.GetRootNode();
parseTree->Traverse(generator);
}
ЛЕКЦИЯ 7. Паттерны поведения.
Паттерны поведения связаны с алгоритмами и распределением обязанностей между объектами. Руечь в них идет не только о самих объектах и классах, но и о типичных способах взаимодействия. Паттерны поведения характеризуют сложный поток управления, который трудно проследить во время выполнения программы. Внимание акцентировано не на потоке управления как таковом, а на связях между объектами.
Chain of Responsibility (цепочка обязанностей)
Можно избежать жесткой зависимости отправителя запроса от его получателя, при этом запросом начинает обрабатываться один из нескольких объектов. Объекты-получатели связываются в цепочку, и запрос передается по цепочке, пока какой-то объект его не обработает.
Command (команда)
Инкапсулирует запрос в виде объекта, позволяя тем самым параметризовывать клиентов типом запроса, устанавливать очередность запросов, протоколировать их и поддерживать отмену выполнения операций.
Interpreter (интерпретатор)
Для заданного языка определяет представление его грамматики, а также интерпретатор предложений языка, использующий это представление.
Iterator (итератор)
Дает возможность последовательно обойти все элементы составного объекта, не раскрывая его внутреннего представления.
Mediator (посредник)
Определяет объект, в котором инкапсулировано знание о том, как взаимодействуют объекты из некоторого множества. Способствует уменьшению числа связей между объектами, позволяя им работать без явных ссылок друг на друга. Это, в свою очередь, дает возможность независимо изменять схему взаимодействия.
Memento (хранитель)
Позволяет, не нарушая инкапсуляции, получить и сохранить во внешней памяти внутреннее состояние объекта, чтобы позже объект можно было восстановить точно в таком же состоянии.
Observer (наблюдатель)
Определяет между объектами зависимость типа один-ко-многим, так что при изменении состоянии одного объекта все зависящие от него получают извещение и автоматически обновляются.
State (состояние)
Позволяет объекту варьировать свое поведение при изменении внутреннего состояния. При этом создается впечатление, что поменялся класс объекта.
Strategy (стратегия)
Определяет семейство алгоритмов, инкапсулируя их все и позволяя подставлять один вместо другого. Можно менять алгоритм независимо от клиента, который им пользуется.
Template Method (шаблонный метод)
Определяет скелет алгоритма, перекладывая ответственность за некоторые его шаги на подклассы. Позволяет подклассам переопределять шаги алгоритма, не меняя его общей структуры.
Visitor (посетитель)
Представляет операцию, которую надо выполнить над элементами объекта. Позволяет определить новую операцию, не меняя классы элементов, к которым он применяется.
Паттерн Аспект, который можно изменять
Интерпретатор Грамматика и интерпретация языка
Итератор Способ обхода элементов агрегата
Команда Время и способ выполнения запроса
Наблюдатель Множество объектов, зависящих от другого объекта; способ, которым зависимые объекты поддерживают себя в актуальном состоянии
Посетитель Операции, которые можно применить к объекту или объектам, не меняя класса
Посредник Объекты, взаимодействующие между собой, и способ их коопераций
Состояние Состояние объекта
Стратегия Алгоритм
Хранитель Закрытая информация, хранящаяся вне объекта, и время ее сохранения
Цепочка обязанностей Объект, выполняющий запрос
Шаблонный метод Шаги алгоритма
НАБЛЮДАТЕЛЬ.
class Subject
{
private: Observer* _obsr;
char* _state;
public: void Notify()
{
_obsr.Update();
}
void SetState( char* s ) { _state = s; Notify(); }
void SetObserver( Observer* o) { _obsr = o; }
};
class Observer {
virtual void Update()=0;
};
class StateObserver : Observer {
private: char* _name;
Subject* _subject;
Public: void Update()
{
cout<<"Observer"<<_name<<“ knows new subj state:”<<_subject->GetState();
}
public SetSubject(Subject* subj) { _subject = subj; }
}
};
void main()
{
Subject* s = new Subject;
StateObserver* so = new StateObserver;
so->SetSubject( s );
s->SetObserver( so );
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
s->SetState( "ABC" );
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
}
СТРАТЕГИЯ.
class SortStrategy
{
virtual void Sort(vector<char*> list);
}
class QuickSort : SortStrategy // ShellSort, TreeSort, …
{
public: void Sort(vector<char*> list)
{
...
}
}
class SortedList {
private:
vector<char*> _list;
SortStrategy* _sortstrategy;
public:
…
void Add( char* elem ) {…}
void SetSortStrategy(SortStrategy* ssg)
{
_sortstrategy = ssg;
}
void SortElems()
{
_sortstrategy->Sort( _list );
...
}
}
}
SortedList* students = new SortedList;
Students->Add("Samual");
Students->Add("Jimmy");
Students->SetSortStrategy( new QuickSort );
Students->Sort();
ШАБЛОННЫЙ МЕТОД.
class Poster
{
public:
…
void AllInfo();
private:
virtual void PreInfo() { cout << "Today:" << endl; }
};
void Poster::AllInfo()
{
PreInfo();
cout << "…" << endl;
}
class HolidayPoster : public Poster
{
char* _holiday;
public:
void PreInfo() { cout << "IT’S " << _holiday << endl; }
}
void main() {
HolidayPoster* hp = new Poster;
Poster* p = hp;
p->AllInfo(); // hp->AllInfo();
}
To Be Continued…
