Как правильно удалить / реорганизовать объявление "друга" зависимости?

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

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

Оригинальный дизайн с использованием друга

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

Короче, зачем мне эта штука там:

  1. ClassAProvider делится ссылкой на ClassA в течение нескольких одновременных обращений Client экземпляры
  2. Client экземпляры должны получить доступ ClassA исключительно через ClassAAccessor вспомогательный класс, который управляет внутренними
  3. ClassA скрывает все методы, предназначенные для ClassAAccessor как защищено.
  4. Так ClassA может гарантировать, что Client необходимо использовать ClassAAccessor пример

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

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

Желаемое поведение общей конструкции

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

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

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

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

Как говорится в заголовке моего вопроса

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

2 ответа

Решение

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

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

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

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

1-й выстрел рефакторинга

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


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

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

Созревший дизайн

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

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

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

Склеить все вместе


Вот реализация C++:

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's inherited 
                                             // in the protected scope.
}

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

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

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


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

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

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

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

Другие вопросы по тегам