Подтвердить что ты не робот

Как я могу удалить/реорганизовать декларацию зависимостей "друга"?

Предыстория этого вопроса основана на практическом примере, где я хотел удалить зависимость "друга" из пары классов, которые используются для управления доступом для чтения/записи с заблокированным доступом к общему ресурсу.

Здесь абстракция исходного структурного проекта для этого сценария:

Original Design using friend

Отмечено красным цветом, вот эта уродливая зависимость "друга", которую я хочу удалить из дизайна.

Короче говоря, почему у меня есть эта вещь:

  • ClassAProvider делится ссылкой на ClassA на несколько одновременный доступ к экземплярам Client
  • Client экземпляры должны получить доступ к ClassA только через ClassAAccessor вспомогательный класс который управляет внутренними элементами.
  • ClassA скрывает все методы, предназначенные для использования с ClassAAccessor как защищенные.
  • Итак ClassA может гарантировать, что Client должен использовать экземпляр ClassAAccessor

Этот шаблон в первую очередь полезен, когда он обеспечивает сохранение экземпляров ClassA в если выполняется операция Client (из-за неперехваченного исключения). Думать о ClassA обеспечение (внутренне видимых) парных операций, таких как lock()/unlock() или open()/close().

В любом случае следует вызывать операции (state-) reverseing, особенно когда клиент вылетает из строя к исключению.
Это можно безопасно обрабатывать с помощью поведения ClassAAcessor жизненного цикла, деструктора реализация может обеспечить его. Следующая диаграмма последовательности иллюстрирует то, что предполагаемое поведение:

The desired behavior of the overall construct

Кроме того, экземпляры Client могут легко получить прекрасный контроль доступа к ClassA, просто используя Блоки областей С++:

// ...
{ 
    ClassAAccessor acc(provider.getClassA());
    acc.lock();
    // do something exception prone ...
} // safely unlock() ClassA
// ...

Все до сих пор хорошо, но зависимость "friend" между ClassA и ClassAAccessor должна быть удалена по ряду веских причин.

  • В надстройке UML 2.2 в разделе C.2 в разделе "Изменения из предыдущего UML" говорится: The following table lists predefined standard elements for UML 1.x that are now obsolete. ... «friend» ...
  • Большинство правил и рекомендаций по кодированию, которые я видел, запрещают или сильно препятствуют использованию друга, чтобы избежать жесткой зависимости от классов экспорта для друзей. Эта вещь вызывает некоторые серьезные проблемы с обслуживанием.

Как говорится в названии вопроса

Как правильно удалить/реорганизовать объявление друга (желательно, начиная с дизайна UML для моих классов)?

4b9b3361

Ответ 1

Сначала установите некоторые ограничения для рефакторинга:

  • Открытый доступ к общедоступному интерфейсу ClassAAccessor не должен
  • Внутренние операции ClassA не должны быть видимыми/доступными из общедоступных
  • Общая производительность и площадь оригинального дизайна не должны быть повреждены.

Шаг 1: Представьте абстрактный интерфейс

Для первого снимка я угадал стереотип "друга" и заменил его классом (интерфейсом) InternalInterface и соответствующие отношения.

1st shot refactoring

Что составляло зависимость "friend", было разделено на простое отношение зависимостей (синее) и зависимость "вызова" (зеленая) от нового элемента InternalInterface.


Шаг 2: Переместите операции, которые составляют зависимость "вызова" к интерфейсу

Следующий шаг - созреть зависимость "звонок". Для этого я меняю диаграмму следующим образом:

Matured design

  • Зависимость "call" превратилась в направленную ассоциацию из ClassAAccessor к InternalInterface (I.e. ClassAAccessor содержит частная переменная internalInterfaceRef).
  • Операции, о которых идет речь, были перенесены с ClassA на InternalInterface.
  • InternalInterface расширен с помощью защищенного конструктора, что он полезен в наследовании только.
  • ClassA Связь "обобщения" с InternalInterface отмечена как protected, поэтому он стал невидимым.

Шаг 3: склейте все вместе в реализации

На последнем этапе нам нужно смоделировать способ, которым ClassAAccessor может получить ссылку на InternalInterface. Поскольку обобщение не видно публично, ClassAAcessor не может инициализировать его из ссылки ClassA, переданной в конструкторе. Но ClassA может получить доступ к InternalInterface и передать ссылку с помощью дополнительного метода setInternalInterfaceRef(), представленного в ClassAAcessor:

Glue everything together


Здесь реализация С++:

class ClassAAccessor {
public:
    ClassAAccessor(ClassA& classA);
    void setInternalInterfaceRef(InternalInterface & newValue) {
        internalInterfaceRef = &newValue;
    }
private:  
    InternalInterface* internalInterfaceRef;
};

Этот вызов на самом деле называется, когда вновь введенный метод ClassA::attachAccessor() метод называется:

class ClassA : protected InternalInterface {
public:
    // ...
    attachAccessor(ClassAAccessor & accessor);
    // ...
};

ClassA::attachAccessor(ClassAAccessor & accessor) {
    accessor.setInternalInterfaceRef(*this); // The internal interface can be handed
                                             // out here only, since it inherited 
                                             // in the protected scope.
}

Таким образом, конструктор ClassAAccessor можно переписать следующим образом:

ClassAAccessor::ClassAAccessor(ClassA& classA)
: internalInterfaceRef(0) {
    classA.attachAccessor(*this);
}

Наконец, вы можете еще больше отделить реализации, введя еще один InternalClientInterface следующим образом:

enter image description here


По крайней мере, необходимо упомянуть, что этот подход имеет некоторые недостатки против использования объявлений friend:

  • Это усложняет код больше
  • friend не нужно вводить абстрактные интерфейсы (которые могут повлиять на след, поэтому ограничение 3. не полностью выполнено)
  • Сопровождение обобщения protected недостаточно хорошо поддерживается представлением UML (мне пришлось использовать это ограничение)

Ответ 2

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

Определите ClassAProvider и Client в том же пакете и установите видимость атрибута classA для типа видимости пакета. Экземпляр клиента может читать значение атрибута classA, но экземпляры других типов, не определенные в одном пакете, не могут.