По сути, моя проблема в том, что функция в библиотеке, которую я использую (функция 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);
}
};
Вы можете сделать указатель константным и затем привести его при передаче в библиотечную функцию.
Foo(const_cast<Object *>(bar.mbar));
Это будет работать, если Foo не пытается изменить mbar. Актер удаляет постоянство «только по имени». Попытка изменить тайно-постоянное значение может привести к ужасным вещам.
Даже если бы был способ заставить Bar возвращать указатель «только для чтения», пример кода в вашем вопросе все равно нарушил бы инкапсуляцию. Этот конкретный вкус не инкапсуляции называется особенность зависти: данные живут в одном объекте, но другой объект выполняет большинство операций с данными. Более объектно-ориентированным подходом было бы перенести манипуляции и данные в один и тот же объект.
Очевидно, что пример кода, который вы нам дали, гораздо менее сложен, чем ваш реальный проект, поэтому я не могу найти наиболее разумный способ реструктуризации вашего кода. Вот несколько предложений:
Переместите функцию Foobar в Bar:
class Bar
{
private:
Object* mbar;
public:
void FoobarFunction()
{
Foo(mbar);
}
};
использование внедрение зависимости. Инициализируйте 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 ++, не имеют конструкции «друг». Исходя из моего собственного опыта, внедрение зависимостей — лучший способ решения многих проблем, для решения которых были предназначены друзья.
Если участник является частным, это, вероятно, частное по причине …
Если Bar должен быть единственным владельцем Obj, то он не должен раскрывать его, так как любое другое изменение в Obj может привести к неправильной работе Bar.
Хотя, если Bar не должен быть единственным владельцем Obj, вы можете либо вставить инъекцию зависимости использования геттера и передать ее в Bar извне, таким образом вы позже сможете передать ее foo
также.
Решение, которое, я думаю, вам следует избегать, заключается в вызове foo внутри Bar. Это может нарушить Принцип единой ответственности
Я верю, что в этом случае жестко, вы можете использовать метод друга.
Я отошлю вас к Часто задаваемые вопросы утверждая, что друг не всегда плох для инкапсуляции.
Нет! Если они используются правильно, они улучшают инкапсуляцию.
Вам часто нужно разделить класс пополам, когда у двух половинок будет разное количество экземпляров или разное время жизни. В этих случаях две половины обычно нуждаются в прямом доступе друг к другу (две половины раньше были в одном классе, поэтому вы не увеличили объем кода, которому нужен прямой доступ к структуре данных; вы просто перетасовали код на два класса вместо одного). Самый безопасный способ реализовать это — подружить две половинки друг с другом.
Если вы используете друзей, как описано выше, вы будете держать личные вещи в секрете. Люди, которые не понимают этого, часто делают наивные попытки избежать использования дружбы в ситуациях, подобных описанным выше, и часто они фактически разрушают инкапсуляцию. Они либо используют общедоступные данные (гротеск!), Либо делают данные доступными между половинами с помощью функций-членов public get () и set (). Наличие общедоступных функций-членов get () и set () для закрытых данных нормально только тогда, когда закрытые данные «имеют смысл» вне класса (с точки зрения пользователя). Во многих случаях эти функции-члены get () / set () почти так же плохи, как и общедоступные данные: они скрывают (только) имя частного элемента данных, но не скрывают существование частного элемента данных.
Точно так же, если вы используете дружественные функции в качестве синтаксического варианта функций открытого доступа класса, они не нарушают инкапсуляцию больше, чем функция-член нарушает инкапсуляцию. Другими словами, друзья класса не нарушают барьер инкапсуляции: наряду с функциями-членами класса они являются барьером инкапсуляции.
(Многие люди думают о функции друга как о чем-то вне класса. Вместо этого попробуйте представить функцию друга как часть открытого интерфейса класса. Функция друга в объявлении класса не нарушает инкапсуляцию больше, чем открытая функция-член инкапсуляция: оба имеют одинаковые права доступа к закрытым частям класса.)