#c #oop #inheritance #methods #class-design
#c #ооп #наследование #методы #дизайн класса
Вопрос:
У меня есть простой класс контейнера низкого уровня, который используется более высокоуровневым классом файлов. По сути, класс file использует контейнер для локального хранения изменений перед сохранением окончательной версии в фактический файл. Следовательно, некоторые методы переносятся непосредственно из класса container в класс file. (Например, Resize()
.)
Я только что определял методы в классе file для вызова их вариантов контейнерного класса. Например:
void FileClass::Foo()
{
ContainerMember.Foo();
}
Это, однако, становится помехой. Есть ли лучший способ сделать это?
Вот упрощенный пример:
class MyContainer
{
// ...
public:
void Foo()
{
// This function directly handles the object's
// member variables.
}
}
class MyClass
{
MyContainer Member;
public:
void Foo()
{
Member.Foo();
// This seems to be pointless re-implementation, and it's
// inconvenient to keep MyContainer's methods and MyClass's
// wrappers for those methods synchronized.
}
}
Комментарии:
1. Я вижу здесь композицию, а не наследование. Я что-то упускаю?
2. @Doug Я спрашиваю, есть ли способ наследовать от класса container, или
MyContainer
в моем примере, всего для нескольких методов. (Или лучшее решение, если оно есть.) Я отредактирую вопрос, чтобы он был более понятным.3. В таком случае, похоже, вам следует разделить MyContainer на класс, в котором есть нужные вам методы (и затем наследовать от них), и остальные методы MyContainer в другом классе.
Ответ №1:
Ну, почему бы просто не наследовать приватно от MyContainer
и не предоставить те функции, которые вы хотите просто переслать с using
объявлением? Это называется «Реализация MyClass
в терминах MyContainer
«.
class MyContainer
{
public:
void Foo()
{
// This function directly handles the object's
// member variables.
}
void Bar(){
// ...
}
}
class MyClass : private MyContainer
{
public:
using MyContainer::Foo;
// would hide MyContainer::Bar
void Bar(){
// ...
MyContainer::Bar();
// ...
}
}
Теперь «извне» можно будет вызывать напрямую Foo
, в то время как Bar
это доступно только внутри MyClass
. Если вы сейчас создадите функцию с тем же именем, она скроет базовую функцию, и вы сможете обернуть базовые функции подобным образом. Конечно, теперь вам нужно полностью квалифицировать вызов базовой функции, иначе вы перейдете к бесконечной рекурсии.
Кроме того, если вы хотите разрешить (неполиморфное) подклассирование MyClass
, чем это одно из редких мест, действительно полезно защищенное наследование:
class MyClass : protected MyContainer{
// all stays the same, subclasses are also allowed to call the MyContainer functions
};
Неполиморфный, если у вашего MyClass
нет виртуального деструктора.
Ответ №2:
Да, поддержание такого прокси-класса, как этот, очень раздражает. В вашей IDE могут быть некоторые инструменты, которые немного упрощают работу. Или вы могли бы загрузить надстройку IDE.
Но обычно это не очень сложно, если только вам не нужно поддерживать десятки функций, переопределений и шаблонов.
Обычно я пишу их как:
void Foo() { return Member.Foo(); }
int Bar(int x) { return Member.Bar(x); }
Это красиво и симметрично. C позволяет возвращать значения void в функциях void, потому что это улучшает работу шаблонов. Но вы можете использовать то же самое, чтобы сделать другой код красивее.
Ответ №3:
Это наследование делегирования, и я не знаю, предлагает ли C какой-либо механизм, помогающий с этим.
Ответ №4:
Подумайте, что имеет смысл в вашем случае — композиция (имеет) или наследование (является) отношением между MyClass и MyContainer.
Если вы больше не хотите иметь подобный код, вы в значительной степени ограничены наследованием реализации (MyContainer как базовый / абстрактный базовый класс). Однако вы должны убедиться, что это действительно имеет смысл в вашем приложении, и вы не наследуете исключительно для реализации (наследование для реализации — это плохо).
Если вы сомневаетесь, то то, что у вас есть, вероятно, подойдет.
РЕДАКТИРОВАТЬ: Я больше привык думать на Java / C # и упустил из виду тот факт, что C обладает большей гибкостью наследования, которую Xeo использует в своем ответе. В данном случае это просто кажется хорошим решением.
Ответ №5:
Эта функция, которая вам нужна для написания больших объемов кода, на самом деле является необходимой функцией. C — это подробный язык, и если вы попытаетесь избежать написания кода на c , ваш дизайн никогда не будет очень хорошим.
Но реальная проблема с этим вопросом заключается в том, что класс не имеет поведения. Это просто оболочка, которая ничего не делает. Каждый класс должен делать что-то еще, кроме простой передачи данных.
Ключевым моментом является то, что каждый класс имеет правильный интерфейс. Это требование делает необходимым написание функций пересылки. Основная цель каждой функции-члена — распределить работу, необходимую для всех элементов данных. Если у вас есть только один элемент данных, и вы еще не решили, что должен делать класс, тогда все, что у вас есть, это функции пересылки. Как только вы добавите больше объектов-членов и решите, что должен делать класс, ваши функции пересылки изменятся на что-то более разумное.
Одна вещь, которая поможет в этом, — сохранить ваши классы небольшими. Если интерфейс небольшой, каждый прокси-класс будет иметь только небольшой интерфейс, и интерфейс не будет меняться очень часто.
Комментарии:
1. Класс действительно что-то делает. Мой пример был упрощен ради вопроса.