If I have a class called Node
, would it be bad, if objects of the Node
class knew their NodeType
, and the NodeType
would be used to cast to a specific interface like this:
// NodeType
enum class NodeType : uint8_t
{
None = 0,
Foo = 1 << 0,
Bar = 1 << 1,
FooBar = 1 << 2,
FooMask = Foo | FooBar,
BarMask = Bar | FooBar,
};
inline constexpr uint8_t operator&(const NodeType& t_lhs, const NodeType& t_rhs)
{
return static_cast<uint8_t>(t_lhs) & static_cast<uint8_t>(t_rhs);
}
// Base Node Class
class Node
{
public:
virtual NodeType GetNodeType() const = 0;
};
// Interfaces
class IFoo
{
public:
virtual ~IFoo() = default;
virtual void FooSpecificMethod() const = 0;
};
class IBar
{
public:
virtual ~IBar() = default;
virtual void BarSpecificMethod() const = 0;
};
// Derived Node Classes
class FooNode : public Node, public IFoo
{
public:
NodeType GetNodeType() const override { return NodeType::Foo; }
void FooSpecificMethod() const override { std::cout << "Foo.\n"; }
};
class BarNode : public Node, public IBar
{
public:
NodeType GetNodeType() const override { return NodeType::Bar; }
void BarSpecificMethod() const override { std::cout << "Bar.\n"; }
};
class FooBarNode : public Node, public IFoo, public IBar
{
public:
NodeType GetNodeType() const override { return NodeType::FooBar; }
void FooSpecificMethod() const override { std::cout << "Foo.\n"; }
void BarSpecificMethod() const override { std::cout << "Bar.\n"; }
};
// Use of NodeType and Interfaces
std::vector<std::unique_ptr<Node>> GetNodes()
{
std::vector<std::unique_ptr<Node>> nodes{};
nodes.push_back(std::make_unique<FooNode>());
nodes.push_back(std::make_unique<FooNode>());
nodes.push_back(std::make_unique<BarNode>());
nodes.push_back(std::make_unique<FooBarNode>());
nodes.push_back(std::make_unique<FooBarNode>());
nodes.push_back(std::make_unique<FooBarNode>());
return nodes;
}
int main()
{
std::vector<std::unique_ptr<Node>> nodes{ GetNodes() };
for (const auto& node : nodes)
{
if ((node->GetNodeType() & NodeType::FooMask) != 0)
dynamic_cast<const IFoo*>(node.get())->FooSpecificMethod();
}
for (const auto& node : nodes)
{
if ((node->GetNodeType() & NodeType::BarMask) != 0)
dynamic_cast<const IBar*>(node.get())->BarSpecificMethod();
}
}
My goal is to do type specific things on objects in a polymorphic collection like in the last code snippet. Is this a bad approach? Is there any more OO approach to this?
CodePudding user response:
Is this a bad approach? Is there any more OO approach to this?
Yes. You can just dynamic_cast
to the appropriate pointer type and check the result is not null.
int main()
{
std::vector<std::unique_ptr<Node>> nodes{ GetNodes() };
for (const auto& node : nodes)
{
if (auto foo = dynamic_cast<const IFoo*>(node.get()))
foo->FooSpecificMethod();
}
for (const auto& node : nodes)
{
if (auto bar = dynamic_cast<const IBar*>(node.get()))
bar->BarSpecificMethod();
}
}
If for some reason you want to avoid dynamic_cast
, you can add virtual functions to Node
. This is perhaps the "most OO" way.
class Node
{
public:
virtual ~Node() = default;
virtual const IFoo * asFoo() const { return nullptr; }
virtual const IBar * asBar() const { return nullptr; }
};
class FooNode : public Node, public IFoo
{
public:
const IFoo* asFoo() const override { return this; }
void FooSpecificMethod() const override { std::cout << "Foo.\n"; }
};
class BarNode : public Node, public IBar
{
public:
const IBar* asBar() const override { return this; }
void BarSpecificMethod() const override { std::cout << "Bar.\n"; }
};
class FooBarNode : public Node, public IFoo, public IBar
{
public:
const IFoo* asFoo() const override { return this; }
const IBar* asBar() const override { return this; }
void FooSpecificMethod() const override { std::cout << "Foo.\n"; }
void BarSpecificMethod() const override { std::cout << "Bar.\n"; }
};
int main()
{
std::vector<std::unique_ptr<Node>> nodes{ GetNodes() };
for (const auto& node : nodes)
{
if (auto foo = node->asFoo())
foo->FooSpecificMethod();
}
for (const auto& node : nodes)
{
if (auto bar = node->asBar())
bar->BarSpecificMethod();
}
}