I'm working on a project with others and we have a discussion about information hiding and static type safety. Our scenario is described below.
Scenario: We want to create a tree-like structure. Each tree nodes has its own class, which all are sub-types of base class
NodeType. Each sub-type has its own rule to link other nodes. For example,
NodeTypeC are sub-classes of
NodeTypeAcan only has
NodeTypeBas its first child,
NodeTypeAas its second child.
NodeTypeBcan only has
NodeTypeBas its childron. (It may have any number of childron)
NodeTypeCcan only has
NodeTypeAas its first child,
NodeTypeBas its second child.
The example may have recursion problem, but for illustration is fine.
Now there is a
Factory class to create each node:
There is a class,
Builder, that wants to convert user text input to the tree.
Builder need not know information about each node's type. It just carries pointers got from
Factory, and passes them to another
Factory's method. In
Builder's perspective, all node's type can be the base class
If we value Information Hiding more, the Factory's method should be:
NodeType *createNodeTypeA(NodeType *first, NodeType *second)
NodeType *createNodeTypeB(std::vector<NodeType *> children)
NodeType *createNodeTypeC(NodeType *first, NodeType *second)
and to provide correctness, we have to provide run-time check on each parameter in Factory's method.
If we value Static Type Safety more, the Factory's method should be:
NodeTypeA *createNodeTypeA(NodeTypeB *first, NodeTypeA *second)
NodeTypeB *createNodeTypeB(std::vector<NodeTypeB *> childron)
NodeTypeC *createNodeTypeC(NodeTypeA *first, NodeTypeB *second)
Builder must know sub-types of each node. What we gain is type safety.
The figure below illustrates the two ways:
Favor Information Hiding:
- Information hiding itself is important. Developers of
Buildercan work without knowledge of sub-types of nodes.
- We can compensate the lose of static type safety by code review, testing and automatic analyzing tool. A qualified team is expected to complete those pretty well.
Favor Static Type Safety:
- Increase confidence of the program's correctness.
- When calling
Factory's method, the type information (which is indicated by function name) has exposed to
Builder. The one more thing
Builderis expected to do is just to preserve it.
- If we erased type information, then we would violate several good code practices suggested by C++ Core Guidelines, which would cause code to be error-prone:
- Avoid corresponding testing time and run-time check.
Which one may be a good practice? We value long-term impact more.
iteratortypes have an interface, but don't share any ancestor type). Your
Bs form trees, but
A's are just lists (with
Btrees hanging off each element) and
Cis only available at the root