Как я могу использовать закрытую переменную-член в функции, не являющейся членом, когда переменная оказывается указателем?

По сути, моя проблема в том, что функция в библиотеке, которую я использую (функция Foo в этом коде), требует указателя на объект (Object * mbar) в качестве параметра. Однако mbar — это закрытая переменная-член для bar.

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

Следующее, что я подумал, было то, что я мог бы использовать константные указатели, потому что они не позволяют изменять ресурс, на который они указывают, но, насколько я мог судить, мне нужно было бы изменить Foo, чтобы принять его, что невозможно, так как это библиотечная функция.

Последнее, о чем я могу подумать, — это просто использовать друга Bar для вызова FoobarFunction, но мне всегда говорили, что функции друзей — это последнее средство.

Есть ли способ сделать это, не нарушая инкапсуляцию каким-либо образом?

//Main.cpp

#include "Foobar.h"
int main()
{
Foobar fb;
Bar b;
fb.FoobarFunction(b);
return 0;
}

//Bar.h

#include "Object.h"
class Bar
{
private:
Object* mbar;
};

//Foobar.h

#include "Foo.h"#include "Bar.h"
class Foobar
{
public:
void FoobarFunction(Bar bar)
{
Foo(bar.mbar);
}
};

5

Решение

Легкий выход

Вы можете сделать указатель константным и затем привести его при передаче в библиотечную функцию.

Foo(const_cast<Object *>(bar.mbar));

Это будет работать, если Foo не пытается изменить mbar. Актер удаляет постоянство «только по имени». Попытка изменить тайно-постоянное значение может привести к ужасным вещам.

Но действительно…

Даже если бы был способ заставить Bar возвращать указатель «только для чтения», пример кода в вашем вопросе все равно нарушил бы инкапсуляцию. Этот конкретный вкус не инкапсуляции называется особенность зависти: данные живут в одном объекте, но другой объект выполняет большинство операций с данными. Более объектно-ориентированным подходом было бы перенести манипуляции и данные в один и тот же объект.

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

  1. Переместите функцию Foobar в Bar:

    class Bar
    {
    private:
    Object* mbar;
    public:
    void FoobarFunction()
    {
    Foo(mbar);
    }
    };
    
  2. использование внедрение зависимости. Инициализируйте mbar перед созданием Bar, затем передайте mbar в конструктор Bar.

    int main()
    {
    Object *mbar;
    Foobar fb;
    Bar b(mbar);
    fb.FoobarFunction(mbar);
    return 0;
    }
    

    В этом примере Bar больше не является «владельцем» mbar. Основной метод создает mbar напрямую, а затем передает его тому, кто в нем нуждается.

    На первый взгляд, этот пример нарушает указанную мной ранее директиву (данные и поведение хранятся в разных объектах). Тем не менее, есть большая разница между вышеизложенным и созданием геттера на Bar. Если в Bar есть метод getMBar (), то любой человек в мире может прийти и захватить mbar и использовать его для любых злых целей, которые он пожелает. Но в приведенном выше примере владелец mbar (main) полностью контролирует, когда передавать свои данные другому объекту / функции.

Большинство объектно-ориентированных языков, кроме C ++, не имеют конструкции «друг». Исходя из моего собственного опыта, внедрение зависимостей — лучший способ решения многих проблем, для решения которых были предназначены друзья.

3

Другие решения

Если участник является частным, это, вероятно, частное по причине …

Если Bar должен быть единственным владельцем Obj, то он не должен раскрывать его, так как любое другое изменение в Obj может привести к неправильной работе Bar.
Хотя, если Bar не должен быть единственным владельцем Obj, вы можете либо вставить инъекцию зависимости использования геттера и передать ее в Bar извне, таким образом вы позже сможете передать ее foo также.

Решение, которое, я думаю, вам следует избегать, заключается в вызове foo внутри Bar. Это может нарушить Принцип единой ответственности

Я верю, что в этом случае жестко, вы можете использовать метод друга.
Я отошлю вас к Часто задаваемые вопросы утверждая, что друг не всегда плох для инкапсуляции.

Нет! Если они используются правильно, они улучшают инкапсуляцию.

Вам часто нужно разделить класс пополам, когда у двух половинок будет разное количество экземпляров или разное время жизни. В этих случаях две половины обычно нуждаются в прямом доступе друг к другу (две половины раньше были в одном классе, поэтому вы не увеличили объем кода, которому нужен прямой доступ к структуре данных; вы просто перетасовали код на два класса вместо одного). Самый безопасный способ реализовать это — подружить две половинки друг с другом.

Если вы используете друзей, как описано выше, вы будете держать личные вещи в секрете. Люди, которые не понимают этого, часто делают наивные попытки избежать использования дружбы в ситуациях, подобных описанным выше, и часто они фактически разрушают инкапсуляцию. Они либо используют общедоступные данные (гротеск!), Либо делают данные доступными между половинами с помощью функций-членов public get () и set (). Наличие общедоступных функций-членов get () и set () для закрытых данных нормально только тогда, когда закрытые данные «имеют смысл» вне класса (с точки зрения пользователя). Во многих случаях эти функции-члены get () / set () почти так же плохи, как и общедоступные данные: они скрывают (только) имя частного элемента данных, но не скрывают существование частного элемента данных.

Точно так же, если вы используете дружественные функции в качестве синтаксического варианта функций открытого доступа класса, они не нарушают инкапсуляцию больше, чем функция-член нарушает инкапсуляцию. Другими словами, друзья класса не нарушают барьер инкапсуляции: наряду с функциями-членами класса они являются барьером инкапсуляции.

(Многие люди думают о функции друга как о чем-то вне класса. Вместо этого попробуйте представить функцию друга как часть открытого интерфейса класса. Функция друга в объявлении класса не нарушает инкапсуляцию больше, чем открытая функция-член инкапсуляция: оба имеют одинаковые права доступа к закрытым частям класса.)

2

По вопросам рекламы [email protected]