Объектно-ориентированный подход. Объектно_ориентированный_подход. Объектно ориентированный подход Мэтт Вайсфельд 5е международное издание ббк 32. 973. 2018
Скачать 5.43 Mb.
|
Пример из сферы электронного бизнеса Иногда трудно убедить человека, который принимает решения и не имеет ни- какого опыта разработки, в том, что повторное использование кода позволяет сэкономить финансовые средства. Однако при повторном использовании кода довольно легко понять преимущества этого подхода. В текущем разделе мы подробно рассмотрим простой, но практический пример того, как создать рабо- тоспособный фреймворк с применением наследования, абстрактных классов, интерфейсов и композиции. Проблема, касающаяся электронного бизнеса Пожалуй, наилучший способ понять всю мощь повторного использования — рассмотреть пример того, как оно может осуществляться. В этом примере мы прибегнем к наследованию (с помощью интерфейсов и абстрактных классов) и композиции. Наша цель — создать фреймворк, который сделает повторное использование кода реальностью, сократит время, уходящее на написание кода, и упростит сопровождение, то есть претворит в жизнь все то, что входит в ти- пичный список пожеланий при разработке программного обеспечения. Откроем наш собственный интернет-бизнес. Предположим, что у нас есть кли- ент — небольшая пиццерия под названием Papa’s Pizza. Хотя это небольшое семейное предприятие, его владелец осознает, что присутствие в интернете может во многих отношениях помочь бизнесу. Он хочет, чтобы клиенты смогли Глава.8..Фреймворки.и.повторное.использование 180 зайти на его сайт, узнать, что такое Papa’s Pizza, и заказать пиццу прямо из сво- их браузеров. На сайт, который мы будем разрабатывать, клиенты смогут зайти, выбрать там продукцию, которую они желают заказать, и указать вариант и время доставки. Они также смогут съесть заказанное в ресторане, забрать свой заказ самостоя- тельно либо получить его через курьера. Например, клиенту в 3:00 захочется заказать на ужин пиццу (с салатами, хлебными палочками и напитками), кото- рая должна быть доставлена к нему домой в 6:00. Допустим, этот клиент нахо- дится на работе (и у него, конечно же, сейчас перерыв). Он заходит на соответ- ствующий сайт и выбирает пиццы, указывая, какого они должны быть размера, чем должны быть покрыты и должна ли у них быть корочка. Затем он выбирает салаты, включая приправы, а также хлебные палочки и напитки. Далее клиент выбирает вариант доставки и просит, чтобы его заказ был доставлен к нему до- мой в 6:00. После этого он оплачивает заказ кредитной карточкой, получает номер подтверждения и выходит из системы. Через несколько минут он также получает подтверждение по электронной почте. Мы создадим систему учетных записей, благодаря чему люди, снова зашедшие на этот сайт, увидят приветствие с их именами, которое также будет содержать информацию о том, какая пицца у них любимая и какие новые виды пиццы готовятся на этой неделе. Когда программная система в конце концов будет готова, это будет означать громкий успех. На протяжении последующих нескольких недель клиенты Papa’s Pizza будут с радостью заказывать пиццы и другие блюда с напитками через интернет. Допустим, во время этого периода раскрутки родственник главы Papa’s Pizza, владеющий магазином пончиков под названием Dad’s Donuts, наносит ему визит. Глава Papa’s Pizza показывает владельцу Dad’s Donuts свою систему, которая последнему очень нравится. На следующий день владелец Dad’s Donuts звонит в нашу компанию и просит разработать веб-систему для его магазина пончиков. Это здорово и именно то, на что мы надеялись. Как мы теперь можем выгодно использовать код, который задействовали при создании системы для пиццерии, чтобы создать систему для магазина пончиков? Сколько еще небольших предприятий, помимо Papa’s Pizza и Dad’s Donuts, смогли бы воспользоваться преимуществами нашего фреймворка для того, чтобы преуспеть в интернете? Если у нас получится разработать хороший, на- дежный фреймворк, то мы сможем успешно создавать веб-системы, которые будут дешевле тех, что мы были в состоянии создавать прежде. Кроме того, есть и дополнительное преимущество: код будет уже протестирован и реализован, благодаря чему отладка и сопровождение должны стать намного проще. Подход без повторного использования кода По многим причинам концепция повторного использования кода не настолько успешна, насколько хотелось бы некоторым разработчикам программного обес- 181 Пример.из.сферы.электронного.бизнеса. . печения. Во-первых, при разработке систем повторное использование кода во многих случаях даже не принимается во внимание. Во-вторых, даже если оно и «входит в уравнение», такие вещи, как сжатые сроки графика, ограниченные ресурсы и бюджетные вопросы, часто становятся препятствием для самых луч- ших побуждений. Во многих ситуациях код в итоге получается тесно связанным с тем приложе- нием, для которого он был написан. Это означает, что код в приложении сильно зависит от другого кода в том же приложении. Повторное использование кода часто оказывается результатом операций вы- резания, копирования и вставки. Пока одно приложение открыто в текстовом редакторе, вы копируете код, а затем вставляете его в другое приложение. Ино- гда определенные функции или программы используются без внесения в них каких-либо изменений. К сожалению, нередко бывает так, что даже если большая часть кода и сможет остаться прежней, все равно придется немного изменить его для того, чтобы он смог работать в определенном приложении. Взгляните, к примеру, на два отдельных приложения, представленных на UML- диаграмме на рис. 8.6. Рис. 8.6. Приложения,.пути.которых.расходятся В этом примере приложения testDonutShop и testPizzaShop являются полностью независимыми модулями кода. Код каждого из них хранится отдельно, и ника- кого взаимодействия между этими модулями нет. Однако у этих приложений может быть кое-какой общий код. Фактически часть кода могла бы быть до- словно скопирована из одного приложения в другое. В определенный момент кто-либо, вовлеченный в проект, мог бы решить создать библиотеку таких со- вместно используемых фрагментов кода, чтобы использовать их в этих и других приложениях. Во многих слаженно организованных проектах такой подход Глава.8..Фреймворки.и.повторное.использование 182 хорошо работает. Использование стандартов программирования, управление конфигурациями, управление изменениями и т. д. отлично налажены. Однако во многих случаях порядок нарушается. Любому, кто знаком с процессом разработки программного обеспечения, из- вестно, что когда неожиданно обнаруживаются дефекты, а время имеет суще- ственное значение, возникает соблазн внести в систему кое-какие исправления или дополнения, специфичные для приложения, которое в них нуждается в данный момент. Это может решить проблему для одного приложения, но также способно непреднамеренно, возможно, пагубно, сказаться на других. Таким образом, в подобных ситуациях изначально совместно используемый код может разниться, и приходится сопровождать отдельные кодовые базы. Представим, например, что однажды сайт Papa’s Pizza перестал работать. Его владелец в панике звонит нам, и один из наших разработчиков может отследить проблему. Разработчик устраняет проблему, зная при этом, что внесенное ис- правление поможет, но не вполне уверен почему. Этот разработчик также дела- ет копию кода строго для использования в системе для Papa’s Pizza. Она будет ласково называться «Версия 2.01papa». Поскольку разработчик еще не полно- стью понимает проблему, а также потому, что система Dad’s Donuts и так от- лично работает, перенос кода в систему для магазина пончиков не осуществля- ется. ОТСЛЕЖИВАНИЕ ДЕФЕКТА ___________________________________________________________ Тот.факт,.что.в.системе.для.Papa’s.Pizza.оказался.дефект,.не.означает,.что.он.также. будет.и.в.системе.для.Dad’s.Donuts..Хотя.этот.дефект.привел.к.тому,.что.сайт.Papa’s. Pizza.перестал.работать,.с.сайтом.Dad’s.Donuts.этого.может.вообще.никогда.не. случиться..Возможно,.исправление.кода.как.в.системе.для.Papa’s.Pizza.окажется. более.опасным.для.Dad’s.Donuts,.чем.первоначальный.дефект. На следующей неделе владелец Papa’s Pizza снова в панике звонит, но уже с со- вершенно другой проблемой. Разработчик решает ее, опять-таки не зная при этом, как внесенное исправление повлияет на остальную часть системы, делает отдельную копию кода и называет ее «Версия 2.03dad». Этот сценарий разы- грывается для всех сайтов, которые сейчас находятся у нас в разработке. Теперь у нас дюжина или более копий кода с разными версиями для разных сайтов. Все это превращается в неразбериху. У нас имеется множество ветвей кода, и мы пересекли точку невозврата. Возможно, нам никогда не удастся объединить их снова (пожалуй, мы все же смогли бы, однако с точки зрения бизнеса это обо- шлось бы дорого). Наша цель состоит в том, чтобы избежать путаницы, как в приведенном ранее примере. Несмотря на то что во многих системах приходится решать проблемы, связанные с унаследованным кодом, к счастью для нас, приложения для Papa’s Pizza и Dad’s Donuts являются совершенно новыми системами. Поэтому мы 183 Пример.из.сферы.электронного.бизнеса. . можем проявить некоторую дальновидность и спроектировать требуемую си- стему, прибегнув к подходу, предполагающему повторное использование кода. Таким образом, мы избежим проблем сопровождения, описанных совсем не- давно. Для этого нам потребуется выделить как можно большую общность. При проектировании мы сосредоточимся на всех общих бизнес-функциях, присут- ствующих в веб-приложении. Вместо того чтобы располагать разными классами приложений вроде testPizzaShop и testDonutShop , мы можем создать конструк- цию, включающую класс Shop , который будет использоваться всеми приложе- ниями. Обратите внимание, что у testPizzaShop и testDonutShop имеются одинаковые интерфейсы getInventory() и buyInventory() . Мы выделим эту общность и сде- лаем так, чтобы все приложения, соответствующие нашему фреймворку Shop , обязательно реализовывали методы getInventory() и buyInventory() . Это требование соответствия стандарту иногда называют контрактом. Четко изложив контракт об оказании услуг, вы изолируете код от одной реализации. В Java реализация контракта осуществляется с помощью интерфейса или абстрактно- го класса. Посмотрим, как это делается. Решение для электронного бизнеса Теперь взглянем, как использовать контракт для выявления общности этих систем. В данном случае мы создадим абстрактный класс для выявления общ- ности в реализациях, а также интерфейс (уже знакомый нам Nameable ) для вы- явления общности в поведениях. Наша цель — создать специальные версии нашего веб-приложения со следую- щими функциями: интерфейсом Nameable , который будет частью контракта; абстрактным классом Shop , который тоже будет частью контракта; классом CustList , который мы используем при композиции; новой реализацией Shop для каждого клиента, которому будут предостав- ляться услуги. Объектная модель UML Новый созданный класс Shop будет хранилищем функциональности. Обратите внимание на рис. 8.7: методы getInventory() и buyInventory() были «подняты» по дереву иерархии из DonutShop и PizzaShop в абстрактный класс Shop . Теперь всякий раз, когда нам понадобится создать новую, специальную версию Shop , мы станем добавлять новую реализацию Shop (например, GroceryShop ). Shop — это контракт, которого должны придерживаться реализации: Глава.8..Фреймворки.и.повторное.использование 184 public abstract class Shop { CustList customerList; public void CalculateSaleTax() { System.out.println("Вычисление налога на продажу"); } public abstract String[] getInventory(); public abstract void buyInventory(String item); } Рис. 8.7. UML-диаграмма.модели.Shop Чтобы показать, как композиция вписывается в эту картину, класс Shop вклю- чает CustList . Таким образом, класс CustList содержится в Shop : public class CustList { String name; public String findCust() {return name;} public void addCust(String Name){} } 185 Пример.из.сферы.электронного.бизнеса. . Чтобы проиллюстрировать использование интерфейса в этом примере, опреде- ляется интерфейс Nameable : public interface Nameable { public abstract String getName(); public abstract void setName(String name); } Мы потенциально могли бы располагать большим количеством разных реали- заций, однако весь остальной код (приложение) был бы неизменным. В этом маленьком примере экономия кода может не показаться большой. Однако в крупном, реальном приложении экономия кода будет значительной. Взглянем на реализацию DonutShop : public class DonutShop extends Shop implements Nameable { String companyName; String[] menuItems = { "Пончики", "Маффины", "Пирожное из слоеного теста", "Кофе", "Чай" }; public String[] getInventory() { return menuItems; } public void buyInventory(String item) { System.out.println("\nВы только что приобрели" + item); } public String getName(){ return companyName; } public void setName(String name){ companyName = name; } } Глава.8..Фреймворки.и.повторное.использование 186 Реализация PizzaShop выглядит похожим образом: public class PizzaShop extends Shop implements Nameable { String companyName; String[] foodOfferings = { "Пицца", "Спагетти", "Овощной салат", "Антипасто", "Кальцоне" } public String[] getInventory() { return foodOfferings; } public void buyInventory(String item) { System.out.println("\nВы только что приобрели " + item); } public String getName(){ return companyName; } public void setName(String name){ companyName = name; } } В отличие от изначальной ситуации, когда присутствовало большое количество специальных приложений, теперь у нас имеется только один первичный класс ( Shop ) и разные специальные классы ( PizzaShop , DonutShop ). Связанность при- ложения с каким-либо из специальных классов отсутствует. Приложение свя- зано лишь с контрактом ( Shop ). Он определяет, что любая реализация Shop должна обеспечивать реализацию для двух методов — getInventory() и buyInventory() . Она также должна обеспечивать реализацию для getName() и setName() , которая связана с реализуемым интерфейсом Nameable Несмотря на то что такой подход решает проблему тесно связанных реализа- ций, нам все еще нужно решить, какую реализацию использовать. При текущей стратегии нам все же пришлось бы располагать отдельными приложениями. По сути, придется предусмотреть по одному приложению для каждой реали- 187 Пример.из.сферы.электронного.бизнеса. . зации Shop . Несмотря на использование нами контракта Shop , мы все равно находимся в такой же ситуации, что и раньше, когда не использовали этот контракт: DonutShop myShop= new DonutShop(); PizzaShop myShop = new PizzaShop (); Как же нам решить эту проблему? У нас есть возможность создавать объекты динамически. Используя Java, мы можем написать такой код: String className = args[0]; Shop myShop; myShop = (Shop)Class.forName(className).newInstance(); В данном случае значение для className задается после передачи параметра соответствующему коду (есть и другие способы задания значения для className , например использование системного свойства). Взглянем на Shop с применением этого подхода (обратите внимание, что обра- ботка исключений отсутствует и нет ничего другого, кроме создания экземпля- ра объекта). class TestShop { public static void main (String args[]) { Shop shop = null; String className = args[0]; System.out.println("Создание экземпляра класса:" + className + "\n"); try { // new pizzaShop(); shop = (Shop)Class.forName(className).newInstance(); } catch (Exception e) { e.printStackTrace(); } String[] inventory = shop.getInventory(); // показ списка товаров for (int i=0; i Глава.8..Фреймворки.и.повторное.использование 188 } // покупка товара shop.buyInventory(Inventory[1]); } } Таким образом, мы можем использовать один и тот же программный код как для PizzaShop , так и для DonutShop . Если мы добавим приложение GroceryShop , то нам потребуется лишь обеспечить реализацию и соответствующую строку в основном приложении. Изменять программный код не понадобится. Резюме При проектировании классов и объектных моделей жизненно важно понимать, как объекты связаны друг с другом. В этой главе мы рассмотрели основные во- просы создания объектов — наследование, интерфейсы и композицию. Из нее вы узнали, как создавать пригодный для повторного использования код путем проектирования с применением контрактов. В главе 9 мы завершим наше объектно-ориентированное «путешествие» и ис- следуем, как объекты, которые могут быть абсолютно несвязанными, способны взаимодействовать друг с другом. Ссылки Гради Буч, Роберт А. Максимчук, Майкл У. Энгл, Бобби Дж. Янг, Джим Коналлен и Келли А. Хьюстон, «Объектно-ориентированный анализ и про- ектирование с примерами приложений» (Object-Oriented Analysis and Design with Applications). — 3-е изд. — Бостон, штат Массачусетс: Addison-Wesley, 2007. Петер Коуд и Марк Мейфилд, «Проектирование на Java» (Java Design). — Аппер Сэддл Ривер, штат Нью-Джерси: Prentice-Hall, 1997. Скотт Майерс, «Эффективное использование C++» (Effective C++). — 3-е изд. — Бостон, штат Массачусетс: Addison-Wesley Professional, 2005. Глава 9 СОЗДАНИЕ ОБЪЕКТОВ И ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОЕКТИРОВАНИЕ В двух предыдущих главах мы рассмотрели темы наследования и композиции. Из главы 7 вы узнали, что наследование и композиция — это основные способы создания объектов. А из главы 8 вам стало известно, что существуют разные степени наследования, а также то, как наследование, интерфейсы, абстрактные классы и композиция сочетаются друг с другом. В этой главе рассматривается, как объекты связаны друг с другом в общей конструкции. Вы могли бы сказать, что эта тема уже была разобрана ранее, и оказались бы правы. И наследование, и композиция — способы взаимодей- ствия объектов. Однако между ними есть одно существенное различие в пла- не подхода к созданию объектов. При использовании наследования конечным результатом, по крайней мере концептуально, является класс, который вклю- чает все поведения и атрибуты иерархии наследования. А при использовании композиции для создания нового класса применяется один или несколько классов. Хотя наследование представляет собой отношение между двумя классами, на самом деле при использовании этого механизма создается родительский класс, который включает атрибуты и методы дочернего класса. Снова обратимся к при- меру классов Person и Employee (рис. 9.1). Несмотря на то что в данном случае действительно имеется два отдельно спро- ектированных класса, отношение между ними нельзя назвать просто взаимо- действием — это отношение наследования. По сути, Employee представляет собой Person . Объекту Employee не нужно отправлять сообщение объекту Person Объект Employee не нуждается в услугах Person , ведь объект Employee — это объект Person Однако в случае с композицией дело обстоит иначе. Композиция — это взаимо- действие между разными объектами. Таким образом, в то время как в главе 8 |