Я начинаю кодировать большие объекты, имеющие внутри себя другие объекты.
Иногда мне нужно уметь вызывать методы подобъекта извне класса объекта, его содержащего, из функции main (), например.
До сих пор я использовал геттеры и сеттеры как я узнал.
Это даст что-то вроде следующего кода:
class Object {
public:
bool Object::SetSubMode(int mode);
int Object::GetSubMode();
private:
SubObject subObject;
};
class SubObject {
public:
bool SubObject::SetMode(int mode);
int SubObject::GetMode();
private:
int m_mode(0);
};
bool Object::SetSubMode(int mode) { return subObject.SetMode(mode); }
int Object::GetSubMode() { return subObject.GetMode(); }
bool SubObject::SetMode(int mode) { m_mode = mode; return true; }
int SubObject::GetMode() { return m_mode; }
Это кажется очень неоптимальным, вынуждает меня писать (некрасивый) код для каждого метода, который должен быть доступен извне. Я хотел бы иметь возможность сделать что-то так просто, как Object->SubObject->Method(param);
Я подумал о простом решении: выставить подобъект как публичный в моем объекте.
Таким образом, я должен иметь возможность просто получить доступ к его методам извне.
Проблема в том, что когда я изучал объектно-ориентированное программирование, мне сказали, что публичное размещение чего-либо, кроме методов, было богохульством, и я не хочу начинать использовать плохие привычки кодирования.
Еще одно решение, с которым я столкнулся во время своего исследования до публикации здесь, заключается в возможно добавить публичный указатель на подобъект?
Как я могу получить доступ к методам подобъекта аккуратным способом?
Разрешено ли / является хорошей практикой помещать объект внутри класса как общедоступный для доступа к его методам? Как обойтись без этого в противном случае?
Большое спасибо за вашу помощь в этом.
Проблема как с указателем, так и с открытым объектом-членом заключается в том, что вы только что удалили сокрытие информации. Ваш код теперь более хрупкий, потому что все «знают», что вы реализовали объект Car с 4 объектами Wheel. Вместо вызова функции Car, которая скрывает детали, подобные этой:
Car->SetRPM(200); // hiding
Вы хотите сразу начать вращать Колеса, как это:
Car.wheel_1.SetRPM(200); // not hiding! and brittle!
Car.wheel_2.SetRPM(200);
А что, если вы измените внутреннюю часть класса? Вышеприведенное может теперь быть нарушено и должно быть изменено на:
Car.wheel[0].SetRPM(200); // not hiding!
Car.wheel[1].SetRPM(200);
Кроме того, для вашего автомобиля вы можете сказать SetRPM (), и класс выясняет, является ли это передним приводом, задним приводом или полным приводом. Если вы говорите с членами колеса напрямую, детали реализации больше не будут скрыты.
Иногда вам нужен прямой доступ к членам класса, но одна из целей при создании класса заключалась в том, чтобы инкапсулировать и скрыть детали реализации от вызывающей стороны.
Обратите внимание, что у вас могут быть операции Set и Get, которые обновляют более одного бита данных-членов в классе, но в идеале эти операции имеют смысл для самого автомобиля, а не для конкретных объектов-членов.
Мне сказали, что публиковать что-либо публично, кроме методов, было богохульством
Такие общие заявления опасны; У каждого стиля есть свои плюсы и минусы, которые вы должны принять во внимание, но прямой запрет на публичных участников — плохая идея IMO.
Основная проблема, связанная с наличием открытых участников, заключается в том, что он предоставляет детали реализации, которые могут быть лучше скрыты. Например, допустим, вы пишете какую-то библиотеку:
struct A {
struct B {
void foo() {...}
};
B b;
};
A a;
a.b.foo();
Через несколько лет вы решаете, что хотите изменить поведение A
в зависимости от контекста; может быть, вы хотите, чтобы он работал по-другому в тестовой среде, может быть, вы хотите загрузить из другого источника данных и т. д. Черт, может быть, вы просто решаете, имя члена b
не достаточно описательный. Но потому что b
является публичным, вы не можете изменить поведение A
не нарушая клиентский код.
struct A {
struct B {
void foo() {...}
};
struct C {
void foo() {...}
};
B b;
C c;
};
A a;
a.c.foo(); // Uh oh, everywhere that uses b needs to change!
Теперь, если бы вы позволили A
заверните реализацию:
class A {
public:
foo() {
if (TESTING) {
b.foo();
} else {
c.foo();
}
private:
struct B {
void foo() {...}
};
struct C {
void foo() {...}
};
B b;
C c;
};
A a;
a.foo(); // I don't care how foo is implemented, it just works
(Это не идеальный пример, но вы поняли.)
Конечно, недостатком здесь является то, что для этого требуется много лишних шаблонов, как вы уже заметили. Таким образом, в основном, вопрос заключается в том, «ожидаете ли вы, что детали реализации изменятся в будущем, и если так, будет ли дороже добавлять шаблон сейчас или реорганизовывать каждый вызов позже?» А если вы пишете библиотеку, используемую внешними пользователями, то «рефакторинг каждого звонка» превращается в «сломать весь клиентский код и заставить их рефакторинг «, что очень расстроит многих людей.
Конечно, вместо написания функций пересылки для каждой функции в SubObject
Вы можете просто добавить геттер для subObject
:
const SubObject& getSubObject() { return subObject; }
// ...
object.getSubObject().setMode(0);
Который страдает от некоторых из тех же проблем, что и выше, хотя это немного легче обойти, потому что SubObject
интерфейс не обязательно привязан к реализации.
Несмотря на все сказанное, я думаю, что, безусловно, времена, когда публичные участники являются правильным выбором. Например, простые структуры, основная цель которых состоит в том, чтобы выступать в качестве входных данных для другой функции, или которые просто получают пакет данных из точки А в точку Б. Иногда весь этот шаблон действительно излишним.