I have two builder classes that return a Tree
and a Set
respectively. The execution of the second builder is just the execution of the first builder with an extra step and a different return type. I really don't want to duplicate the same steps in two different classes, because if there is a change in the Tree
-building process, I have to change code in two classes. Here are the two builders:
public class TreeBuilder {
private NodeHelper nodeHelper;
public TreeBuilder(NodeHelper nodeHelper) {
this.nodeHelper = nodeHelper;
}
public Tree<EquipmentDetails> buildTree() {
Tree<EquipmentDetails> tree = new Tree<>(nodeHelper.getEquipmentDetails(nodeHelper.getModelName()));
return buildTree(tree);
}
private Tree<EquipmentDetails> buildTree(Tree<EquipmentDetails> parentTree) {
Map<String, NativeSlotDisplayLabel> childSubTypeMap = nodeHelper.getChildSubTypeMap(parentTree);
if (CommonUtils.nullOrEmpty(childSubTypeMap))
return parentTree;
for (String holderCommonName : childSubTypeMap.keySet()) {
NativeSlotDisplayLabel nativeSlotDisplayLabel = childSubTypeMap.get(holderCommonName);
EquipmentDetails equipmentDetails = nodeHelper.constructEqptDetails(holderCommonName, nativeSlotDisplayLabel, parentTree);
Tree<EquipmentDetails> childTree = new Tree<>(equipmentDetails);
parentTree.addChild(childTree);
String equipmentCommonName = equipmentDetails.getModelEquipType();
if (equipmentCommonName == null)
continue;
buildTree(childTree);
}
return parentTree;
}
}
The second builder uses one more dependency and returns a Set
instead of a Tree
:
public class FreePortsBuilder {
private NodeHelper nodeHelper;
private PortLabelGenerator portLabelGenerator;
public FreePortsBuilder(NodeHelper nodeHelper, PortLabelGenerator portLabelGenerator) {
this.nodeHelper = nodeHelper;
this.portLabelGenerator = portLabelGenerator;
}
public Set<PortNameAndLabel> collectFreePorts(){
Tree<EquipmentDetails> tree = new Tree<>(nodeHelper.getEquipmentDetails(nodeHelper.getModelName()));
Set<PortNameAndLabel> portNameAndLabels = new HashSet<>();
return collectFreePorts(tree, portNameAndLabels);
}
private Set<PortNameAndLabel> collectFreePorts(Tree<EquipmentDetails> parentTree, Set<PortNameAndLabel> portNameAndLabels) {
Map<String, NativeSlotDisplayLabel> childSubTypeMap = nodeHelper.getChildSubTypeMap(parentTree);
if (CommonUtils.nullOrEmpty(childSubTypeMap))
return portNameAndLabels;
Set<PortNameAndLabel> generatedPortNameAndLabel = portLabelGenerator.getPortNameAndLabel(childSubTypeMap, parentTree.getUserObject());
portNameAndLabels.addAll(generatedPortNameAndLabel);
for (String holderCommonName : childSubTypeMap.keySet()) {
NativeSlotDisplayLabel nativeSlotDisplayLabel = childSubTypeMap.get(holderCommonName);
EquipmentDetails equipmentDetails = nodeHelper.constructEqptDetails(holderCommonName, nativeSlotDisplayLabel, parentTree);
Tree<EquipmentDetails> childTree = new Tree<>(equipmentDetails);
parentTree.addChild(childTree);
String equipmentCommonName = equipmentDetails.getModelEquipType();
if (equipmentCommonName == null)
continue;
collectFreePorts(childTree, portNameAndLabels);
}
return portNameAndLabels;
}
}
I am looking for a way to avoid the duplication. Any suggestions?
FreePortsBuilder
is doing two things at the same time:
Tree
: as you know, this is identical to what TreeBuilder
does. Set<PortNameAndLabel>
, where each PortNameAndLabel
appears to be associated with a node or child (which is itself a Tree
) in the final Tree
. Instead of doing the above two things simultaneously, do them sequentially. That is, in FreePortsBuilder
, first use TreeBuilder
to build the Tree
, then traverse this Tree
to construct the Set<PortNameAndLabel>
. One way to do that is the following (I assume that your Tree
class has a method like getChildren()
that returns a List<Tree<EquipmentDetails>>
).
First, allow a client to obtain a TreeBuilder
's NodeHelper
by adding a getter:
public class TreeBuilder {
private NodeHelper nodeHelper;
public NodeHelper getNodeHelper() {
return this.nodeHelper;
}
...
}
Then, inject a TreeBuilder
instead of a NodeHelper
into FreePortsBuilder
( FreePortsBuilder
can use the NodeHelper
that is in the TreeBuilder
, because of the getter that we just added):
public class FreePortsBuilder {
private TreeBuilder treeBuilder;
private PortLabelGenerator portLabelGenerator;
public FreePortsBuilder(TreeBuilder treeBuilder, PortLabelGenerator portLabelGenerator) {
this.treeBuilder = treeBuilder;
this.portLabelGenerator = portLabelGenerator;
}
public Set<PortNameAndLabel> collectFreePorts() {
Tree<EquipmentDetails> tree = treeBuilder.buildTree();
return collectFreePorts(tree);
}
private Set<PortNameAndLabel> collectFreePorts(Tree<EquipmentDetails> tree) {
Set<PortNameAndLabel> portNameAndLabels = new HashSet<>();
for (Tree<EquipmentDetails> child : tree.getChildren()) {
Map<String, NativeSlotDisplayLabel> childSubTypeMap =
treeBuilder.getNodeHelper().getChildSubTypeMap(child);
if (CommonUtils.nullOrEmpty(childSubTypeMap))
continue;
Set<PortNameAndLabel> generatedPortNameAndLabel =
portLabelGenerator.getPortNameAndLabel(childSubTypeMap, child.getUserObject());
portNameAndLabels.addAll(generatedPortNameAndLabel);
}
return portNameAndLabels;
}
}
Now, FreePortsBuilder
can truly make use of the TreeBuilder
implementation, and the code duplication is removed.
You can use Strategy design pattern or Template Method here. You have an algorithm here where only certain steps change (collectFreePorts & buildTree).
If you cannot use inheritance, you can use a similar solution to this: https://softwareengineering.stackexchange.com/questions/187872/template-method-within-one-class-without-subclasses-or-inheritance
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.