Опубликован: 02.12.2009 | Уровень: специалист | Доступ: свободно | ВУЗ: Тверской государственный университет
Лекция 5:

Отношения между классами. Клиенты и наследники

Отношение вложенности

Рассмотрим два класса Provider и Client, связанных отношением вложенности. Оба класса применяются для демонстрации идей и потому устроены просто, не неся особой смысловой нагрузки. Пусть класс поставщик Provider уже построен. У класса три поля, одно из которых статическое, два конструктора, два метода - один статический и один динамический. Вот его текст:

/// <summary>
    /// Класс поставщик,предоставляет клиентам
    /// статический и зкземплярный методы,
    /// закрывая поля класса
    /// </summary>
    class Provider
    {
        //fields
        string fieldP1;
        int fieldP2;
        static int fieldPS;
        //Конструкторы класса
        /// <summary>
        /// Конструктор с аргументами
        /// </summary>
        /// <param name="p1">аргумент,инициализирующий поле класса</param>
        /// <param name="p2">аргумент,инициализирующий поле класса</param>
        public Provider(string p1, int p2)
        {
            fieldP1 = p1.ToUpper(); fieldP2 = p2*2;
            fieldPS = 0;
        }
        public Provider()
        {
            fieldP1 = ""; fieldP2 = 0; fieldPS = 0;
        }
        //Динамический (Экземплярный) метод
        public string MethodPD()
        {
            fieldPS++;
            string res = "Объект класса Provider" + "\n\r";
            res += string.Format("Мои поля: поле1 = {0}, поле2 = {1}",
                fieldP1, fieldP2);
            return res;
        }
        // Статический (Модульный) метод
        public static string MethodPS()
        {
            string res = "Модуль класса Provider" + "\n\r";
            res += string.Format("Число вызовов метода MethodPD = {0}",
                fieldPS.ToString());
            return res;
        }

Поля класса, как и положено, закрыты для клиентов. У класса, как и положено, есть конструктор без аргументов, инициализирующий поля класса соответствующими константами, и конструктор с аргументами, который преобразует переданные ему значения, прежде чем записать их в поля класса. Методы класса позволяют получить информацию, хранящуюся в полях. Динамический (экземплярный) метод MethodPD, которому доступны поля класса, хранимые экземплярами класса, возвращает строку с информацией о хранимых значениях в полях. Одновременно этот метод увеличивает значение, хранимое в статическом поле, которое можно рассматривать как счетчик общего числа вызовов динамического метода всеми объектами данного класса. Статический метод MethodPS, которому доступно только статическое поле, возвращает в качестве результата строку с информацией о числе вызовов динамического метода.

Построим теперь класс Client - клиента класса Provider. Класс будет устроен похожим образом. Существенное дополнение состоит в том, что одним из полей является объект provider класса Provider:

/// <summary>
    /// Клиент класса Provider
    /// </summary>
    class Client
    {
        //fields
        Provider provider;
        string fieldC1;
        int fieldC2;
        
        const string NEWLINE = "\n\r";
        //Конструкторы класса
        public Client(string p1, int p2, string c1, int c2)
        {
            fieldC1 = c1.ToLower(); fieldC2 = c2-2;
            provider = new Provider(p1,p2);
        }
        public Client()
        {
            fieldC1 = ""; fieldC2 = 0; 
            provider = new Provider();
        }
        /// <summary>
        /// Метод, использующий поле класса provider
        /// для работы с методами класса Provider
        /// </summary>
        /// <returns>композиция строк провайдера и клиента </returns>
        public string MethodClient1()
        {
            string res = provider.MethodPD() + NEWLINE;             
            res += "Объект класса Client" + NEWLINE;
            res += string.Format("Мои поля: поле1 = {0}, поле2 = {1}",
                fieldC1, fieldC2);
            return res;
        }
}

Обратите внимание: конструкторы клиента (класса Client ) создают объект поставщика (класса Provider ), вызывая конструктор поставщика. Для создания объектов поставщика могут требоваться аргументы, поэтому они передаются конструктору клиента, как это сделано в нашем примере.

Создавая объект класса Client, конструкторы этого класса создают и объект класса Provider, связывая его ссылкой с полем provider. Все динамические методы клиентского класса могут использовать этот объект, вызывая доступные клиенту методы и поля класса поставщика. Метод класса Client - MethodClient1 начинает свою работу с вызова: provider.MethodPD(), вызывая сервис, поставляемый методом класса Provider.

Подводя первые итоги, можно заметить, что клиент класса может создавать объекты класса поставщика, а затем, используя созданные объекты, получать доступ к сервисам, предоставляемым классом поставщика своим клиентам. Конечно, поставщик имеет закрытую часть класса, не доступную его клиентам. Как правило, для клиентов закрываются поля класса. Клиенты класса получают доступ к методам класса поставщика - совокупность этих методов определяет интерфейс класса поставщика и те сервисы, которые поставщик предоставляет своим клиентам. Напомним, что интерфейс класса составляют методы с модификатором доступа public или internal для дружественных классов.

Расширение определения клиента класса

До сих пор мы говорили, что клиент содержит поле, представляющее объект класса поставщика. Это частая, но не единственная ситуация, когда класс является клиентом другого класса. Рассмотрим еще три ситуации, когда один класс становится клиентом другого класса, хотя среди его полей и нет объектов, принадлежащих классу поставщика.

Первую возможную ситуацию демонстрирует процедура Main Windows-проекта. В ней "на лету" в момент вызова метода Run создается объект класса, наследуемого от класса Form. Создаваемый объект является результатом вычисления выражения, заданного операцией new. В этом случае у класса клиента нет ни поля, ни локальной переменной, связанной с объектом класса поставщика. Но и в этом случае клиент создает объект поставщика, передавая его затем в качестве аргумента методу Run класса Application.

Рассмотрим еще одну возможную ситуацию. В классе может быть метод, создающий локальный объект поставщика. Используя этот объект, в методе вызываются сервисы поставщика. Вся разница в том, что другим методам клиентского класса локальный объект недоступен и по завершении работы метода его жизнь заканчивается.

/// <summary>
        /// Метод, использующий локальный объект класса provider
        /// для работы с методами класса Provider
        /// </summary>
        /// <returns>композиция строк провайдера и клиента </returns>
        public string MethodClient3()
        {
            Provider local_provider =
                new Provider("Локальный объект Provider", 77);
            string res = local_provider.MethodPD() + NEWLINE;
            res += "Объект класса Client" + NEWLINE;
            res += string.Format("Мои поля: поле1 = {0}, поле2 = {1}",
                fieldC1, fieldC2);
            return res;
        }

Третья возможная ситуация - когда объекты поставщика не создаются ни конструктором, ни методами класса клиента, ни "на лету". Клиент использует готовый объект - модуль класса Provider, автоматически создаваемый для класса, у которого есть статические поля и статические методы. В этом случае класс поставщик сам создает свой статический объект, предоставляя возможность работы с ним всем своим клиентам. Через этот объект клиентам доступны только статические сервисы класса поставщика. Добавим в клиентский класс еще один метод:

/// <summary>
        /// Метод, использующий модуль Provider
        /// для работы со статическим методом класса Provider
        /// </summary>
        /// <returns>композиция строк провайдера и клиента </returns>
        public string MethodClient2()
        {
            string res = Provider.MethodPS() + NEWLINE;
            res += "Объект класса Client" + NEWLINE;
            res += string.Format("Мои поля: поле1 = {0}, поле2 = {1}",
                fieldC1, fieldC2);
            return res;
        }

Дадим теперь расширенное определение клиента.

Определение 3. Класс B называется клиентом класса A, если в классе B создаются объекты класса A или вызываются статические сервисы класса A.

Под сервисом класса понимается метод или поле класса, доступное клиентам, то есть объявленное с модификатором public или internal. Статический сервис - это сервис, объявленный с модификатором static.

Отношения между клиентами и поставщиками

Что могут делать клиенты и что могут делать поставщики? Класс поставщик создает сервисы, предоставляемые своим клиентам. Клиенты создают объекты поставщика. Вызывая доступные им сервисы, клиенты получают возможность выполнить работу, которую сами они выполнить не могут или не хотят выполнять, поскольку класс поставщик эту работу может сделать более квалифицированно. Так, например, арифметические классы - int и другие - не могут вычислять математические функции. При необходимости вычислить sin(x) они обращаются к соответствующему сервису, предоставляемому классом Math.

Клиенты не могут ни повлиять на поведение методов поставщика, ни изменить состав предоставляемых им сервисов, они не могут вызывать закрытые поставщиком поля и методы класса.

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

В заключение построим тест, проверяющий работу с объектами классов Provider и Client:

class Program
    {
        static void Main(string[] args)
        {
            Client client =
                new Client("object of class Provider", 33,
                    "object of class Client", 44);
            string res = client.MethodClient1();
            Console.WriteLine(res);
            res = client.MethodClient2();
            Console.WriteLine(res);
            res = client.MethodClient3();
            Console.WriteLine(res);
        }
    }

Согласно определению, класс Program является клиентом класса Client. В процедуре Main локально создается объект класса Client, в процессе работы конструктора которого создается и объект класса Provider. Вызываемые методы клиентского класса в процессе своей работы вызывают методы класса Provider. Результаты работы показаны на рис. 4.4.

Клиенты и поставщики

Рис. 4.4. Клиенты и поставщики
Сам себе клиент

Зададимся вопросом, может ли класс быть сам себе клиентом, другими словами, может ли поле класса быть объектом описываемого класса? Другой, не менее интересный вопрос: могут ли два класса быть одновременно клиентами и поставщиками друг для друга? Ответы на оба вопросы положительны, и подобные ситуации типичны и не являются какой-либо экзотикой.

Первая ситуация характерна для динамических структур данных. Элемент односвязного списка имеет поле, представляющее элемент односвязного списка; элемент двусвязного списка имеет два таких поля; узел двоичного дерева имеет два поля, представляющих узлы двоичного дерева. Эта ситуация характерна не только для рекурсивно определяемых структур данных. Вот еще один типичный пример. В классе Person могут быть заданы два поля – Father и Mother, задающие родителей персоны, и массив Children. Понятно, что все эти объекты могут быть того же класса Person.

Не менее часто встречается ситуация, когда классы имеют поля, взаимно ссылающиеся друг на друга. Типичным примером могут служить классы Man и Woman, первый из которых имеет поле wife класса Woman, а второй – поле husband класса Man.

Заметьте, классы устроены довольно просто, их тексты понятны, отношения между классами очевидны. А вот динамический мир объектов этих классов может быть довольно сложным, отношения между объектами могут быть запутанными, для объектов характерны не только любовные треугольники, но и куда более сложные фигуры.

Федор Антонов
Федор Антонов

Здравствуйте!

Записался на ваш курс, но не понимаю как произвести оплату.

Надо ли писать заявление и, если да, то куда отправлять?

как я получу диплом о профессиональной переподготовке?

Илья Ардов
Илья Ардов

Добрый день!

Я записан на программу. Куда высылать договор и диплом?