Главная страница
Навигация по странице:

  • Идентичность объектов

  • Ассоциация

  • Отношение использования

  • Курс ООП в С презентация. ООП в с++(полный курс). Объекты и классы


    Скачать 1.76 Mb.
    НазваниеОбъекты и классы
    АнкорКурс ООП в С презентация
    Дата21.02.2022
    Размер1.76 Mb.
    Формат файлаppt
    Имя файлаООП в с++(полный курс).ppt
    ТипДокументы
    #368655
    страница9 из 26
    1   ...   5   6   7   8   9   10   11   12   ...   26

    Идентичность объектов


    Идентичность – это такое свойство объекта, которое отличает его от всех других объектов. Однако следует отличать идентичность от адресуемости объекта (конкретного адреса объекта в памяти).
    struct point
    {
    //определяем точку на плоскости с координатами x и y
    int x, y;
    point() : x(0), y(0) {}
    point(int xv, int yv) : x(xv), y(yv) {}
    };
    class item
    {
    //экранный объект

    public:
    item(const point& location);
    item();
    //…
    };

    Идентичность объектов


    //объявление объектов класса item
    item A;
    item* B = new item(point(75, 75));
    item* C = new item(point(100, 100));
    item* D = 0;
    item& E = A;
    При выполнении этих операторов возникают пять имен, но только три разных объекта. Конкретно, в памяти будут отведены четыре ячейки под имена A, B, C и D. При этом A будет именем объекта, В, C и D будут указателями, а Е является другим именем для объекта А. Кроме того, лишь B и C будут на самом деле указывать на объекты класса item. У объектов, на которые указывают B и C нет имен, хотя на них можно ссылаться, "разыменовывая" соответствующие указатели: например, *B. Поэтому можно сказать, что B указывает на отдельный объект класса item, на имя которого можно косвенно ссылаться через *B. Уникальная идентичность (но не обязательно имя) каждого объекта сохраняется на все время его существования, даже если его внутреннее состояние изменилось.


    Обращение к элементам классов 


    К объектам программы можно обращаться не только по их имени, но и путём разыменования указателей. Атрибуты и методы классов также подразумевают возможность подобного обращения к себе.
    К элементам классов можно обращаться с помощью указателей. Для этого определены операции .* и ->*. Указатели на поля и методы класса определяются по-разному.
    Формат указателя на поле класса:
    тип_данных(имя_класса::*имя_указателя);
    В определение указателя можно включить его инициализацию в форме:
    &имя_класса::*имя_поля; // Поле должно быть public
    Если бы поле age класса person (см. выше) было объявлено как public, определение указателя на него имело бы вид:
    int (person::*page) = person::age;
    person a("Vasia",15);
    person* pa=&a;
    cout << a.*page; // Обращение через операцию .*
    cout << pa->*page; // Обращение через операцию ->*


    Указатели на поля классов не являются обычными указателями т. к. при присваивании им значений они не ссылаются на конкретный адрес памяти, поскольку память выделяется не под классы, а под объекты классов.
    Формат указателя на метод класса:
    возвр_тип (имя_класса::*имя_указателя)(параметры);
    Например, описание указателя на метод класса person
    int person::get_age() {return age;}
    а также на другие методы этого класса с такой же сигнатурой будет иметь вид:
    int (person::*pget)();//указатель на get_age()
    Такой указатель можно задавать в качестве параметра функции. Это дает возможность передавать в функцию имя метода:
    void fun(int (person:: *pget)())
    {
    (*this.*pget)(); // Вызов функции через операцию .*
    (this->*pget)(); // Вызов функции через операцию ->*
    }


    Можно настроить указатель на конкретный метод с помощью операции взятия адреса:
    //присваивание значения указателю:
    pget = & person::get_age;
    person a,*pa;
    pa = new person;
    // Вызов функции через операцию .* :
    int a_age= (a.*pget)();
    // Вызов функции через операцию ->* :
    int pa_age = (p->*pget)();
    Правила использования указателей на методы классов:
      Указателю на метод можно присваивать только адреса методов, имеющих соответствующий заголовок.
      Нельзя определить указатель на статический метод класса.
      Нельзя преобразовать указатель на метод в указатель на обычную функцию, не являющуюся элементом класса.

      Однако в отличие от указателя на переменную или обычную функцию, указатель на метод не ссылается на определенный адрес памяти. Он больше похож на индекс в массиве, поскольку задает смещение. Конкретный адрес в памяти получается путем сочетания указателя на метод с указателем на определенный объект.

    Отношения между классами и объектами


    Отношения двух любых объектов основываются на предположениях, которыми один объект обладает относительно другого: об операциях, которые можно выполнять над объектом и об его ожидаемом поведении.
    Если объект предоставляет свои ресурсы другим объектам, то он называется сервером, а если он их потребляет – клиентом.
    Связь – это специфическое сопоставление, через которое объект-клиент запрашивает услугу у объекта-сервера или через которое один объект находит путь к другому.
    Объект может являться сервером по отношению к одним объектам и клиентом по отношению к другим.


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


    Связь дает классу возможность узнавать об атрибутах, операциях и связях другого класса. В нотации языка UML взаимодействие между классами отражают связывающими их линиями.
    Между объектами могут существовать следующие виды отношений:
    Ассоциация – это смысловая связь, которая не имеет направления и не объясняет, как классы общаются друг с другом. Однако именно это требуется на ранней стадии анализа, поэтому мы фиксируем только участников, их роли и мощность отношения. На диаграммах UML эту связь отображают обыкновенной линией, связывающей классы


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


    Рассмотрим в качестве примера торговую точку. В этом примере можно выделить две абстракции – товары и продажи. Класс product – это то, что мы продали в некоторой сделке, а класс sale – сама сделка, в которой продано несколько товаров. Ассоциация является двунаправленной, т.к. зная товар, можно получить информацию о сделке, в которой он был продан, а, имея информацию о сделке, можно узнать, что было продано.
    Исходя из вышеозначенной диаграммы, для классического C++ мы получим два заголовочных файла с определениями используемых классов.


    //файл product.h
    #include "sale.h"
    class product
    {
    private:
    sale *LastSale;
    };
    //файл sale.h
    #include "product.h"
    class sale
    {
    private:
    //указатель на массив, содержащий совокупность товаров, //проданных в сделке
    product* Products
    };
    Это ассоциация представляет собой связь "один-ко-многим": каждый экземпляр товара относится только к одной последней продаже, в то время как каждый экземпляр sale может указывать на совокупность товаров.


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


    Объект класса sale ("продажа") содержит множество строк (объекты sale_line), каждая из которых указывает на описываемый ей товар типа product и на количество этого товара – quantity. Между строкой продажи и товаром мы имеем однонаправленную ассоциацию с мощностью связи "один-к-одному", поскольку классу product, описывающему товар, ничего не требуется знать о строке продажи, а та описывает потребность только в одном товаре.


    Если все сообщения отправляются только одним классом и прини­маются только другим классом, а не наоборот, между этими классами имеет место однонаправленная связь. Если хотя бы одно сообщение отправляется в обратную сторону, ассоциация должна быть двунаправленной.
    Связь вида "многие-ко-многим" обычно присутствует в качестве ассоциации только на ранних стадиях анализа.
    Допускается также указывать рядом с классами конкретные значения количества их экземпляров, если они заранее известны. Это непосредственно влияет на создаваемый по диаграммам код языка.
    Связи можно уточнить с помощью имен связей или ролевых имен. Имя связи – это обычно глагол или глагольная фраза, описывающая, зачем она нужна. В рассмотренном примере между классом sale (продажа) и классом sale_line (строка продажи) существует ассоциация. В связи с этим возникает вопрос понимания, чем является объект класса sale по отношению к объекту класса sale_line? Для того чтобы указать на это, ассоциацию можно обозначить "содержит строки продажи", т. е. класс sale "содержит" объекты класса sale_line.


    Имена у связей определять необязательно. На создаваемый по диаграммам код влияния они не оказывают. Обычно это делают, если причина создания связи не очевидна. Кроме того, проектировщику легче различать именованные связи, чем угадывать смысл отношений между классами и их экземплярами. Имя показывают около линии соответствующей связи.
    Для описания того, зачем нужны связи в ассоциации, применяют ролевые имена. Возвращаясь к примеру с торговой точкой, можно сказать, что объекты класса sale_line по отношению к объекту sale играют роль строк – Lines (Строки). Ролевые имена – это обычно имена существительные, их показывают на диаграмме рядом с классом, играющим соответствующую роль. Как правило, пользуются или ролевым именем, или именем связи, но не обоими сразу. Как и имена связей, ролевые имена не обязательны, их дают, только если цель связи не очевидна. На готовый код имена ролей не влияют, однако делают текст программы более удобным для восприятия, поскольку используются в качестве имен атрибутов соответствующих классов.


    Ассоциации могут быть рефлексивными. Рефлексивная ассоциация предполагает, что один экземпляр класса взаимодействует с другими экземплярами этого же класса. Примером может служить рассмотренный выше контейнерный класс group, экземпляры которого связаны друг с другом:


    На ранних этапах проектирования ассоциация обычно говорит лишь о наличии связи, реализация которой будет определена позднее. Так, мы можем вначале сказать только то, что детали могут собираться из других деталей:
    … а в дальнейшем уточнить это отношение:


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


    В случае, когда может возникнуть необходимость в подобном отношении. Нередко встречается ситуация, когда одинаковый товар заказчику поступает от разных поставщиков, соответственно, и цены на него могут отличаться. С одной стороны, по одному товару можно выйти на всех его поставщиков, с другой – от поставщика определить все поставляемые им товары. Однако по такой связи нельзя однозначно определить цену товара. Однозначно ее определяет пара ключей – код поставщика и код товара. Поскольку сведения о цене определяются их связью, запись о цене выражают в виде ассоциированного с ее линией класса.
    В нашем случае объявления классов SupplierRecord (сведения о поставщиках) и ProductRecord (сведения о продуктах) не будут содержать никаких ссылок друг на друга и, в то же время, оба будут включать атрибут, содержащий указатели на объекты PriceRecord, тем самым, выражая отношение "один-ко-многим".


    //сведения о поставщиках:
    class SupplierRecord
    {
    private:

    PriceRecord* the_PriceRecord;

    }
    В свою очередь класс PriceRecord представляет собой составной ключ, однозначно определяющий связь цены, выражаемой атрибутом price, с товаром и его поставщиком.
    //цена на конкретный продукт от конкретного поставщика:
    class PriceRecord
    {
    private:
    double price;
    ProductRecord *productID;
    SupplierRecord *supplierID;

    }


    Агрегация описывает отношения целого и части, приводящие к соответствующей иерархии объектов, причем, идя от целого (агрегата) мы можем придти к его частям (атрибутам). В этом смысле агрегация – специализированный частный случай ассоциации. Объект, являющийся атрибутом другого объекта (агрегата), имеет связь со своим агрегатом. Через эту связь агрегат может посылать ему сообщения (см. "Использование"). Агрегацию отображают в виде линии с ромбиком у класса, являющегося целым.
    Приведенный выше вариант агрегации (с незакрашенным ромбиком) называется также агрегацией по ссылке.


    В дополнение к простой агрегации UML вводит более сильную разновидность агрегации, называемую композицией или агрегацией по значению. Согласно композиции объект-часть может принадлежать только единственному целому, и, кроме того, зачастую жизненный цикл час­тей совпадает с циклом целого: они живут и умирают вместе с ним. Любое удаление целого распространяется на его части. Такой вид агрегации отображается с помощью закрашенного ромбика со стороны целого
    Отношение агрегации между классами имеет непосредственное отношение к агрегации между их экземплярами. Агрегация по ссылке означает включение в класс агрегата атрибута-указателя или атрибута-ссылки на экземпляр связанного с ним класса. В этом случае уничтожение целого (агрегата) не приводит к уничтожению его частей и эти части, таким образом, могут принадлежать различным объектам. Агрегация по значению предполагает включение в класс агрегата собственно объекта-части.


    Рассмотрим пример из области торговли. Товары принимаются согласно документам строгой отчетности:


    Эти документы необходимо создавать, обрабатывать и хранить. Важен не внешний вид, а логическое представление.
    Начнем с документа «счет». В исходном документе имеется информация о номере счета, дате выписки, поставщике и плательщике и т.д. Представим документ «счет» как класс invonce. Номер счета и дата являются его непосредствеными свойствами.
    Поставщик с плательщиком в этом плане не столь однозначны: если нет необходимости в отдельном учете предприятий-клиентов, то можно обойтись простыми строками для этих атрибутов (from и to), в противном случае лучше выделить понятие "предприятие" в отдельный класс (enterprise), с которым и связать класс, описывающий счет.


    Для предприятия, выписывающего счет, вряд ли будет представлять ценность поле поставщика, поэтому данный атрибут вообще можно исключить из логического представления документа, отнеся собственные реквизиты лишь к внешнему виду счета.
    Текcт счета формулируется из множества строк. Каждая строка содержит номер строки, артикул, наименование товара, единицу измерения, количество, цену и сумму. Номер строки не определяет никаких свойств строки, поскольку говорит только о порядке следования строки.
    После номера строки находится артикул – код товара, и далее его наименование. Если предполагается хранить где-то информацию о товаре и выбирать ее, а не заполнять каждый раз текстовое поле, то стоит вынести понятие товара в отдельный класс. К нему отнесем атрибуты "артикул", "наименование товара", "цена" и "единица измерения".
    Атрибут "количество" выражает мощность множества товаров данного наименования, а не описывает свойство единицы товара. Поэтому введем класс line, который и будет содержать указатель на объект, связанный с поставляемым товаром (item) и количество поставляемых товаров. Промежуточная сумма по строке представляет собой произведение цены товара на его количество, а итог складывается из промежуточных сумм.


    В результате мы получим следующую номенклатуру классов:
    Заметим, что каждому счету соответствует множество строк, причем заранее их количество неизвестно. По этой причине следует использовать специальный контейнерный класс вместо объявления статического массива из объектов.
    Мы остановимся на контейнере list, реализующем связный список.


    invoice


    свойства счета, счет содержит множество строк


    line


    строка в счете, указывает количество некоторых предметов


    item


    свойства предмета, на который выписывается документ


    enterprise


    реквизиты предприятия, на которое выписывается счет


    Отношения между экземплярами рассмотренных классов будет выражены следующей диаграммой:


    Код, полученный на её основе, будет выглядеть так:
    //Класс "Предприятие"
    class enterprise
    { private:
    //наименование предприятия
    string name;
    //ИНН предприятия
    long INN;
    };
    //Класс "Предмет"
    class item
    { public:
    //наименование
    string name;
    //метод, возвращающий цену предмета
    const double get_price() const;
    private:
    //артикул
    string code;
    //единица измерения
    string unit;
    //цена
    double price;
    };


    //Класс "Строка"
    class line
    {
    public:
    //количество
    double quantity;
    //Предмет, на который указывает строка
    item* Item;
    };
    //Класс "Счет"
    class invoice
    {
    public:
    //номер счета
    int number;
    //Строки счета
    list Lines;
    //Предприятие-плательщик
    enterprise* To;
    //подсчет стоимости счета
    double get_price();
    private:
    //дата
    long date;
    };


    Из приведенного кода видно, что ассоциативные связи реализованы посредством указателей на экземпляры классов (например, атрибут Item класса line хранит адрес объекта типа item). В классе invoice мы имеем агрегацию экземпляров класса "Строка" по значению посредством атрибута Lines, представляющего собой контейнер "список" для экземпляров класса line.
    Логично будет предположить, что те экземпляры классов, с которыми другие связаны ассоциацией, тоже должны где-то храниться. По этой причине будет разумно добавить еще одну абстракцию – класс base, описывающий базу данных, которая будет хранить набор ключевых объектов предметной области. Здесь мы специально делаем акцент на физическом включении таким объектов других:


    Отношение использования между классами соответствует равноправной связи между их экземплярами. Это то, во что превращается ассоциация, если оказывается, что одна из ее сторон (клиент) пользуется услугами другой (сервера). В UML эти отношения изображают в виде пунктирной линии со стрелкой.
    Когда дело доходит до реализации системы, необходимо обеспечить видимость связанных объектов. Есть четыре способа обеспечить видимость:
      сервер глобален по отношению к клиенту;
      сервер (или указатель на него) передан клиенту в качестве параметра операции;
      сервер является частью клиента;
      сервер локально порождается клиентом в ходе выполнения какой-либо операции.


    Приведенный пример показывает обращение клиента, объекта некоего диалогового класса (dialog), к серверу: объекту класса base, играющему роль базы данных, хранящей объекты класса dialog. Здесь использование объекта класса base подразумевает вызов его метода, в частности get_person(), для поиска с его помощью объекта класса person по значению его атрибута name.
    class dialog
    {
    public:
    dialog();
    void find_in(base& B);
    void find_in(base* BP);
    };
    dialog::dialog()
    {
    string x; person* pp;
    while (x != "0")
    {
    cout << "Search name: "; cin >> x;
    pp = DB.get_person(x); //DB – глобальный объект.
    if (pp) cout << "Address of object is " << pp << endl;
    else cout << "Object not found!" << endl;
    }
    }


    В данном случае создаваемый конструктором объект диалога обращается к объекту DB, который является глобальным в программе. Если же мы пожелаем производить поиск в какой-то другой базе данных, можно добавить такую возможность путем передачи классу диалога ссылки или указателя на один из таких объектов:
    void dialog::find_in(base &B) //передача сервера по ссылке
    {
    string x; cout << "Search name: "; cin >> x;
    cout << B.get_person(x);
    }
    void dialog::find_in(base *BP) //передача сервера по указателю
    {
    string x; cout << "Search name: "; cin >> x;
    cout << BP->get_person(x);
    }
    Любой из этих вариантов соответствует приведенной выше диаграмме, иллюстрируя только способы обеспечения видимости сервера для клиента. Связь использования на диаграмме лишь объявляет о наличии соответствующего отношения между объектами классов, оставляя реализацию на откуп разработчику.


    Наследование

    1   ...   5   6   7   8   9   10   11   12   ...   26


    написать администратору сайта