Программерские типсы и триксы Обзор асинхронных фреймворков для Python

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

Объектно-ориентированное программирование позволяет создавать сложные приложения, код которых при этом достаточно просто читать, сопровождать и расширять. При правильном использовании ООП зачастую можно значительно уменьшить количество комментариев. Но, к сожалению, базовые принципы ООП (инкапсуляция, полиморфизм и наследование] зачастую не могут обеспечить ту гибкость, которая требуется для активно развивающихся систем. В свою очередь это приводит к логическим ошибкам, дублированию кода и неэффективной архитектуре. Поэтому для более-менее серьезных проектов спецы в области ООП предпочитают использовать разнообразные паттерны проектирования, которые позволяют без особых усилий обеспечить повторное использование кода. Собственно повторное использование кода — это одна из причин, благодаря которой объектно- ориентированное программирование стало так популярно. Если кто-то сомневается, что все эти конструкторы, деструкторы, виртуальные функции и прочие 00П-штуки не могут полностью перекрыть все потребности в современном программировании, то я приведу простой пример, который поможет в это поверить. Итак, представь, что ты создал интерактивный каталог сотовых телефонов. Это — твой бизнес, он приносит деньги, и его надо развивать. Тем более что индустрия мобильных звонилок не стоит на месте и тоже движется вперед семимильными шагами. Все было хорошо, когда мобилки были простыми, имели монохромные дисплеи и полифонические мелодии. В те времена код твоего каталога выглядел примерно так:

