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.
Language: C++11
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, NodeTypeA
, NodeTypeB
and NodeTypeC
are sub-classes of NodeType
, and
NodeTypeA
can only hasNodeTypeB
as its first child,NodeTypeA
as its second child.NodeTypeB
can only hasNodeTypeB
as its childron. (It may have any number of childron)NodeTypeC
can only hasNodeTypeA
as its first child,NodeTypeB
as its second child.
The example may have recursion problem, but for illustration is fine.
Now there is a Factory
class to create each node:
createNodeTypeA
createNodeTypeB
createNodeTypeC
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 NodeType
.
Dilemma:
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)
and Builder
must know sub-types of each node. What we gain is type safety.
The figure below illustrates the two ways:
Discussion:
Favor Information Hiding:
- Information hiding itself is important. Developers of
Builder
can 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 toBuilder
. The one more thingBuilder
is 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.
Question:
Which one may be a good practice? We value long-term impact more.
iterator
types have an interface, but don't share any ancestor type). YourB
s form trees, butA
's are just lists (withB
trees hanging off each element) andC
is only available at the root