|
ООП С Sharp - Иерархии и дизайн классов
В процедурных языках и даже в некоторых более совершенных языках, таких как Visual Basic, значительный акцент делается на разбиении программы на функции. Объектная ориентация смещает этот акцент дизайна программы в сторону от функциональности программы к объектам, из которых она состоит.
Наследование является чрезвычайно важным средством объектно-ориентированного программирования, и ключевая стадия дизайна программ заключается в проектировании иерархий классов — то есть отношений между классами. Вообще, как и в случае с примером Mortimer Phones, вы обнаружите, что ваша программа в конечном итоге будет состоять из множества специализированных объектов, которые будут представлять собой конкретные типы более обобщенных объектов.
Когда вы проектируете классы, обычно удобнее всего использовать диаграммы, известные под названием диаграмм иерархий классов, которые иллюстрируют отношения между различными базовыми и унаследованными классами вашей программы. Традиционно диаграммы иерархий классов изображаются с базовыми классами вверху и стрелками, направленными от классов-наследников к непосредственным предкам. Например, иерархия классов для примера MortimerPhones3, представленного ниже, выглядит так, как показано на рис. А.3.
Эта диаграмма иерархии классов подчеркивает, что наследование может быть прямым и непрямым. В данном примере Nevermore60Customer непосредственно наследуется от Customer и непрямо — от Object. Хотя примеры в нашей дискуссии больше сосредоточены вокруг прямого наследования, все те же принципы в той же мере относятся к непрямому наследованию одного класса от другого.
Рис. А.3. Иерархия классов для примера MortimerPhones3
Другой пример касается одной из иерархий базовых классов .NET. В главе 23 было показано, как использовать базовые классы, инкапсулирующие окна (или, в более современной терминологии .NET — формы). Вы можете даже не представлять, насколько богатая иерархия может стоять за некоторыми элементами управления, которые вы можете помещать в окна (рис. А.4).
Рис. А.4. Иерархия классов для элементов управления
Класс Form представляет обобщенное окно, а ScrollBar, StatusBar, Button, TextBox и RichTextBox — соответствующие знакомые элементы управления. Богатая иерархия, лежащая в основе этих классов, обеспечивает возможность тонкой настройки того, какие реализации каких методов можно сделать общими для множества различных классов. Многие из этих классов также реализуют определенные интерфейсы, посредством которых они могут представить свою оконную природу клиентскому коду.
Также важно понимать, что иерархии классов, как и все прочие аспекты программирования, являются областью, в которой возможны самые разные решения — каждое со своими преимуществами и недостатками. Так, для примера Mortimer Phones возможны и другие способы дизайна классов. Одним из аргументов в пользу выбранной иерархии является то, что абоненты часто могут менять свои тарифные планы — и захотите ли вы каждый раз разрушать объект абонента и создавать новый, но другого класса? Возможно, будет лучше иметь только один класс абонентов, который содержит ссылку на объект тарифного плана, и иметь иерархию тарифных планов? Крупное приложение может иметь не одну иерархию, а чаще реализует большое количество иерархий, которые, возможно, состоят из сотен классов. Это может показаться устрашающим, но альтернатива, которой приходилось пользоваться до появления объектно-ориентированного программирования, предполагала написание буквально тысяч функций, из которых состояла программа, причем не было никаких средств группирования их в какие-то управляемые модули. Классы обеспечивают очень эффективный способ разбиения программы на небольшие разделы. Это не только облегчает сопровождение, но также делает программы более интуитивно понятными.
Проектируя классы, очень важно также тщательно продумывать разделение между общедоступным интерфейсом, представляемым клиентскому коду, и приватной внутренней реализацией. ообще, чем большую часть класса удается сохранить приватной, тем более структурированной становится программа — в том смысле, что вы можете модифицировать и совершенствовать внутреннюю реализацию одного класса, будучи уверенным, что это не разрушит, и даже никак не повлияет на другие части программы. Эта причина того, что часто подчеркивается, что поля-члены, в частности, почти всегда должны быть безусловно приватными, если только это не константы или они не формируют часть структуры, основное назначение которой состоит в группировании вместе небольшого количества полей. В данном приложении мы не вполне строго придерживаемся этого правила, но лишь для того, чтобы примеры оставались предельно простыми.
Класс Object
Один важный момент, который не раскрывается в коде примеров с Mortimer Phones, заключается в том, что сам Customer унаследован от другого класса — System.Object. Это — правило, продиктованное .NET и C#: все классы .NET должны в конечном итоге наследоваться от базового класса по имени Object. В коде C#, если вы пишете класс и не указываете, от какого класса он наследуется, компилятор по умолчанию использует System.Object в качестве базового класса. Это значит, что все объекты в .NET Framework имеют определенные методы, унаследованные от класса Object, включая ToString() и GetType(), о которых мы говорили в начале книги. Более подробно класс Object описан в главе 11.
Одиночное и множественное наследование
В C# каждый класс может быть унаследован только от одного базового класса (хотя при желании вы можете создать сколько угодно классов-наследников, происходящих от одного и того же базового класса). Это описывается термином одиночное наследование. Некоторые другие языки, включая C++, позволяют писать классы, имеющие более одного базового класса, что известно под названием множественное наследование.
|