Иерархия классов мобильных телефонов class MobilePhone { public: void display() = в; void makeSound() { //код воспроизведения мелодии вызова }; } class Nokia3310 : public MobilePhone { void displayO // код вывода телефона на дисплей > class SiemensA35 : public MobilePhone { void displayO { // код вывода телефона на дисплей }; }  

Здесь у нас есть базовый класс MobilePhone, который описывает два общих для всех потомков метода: display! и makeSound. Причем makeSound имеет сразу и реализацию, ведь в сет рубки умеют воспроизводить только полифонию. А вот display является абстрактным методом, каждый дочерний класс реализует его по-своему, так как каждая мобилка имеет свой неповторимый и сногсшибательный дизайн.

И все у тебя было хорошо до тех пор, пока в телефонах не стали появляться фотокамеры, а вместе с ними и mpЗ-мелодии. И если mpЗ ты еще хоть как-то мог игнорировать (есть звук при звонке — и ладно), то отсутствие возможности запечатлеть любимую кошечку пользователи простить не смогут. А тут еще и конкурент объявился, у которого и mpЗ работает, и фотик фоткает. Надо что-то делать!

Классический ООП-подход

Первое, что приходит в голову — добавить метод makePhoto в базовый класс MobilePhone. Идея в принципе неплохая, если не считать того, что Nokia 3310 вдруг каким-то образом научилась фотографировать:).

Добавляем метод makePhoto class MobilePhone { public: void displayO = 0i void makeSound() { //код воспроизведения мелодии вызова }; // добавляем метод фотографирования void makePhotoQ { //код для фото > Nokia3310 mobilePhone; // Кирпичик от Nokia научился фотографировать // Так быть не должно mobilePhone.makePhoto();

Такой подход в данном случае совершенно неприемлем. У нас есть несколько тысяч трубок без фотокамеры и в будущем бесконечное множество мобилоксфотиками. Причем бюджетные модели с минимальным набором функций (читай — только звонят и отправляют SMS) тоже никто не отменял. Можно, конечно, переопределить метод makePhoto в телефонах, которые не умеют делать цифровые снимки, но это не наш метод, поскольку могут возникнуть ошибки, да и править несколько тысяч классов слишком лениво. И, кстати, у нас еще же висят в воздухе mpЗ-звонки. Напрягаем мозги придумываем новый архитектурный изыск.

Используем интерфейсы

Для того чтобы избежать проблем, которые у нас возникли при использовании стандартного механизма наследования, можно попробовать использовать интерфейсы — то есть сущности с описанием, но без реализации. Во многих языках программирования интерфейсы поддерживаются на нативном уровне, например, в Java или РНР, но в С++ такое понятие отсутствует. Но это не является большой проблемой, так как множественное наследование и абстрактные классы успешно справляются со всеми обязанностями интерфейсов. Создадим абстрактный класс (интерфейс) iPhotoCamera, который будет описывать метод makePhoto. При этом не забудем убрать его из класса MobilePhone. Теперьтолько мобильники с фотокамерами будут наследовать интерфейс iPhotoCamera, и наша «Нокиа 3310» уже не будет поражать всех пятимегапиксельными снимками.

Использование интерфейса iPhotoCamera

class MotorolaL9 : public MobilePhone, public iPhotoCamera { void makePhotoQ { // код, отвечающий за фото >; } class MotorolaL7 : public MobilePhone, public iPhotoCamera UML representation of NDL schemas Topology Layer Capability Doman serverlnterface UML-диаграммы помогают разобраться в хитросплетениях связей между классами { 

Все, казалось бы, хорошо, но мы не учли одну вещь — интерфейс предоставляет только описание методов, но не их реализацию. А это значит, что нам надо будет в классе каждой звонилки реализовывать makePhotol. Из-за этого мы получим дублирование кода и кучу потенциальных ошибок. Конечно, в срр можно схитрить и прикрутить реализацию makePhoto в классе iPhotoCamera, но даже несмотря на то, что тогда этот класс перестанет быть интерфейсом, мы все равно не решим проблему. Ведь камеры бывают разные: с автофокусом и без, с CMOS матрицей и CCD... От всех этих параметров будет зависеть реализация makePhoto, и определять один метод для всех классов просто нельзя. Очередная неудача, но мы уже близки к правильному решению. Еще немного скрипа мозгов — и все будет работать как надо.

Паттерн «Стратегия»

В основе любого паттерна проектирования лежит принцип, который требует отделить изменяющиеся куски кода от постоянных. Мы это уже практически сделали, перенеся функционал фотокамеры в отдельный интерфейс. Мы выделили аспект поведения, определив абстрактный класс iPhotoCamera, но не изолировали алгоритмы, реализующие этот аспект. Другими словами, код функции makePhoto надо тоже вынести из иерархии классов сотовых телефонов. Сделать это довольно просто — надо лишь определить несколько субклассов, базовым для которых является iPhotoCamera. На С++ это будет выглядеть так:

Выносим makePhotol из иерархии классов сотовых телефонов class iPhotoCamera { void makePhotoQ = в; } class DoPhoto : public iPhotoCamera { void makePhotoQ { // код, отвечающий за фото }; { class CantDoPhoto : public iPhotoCamera { void makePhotoQ { // пустой метод // для мобилок, которые не умеют фотографировать return; }; }  

Как видно из кода, мы создали два класса: DoPhoto и CantDoPhoto. Оба они наследуют интерфейс iPhotoCamera и реализуют метод makePhoto!. Таким образом, мы выделили аспект поведения (способность фотографировать) и закрепили за этим аспектом алгоритмы. Класс DoPhoto способен делать снимки, a CantDoPhoto — не способен, и тело makePhoto остается пустым. Мы разделили все мобильники на две группы и при этом избежали проблем, которые возникали при использовании предыдущих подходов. Нет дублирования кода, нет логических ошибок в духе «3310 обзавелся фотокамерой». Так же можно поступить и с воспроизведением mpЗ-мелодий.

Выносим реализацию mp3 и полифонии class iPhoneSound { void makeSound() = в;  class PolyphonySound : public iPhotoSound { void makeSound() { // код, воспроизводящий полифонические мелодии }; } class MpBSound : public iPhotoSound { void makeSound() { // код, воспроизводящий mpЗ }; }

Принцип тут точно такой же, как и с фотокамерой. Мы определили интерфейс iPhoneSound и реализовали в субклассах PolyphonySound и Mp3Sound метод makeSoundl). Такой подход, помимо решения перечисленных выше проблем, позволяет еще и менять поведение мобилок на лету, в процессе выполнения кода. Это на самом деле очень полезно и обеспечивает большую гибкость архитектуры. Мобильники с возможностью воспроизведения трЗ могут также хорошо пропищать что-нибудь в свой полифонический синтезатор. Пользователям нашего каталога может прийти в голову прослушать этот писк, и изменение поведения объекта на стадии выполнения нам очень пригодится. Но радоваться еще рано. Надо как-то привязать вновь созданные интерфейсы к классам мобильных телефонов. Для этого в базовом классе MobilePhone объявим две переменные, одна из которых будет иметь тип iPhoneCamera, а другая — iPhoneSound.

Интеграция поведения с классом MobilePhone class MobilePhone { protected: iPhotoCamera &photoBehavior; iPhotoSound SsoundBehavior; public:  

На Java часто пишут особо крупные серверные приложения. Грамотное построение архитектуры там просто необходимо

void displayQ = 0; void makeSoundQ { soundBehavior. makeSoundQ; }; void makePhotoQ { photoBehavior.makePhoto(); };  

Помимо интерфейсных переменных мы объявляем в классе MobilePhone еще и методы makePhoto и makeSound, но их выполнение делегируем photoBehavior и soundBehavior. Классы, описывающие конкретные модели телефонов будут инициализировать переменные photoBehavior и soundBehavior нужными реализациями соответствующих интерфейсов. Чтобы было понятнее, взглянем на код:

Субклассы MobilePhone class MotorolaL9 : public MobilePhone { MotorolaL9() { photoBehavior = new DoPhotoQ; soundBehavior = new Mp3SoundQ; }J > class Nokia3310 : public MobilePhone { Nokia3310Q { photoBehavior = new CantDoPhotoQ; soundBehavior = new PolyphonySoundQ; }; } Nokia3310 nokla;  //а вот 3310 не может фоткать даже при том, что вызов метода проходит успешно nokla.makePhotoQ; // и играет полифонии nokla.makeSoundQ;

В данном случае конкретную реализацию какого-либо интерфейса мы жестко привязываем к переменной в конструкторе на этапе компиляции. Это не совсем правильно, но в рамках нашей задачи не существенно. Теперь на наши мобильники не влияет изменение кода, ответственного за фото и звук. Мы можем добавлять сколь угодно много новых реализаций этих функций, и это никак не отразится на уже существующих классах.

Некоторые могут спросить, почему мы реализовали связь с интерфейсами при помощи отношения «содержит», а не «является». Иными словами, почему мы включили переменные photoBehaviorn soundBehavior внутрь класса MobilePhone, а не объявили базовыми классами конкретные реализации интерфейсов для классов сотовых телефонов? Основная причина заключается в том, что мы бы потеряли гибкость и не смогли динамически менять поведение объектов класса. Сейчас, чтобы заставить мобилку с mpЗ-мелодии переключиться на полифонию, достаточно использовать set-метод, который переопределит soundBehavior.

Динамическое изменение поведения class MotorolaL9 : public MobilePhone { MotorolaL9Q { photoBehavior = new DoPhotoQ; soundBehavior = new Mp3SoundQ; }; void setIPhoneSound(iPhoneSound &sound;) { soundBehavior = sound; };  

Все это вместе и есть паттерн «Стратегия». Если попытаться дать более-менее строгое определение, то можно сказать, что паттерн «Стратегия» определяет семейство алгоритмов, инкапсулирует каждый из них и обеспечивает их взаимозаменяемость. Он позволяет модифицировать алгоритмы независимо от их использования на стороне клиента.

Заключение

Теперь мы знаем, что такое паттерн «Стратегия». Более того, теперь мы поняли суть программирования с помощью паттернов, которая заключается в выделении непостоянных кусков кода и их изолировании от более стабильных частей. В следующих статьях мы познакомимся с другими приемами для проектирования больших приложений и еще больше приблизимся к уровню гуру 00П-программирования.

Заказать у нас создание сайтов в Ярославле. Чтобы ваш сайт был на лучших местах в поисковой выдаче, его нужно создать и раскрутить, и мы создаем сайты в Ярославле по доступным ценам. продвижение сайтов Ярославль, раскрутка

Похожие статьи Меню Опрос Фото Популярное