12.2.8 Программируемые отношения.

   


Назад  |  Вперёд

Конкретный язык программирования не может прямо поддерживать любое понятие любого метода проектирования. Если язык программирования не способен прямо представить понятие проектирования, следует установить удобное отображение конструкций, используемых в проекте, на языковые конструкции. Например, метод проектирования может использовать понятие делегирования, означающее, что всякая операция, которая не определена для класса A, должна выполняться в нем с помощью указателя p на соответствующий член класса B, в котором она определена. На С++ нельзя выразить это прямо. Однако, реализация этого понятия настолько в духе С++, что легко представить программу реализации:

           class A {
               B* p;
               //...
               void f();
               void ff();
           };

           class B {
               //...
               void f();
               void g();
               void h();
           };

Тот факт, что В делегирует A с помощью указателя A::p, выражается в следующей записи:

           class A {
               B* p;      // делегирование с помощью p
               //...
               void f();
               void ff();
               void g() { p->g(); }  // делегирование q()
               void h() { p->h(); }  // делегирование h()
           };

Для программиста совершенно очевидно, что здесь происходит, однако здесь явно нарушается принцип взаимнооднозначного соответствия. Такие "программируемые" отношения трудно выразить на языках программирования, и поэтому к ним трудно применять различные вспомогательные средства. Например, такое средство может не отличить "делегирование" от B к A с помощью A::p от любого другого использования B*.

Все-таки следует всюду, где это возможно, добиваться взаимнооднозначного соответствия между понятиями проекта и понятиями языка программирования. Оно дает определенную простоту и гарантирует, что проект адекватно отображается в программе, что упрощает работу программиста и вспомогательных средств. Операции преобразований типа являются механизмом, с помощью которого можно представить в языке класс программируемых отношений, а именно: операция преобразования X::operator Y() гарантирует, что всюду, где допустимо использование Y, можно применять и X. Такое же отношение задает конструктор Y::Y(X). Отметим, что операция преобразования типа (как и конструктор) скорее создает новый объект, чем изменяет тип существующего объекта. Задать операцию преобразования к функции Y - означает просто потребовать неявного применения функции, возвращающей Y. Поскольку неявные применения операций преобразования типа и операций, определяемых конструкторами, могут привести к неприятностям, полезно проанализировать их в отдельности еще в проекте.

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

           class Big_int {
               //...
               friend Big_int operator+(Big_int,Big_int);
               //...
               operator Rational();
               //...
           };

           class Rational {
               //...
               friend Rational operator+(Rational,Rational);
               //...
               operator Big_int();
           };

Типы Rational и Big_int не так гладко взаимодействуют, как можно было бы подумать:

           void f(Rational r, Big_int i)
           {
               //...
               g(r+i);   // ошибка, неоднозначность:
                         //        operator+(r,Rational(i)) или
                         //        operator+(Big_int(r),i)
               g(r,Rational(i));  // явное разрешение неопределенности
               g(Big_int(r),i);   // еще одно
           }

Можно было бы избежать таких "взаимных" преобразований, сделав некоторые из них явными. Например, преобразование Big_int к типу Rational можно было бы задать явно с помощью функции make_Rational() вместо операции преобразования, тогда сложение в приведенном примере разрешалось бы как g(BIg_int(r),i). Если нельзя избежать "взаимных" операций преобразования типов, то нужно преодолевать возникающие столкновения или с помощью явных преобразований (как было показано), или с помощью определения нескольких различных версий бинарной операции (в нашем случае +).


Назад  |  Вперёд


OtDiatlovaOU
К началу книги

Оформление и дизайн книги OtDiatlovaOU.
Вся книга, архив.