#c
#c
Вопрос:
Я разрабатываю класс с некоторой функциональностью, которую, я думаю, возможно, потребуется расширить позже, но не сейчас. Если бы класс должен был быть расширен, то, я думаю, это сделало бы создание экземпляра базового класса бессмысленным.
Например, предположим, что мой базовый класс — это дерево. Одним из подходов было бы просто поместить все, что дерево должно делать для моих целей, в класс tree и оставить все как есть. Однако это дерево может быть полезно в других аспектах программы в дальнейшем, поэтому я подумал о создании чисто виртуальной onNodeVisited
функции. Затем производные классы могли бы реализовать свою собственную версию onNodeVisited
и не беспокоиться о специфике обхода дерева, определенной в базовом классе.
Имеет ли смысл не использовать чисто виртуальную функцию и сохранить функциональность дерева и функциональность конкретного приложения в одном классе (virtual onNodeVisited
)? Или я должен сделать класс дерева абстрактным и реализовать один подкласс для части, специфичной для приложения.
Ответ №1:
Я бы не стал решать это сейчас. Позже вы могли бы создать абстрактный базовый класс для Tree и перенести туда код, если бы это имело смысл.
Другой вариант наследования для такого рода вещей — это тип указателя на функцию или функтора для вызова. Его намного проще использовать повторно, потому что вам не нужно постоянно создавать новые классы для каждой новой ситуации.
Ответ №2:
Очевидно, что это должно решаться в каждом конкретном случае, и для примера с деревом может показаться, что решающее решение / вопрос заключается в том, всем ли пользователям дерева потребуется / захочется реализовать функцию onNodeVisited. Если дерево в равной степени можно использовать с другими частями интерфейса (например, оно поддерживает итерацию в get_next_child()
стиле или некоторый поиск по «пути»), то, похоже, дерево может быть полезным для людей, которые никогда не собираются посещать каждый узел и, следовательно, не хотели бы реализовывать onNodeVisited
функцию. В таком случае onNodeVisited
не должен быть чисто виртуальным, сейчас или когда-либо. Если ваше дизайнерское решение заключается в том, чтобы интерфейс дерева был настолько ограниченным, что класс бесполезен без посещения, тогда вы могли бы настаивать на том, чтобы люди реализовали onNodeVisited
, сделав его чисто виртуальным.
Ответ №3:
Я вижу два варианта, которые кажутся мне достаточно разумными.
Если вы просто думаете о вещах, которые могли бы в конечном итоге стать полезными, тогда я бы пошел по пути YAGNI и полностью оставил его до тех пор, пока вы не обнаружите в нем реальную потребность.
Если вы совершенно уверены, что вам это действительно понадобится, и просто пока у вас недостаточно другого написанного кода для его использования, тогда стоит разработать класс, даже если вы его еще не используете.
Хотя это не похоже на хорошую ситуацию для чисто виртуальной функции. Чисто виртуальная функция означает, что 1) класс, содержащий чисто виртуальную функцию, не может быть создан напрямую — его можно использовать только как базовый класс, и 2) чтобы иметь возможность создавать объекты производного класса, они должны предоставлять реализацию чисто виртуальной функции .
Другими словами, чисто виртуальная функция указывала бы почти на противоположность вашей ситуации. Чистая виртуальная функция должна быть переопределена, прежде чем объекты класса вообще смогут существовать. Вы (в лучшем случае) создаете «хук», чтобы упростить конкретное будущее расширение.
Я повторю свой совет выше: если вы не вполне уверены, что это будет использовано, вероятно, лучше всего просто спроектировать то, что вам действительно нужно, и оставить все как есть. Если вы решите, что вам нужно (или действительно, действительно хотите) включить это, я бы постарался, чтобы это было как можно более слабо связано, насколько это разумно. Очевидным путем к этому было бы использовать шаблон visitor. Это определяет отдельный класс visitor, который обрабатывает узлы посещения и обработки. Класс tree node будет включать дополнительную функцию (с традиционным именем accept
), которая принимает один параметр: указатель (или ссылку) на объект visitor. Класс visitor — это типично абстрактный базовый класс с функцией-членом (традиционно именуемой visit
) для выполнения обработки на узле. Обычно это является абстрактным базовым классом.
Затем, когда вы решаете, какую обработку вы действительно хотите выполнить на каждом узле, вы производите новый класс из, visitor
который переопределяет visit
обработку для каждого узла. Дерево вообще не нуждается в модификации. Это хорошо известный шаблон (даже среди программистов на C , которые обычно гораздо менее «дружелюбны» к шаблонам, чем другие), поэтому, если вы используете обычные имена, большинство распознает его довольно быстро и легко. Также помогает то, что сам код обычно довольно тривиален — accept
обычно выглядит примерно так:
struct tree_node;
class visitor {
virtual void visit(tree_node *node) = 0;
};
struct tree_node {
void accept(visitor *v) { v->visit(this); }
// ...
};
Затем, чтобы добавить обработку, вы производите от visitor и переопределяете visit
, чтобы выполнить необходимую вам обработку. Древовидная часть вещей вообще не нуждается в модификации. Это также имеет то преимущество, что один класс visitor может быть написан для посещения более чем одного типа узлов (даже если узлы не связаны по наследованию).