|
Программирование >> Аргументация конструирования
делает. Вводящие в заблуждение представления очень трудны для понимания и ведения программы. Кроме того, такие искажения могут привести к проблемам в будущем. Например, представьте себе, что банк изменит свою политику относительно чековых счетов. Скажем, он решит взимать гонорар за обслуживание чековгх счетов только в том случае, если минимальный баланс упадет ниже некоторого значения в течение месяца. Такое изменение политики банка можно легко отразить в классе checking. Все, что нужно сделать, - это добавить новый член в класс checking, чтобы следить за минимальным балансом в течение месяца. Назовем его minimurtiBalance. Однако теперь возникает проблема. Если Savings наследует Checking, значит, Savings тоже получает этот член. При этом он не используется, поскольку в сберегательных счетах минимальный баланс не нужен. Так что дополнительный член просто нрисутствует в классе. Итак, каждый объект чекового счета имеет доноднительный чле ва1апсе. Один дополнительный член - это не так уж и много, но он вносит свою лепту в общую неразбериху. Такие изменения имеют свойство накапливаться. Сегодня это один член, а завтра - измененная функция-член. В результате объекты класса Savings будут содержать множество дополнительных данных, которые нужны исключительно в классе Checking. Если вы будете невнимательны, изменения в классе Checking могут перейти к классу Savings и привести к его некорректной работе. Как же этого избежать? Если поменять местами Checking и Savings, проблема не исчезнет. Нужен некий третий класс (назовем его Account), который будет воплощать в себе все то общее, что есть у Checking и Savings. Такая связь приведена на рис. 23.3. AoooLTt iwithdravraloJ [ acxountNo{) j I пэдр oAccountsQ pRrst pNert OOLlt acx untNumber beEroB noWithdrawals Рис. 23.3. Классы Checking и Savings, базирующиеся на классе Account Каким образом создание нового класса Account решит наши проблемы? Во-первых, гакой класс сделает более аккуратным описание реального мира (чем бы он ни являлся). В нашей концепции мира (по крайней мере, в моей) действительно есть что-то, могущее назваться счетом. Сберегательные и чековые счета являются частным случаем этой более фундаментальной концепции. Кроме того, класс Savings отмежевывается от изменений в классе Checking (и наоборот). Если банк решит провести фундаментальные изменения во всех счетах, можно просто изменить класс Account, и все подклассы автоматически унаследуют эти изменения. Но если банк изменит политику только для чековых счетов, можно просто модифицировать класс Checking, не изменяя при этом класс Savings. Такая процедура отбора общих свойств похожих классов и называется разложением. Этот процесс очень важен в объектно-ориентированных языках по причинам, которые были приведены выше, а также потому, что разложение помогает избавиться от избыточности. Позвольте мне повториться: избыточность - это не просто плохо, это очень плохо... Разложение будет обоснованным только в том случае, когда взаимосвязь, представляемая наследованием, соответствует реальности. В]деление общих свойств класса Mouse и Joystick и разложение их на множители вполне допустимо. И мышь и джойстик являются аппаратными устройствами позиционирования. Но выделение общих свойств классов Mouse и Display ничем не обосновано. Разложение может давать (и обычно дает) результат на нескольких уровнях абстракции. Например, программа, написанная для более продвинутого банка, может иметь структуру классов, показанную на рис. 23.4. Из этого рисунка видно, что между классами Checking и Savings и более общим классом Account вставлен еще один класс. Он называется Conventional и объединяет в себе особенности обычных счетов. Другие типы счетов, например счета ценных бумаг и биржевые счета, также объявляются как отдельные классы. Такая многослойная структура классов весьма распространена и даже желательна (пока отношения, которые она представляет, отражают реальность. Однако не забывайте, что для любого заданного набора классов не существует одной единственно правильной иерархии классов). Представим, что банк позволяет держателям счетов удаленно обращаться к чековым счетам и счетам ценных бумаг. Снимать же деньги с других типов счетов можно только в банке. Хотя структура классов, приведенная на рис. 23.4, выглядит естественной, в данных условиях более приемлема другая структура 23.5). Программист должен решить, какая структура классов лучше всего подходит к данным условиям, и стремиться к наиболее ясному и естественному представлению. РеализациаЯных классов Такое интеллектуальное упражнение, как разложение, поднимает еще одну проблему. Вернемся к классам банковских счетов еще раз, а именно к общему базовому классу Account. На минуту задумайтесь над тем, как вы будете определять различные функции класса Account. Большинство функций-членов класса Account не составят проблем, поскольку оба тина счета реализуют их одинаково. Однако функция Account .withdrawal() отличается в зависимости от типа счета. Правила снятия со сберегательного и чекового счетов различны. Мы вынуждены реализовывать Savings : :withdrawal () не так, как Checking: : withdrawal (). Но как реализовать функцию Account: ;withdrawal () ? Попросим банковского служащего помочь нам. Я так и представляю себе эту беседу: Каковы правила снятия денег со счета? - спросите вы с надеждой. Какого именно счета, сберегательного или чекового? - ответит он вопросом на вопрос. Со счета, - скажете вы, просто со счета! Пустой взгляд в ответ... 9aJr Chokg CD MuLb! Flrtte Puc. 23.4. Развитая структура банковских счетов Рис. 23.5. Альтернативная иерархия классов Проблема в том, что такой вопрос не имеет смысла. Нет такой веши, как просто счет . Все счета (в даппом примере) должны быть чековыми или сберегательными. Концепция счета - это абстракция, с помощью которой мы объединяем общие свойства для конкретных счетов. Это незавершенная концепция, поскольку в ней отсутствует такое важное свойство, как функции () (если вы углубитесь в детали, то найдете и другие свойства, которых не хватает просто счету ). Абстрактный класс - это тот класс, который реализуется только в подклассе. Конкретный - тот, который не является абстрактным. Чтобы объяснить, что я имею в виду, позвольте позаимствовать пример из мира животныс. Наблюдая разные особи теплокровных и живородящих, вы можете заключить, что они все укладываются в концепцию под названием млекопитающие . Вы можете выделить такие классы млекопитающих, как собачьи, кошачьи и гуманоиды. Однако невозможно найти где-либо на земле просто млекопитающее. Другими словами, млекопитающие не могут содержать особь под названием млекопитающее . Млекопитающее - это концепция высокого уровня, которую создал человек, и экземпляров-млекопитающих не существует. Обратите внимание, что утверждать это с уверенностью я могу только по истечении некоторого времени. Ученые постоянно открывают новые виды животных. Проблема в том, что каждое существо обладает свойствами, которых не имеют другие; однако вполне вероятно, что в будущем кто-то найдет такое свойство у других существ. Отражая эту ситуацию, C++ предоставляет возможность оставлять абстрактные классы незавершенными.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки. |