Java. Pattern Composite on Game Server.
Also known as Tree, Composite
The essence of the pattern
Composite — is a structural design pattern that allows you to combine objects into tree structures and then work with those structures as if they were separate objects.
Problem
Using the Composite pattern only makes sense when the underlying model of your application can be represented as a tree.
For example, imagine you have two types of objects: a reward configuration and a chest configuration. A chest can contain several Rewards, as well as several smaller Chests. These small chests can also include some rewards or even smaller chests, etc.
Let’s say you’ve decided to create a reward system that uses these classes. The Rewards configuration can contain simple rewards without packing in a chest and reward chests … and other chests. How would you calculate all the rewards based on this configuration?
You can try the direct approach: expand all chests, view all rewards, and then calculate rewards by type. This would be feasible in the real world, but it is not as easy as starting a loop in a program. In advance, you should know the classes of rewards and chests that you go through, the level of nesting of chests, and other unpleasant details. All this makes the direct approach either too inconvenient or even impossible.
Solution
The Composite pattern assumes that you interact with rewards and chests through a standard interface that declares the method for obtaining a specific reward.
How will this method work? For the award, the ATT of the award will simply be returned. For a chest, the method will go through all the items contained in the chest, generate a reward, and then return the total rewards for that chest. If one of these items were a smaller chest, that chest would also start scrolling through its contents, and so on, until the rewards for all internal components were calculated. The chest can even add some additional stats to the final reward, such as a reward multiplier.
The biggest advantage of this approach is that you don’t have to worry about the specific classes of objects that make up the tree. You don’t need to know if the item is a simple reward or a hard chest. You can get them all the same way through the common interface. When you call a method, the objects themselves pass the request down the tree.
Applicability
Use the Composite pattern when you need to implement a tree-like object structure.
The Composite pattern provides you with two basic elements that share a common interface: simple leaves and complex containers. The container can be composed of leaves or other containers. This allows you to create a nested recursive object structure that resembles a tree.
Use a template if you want the client code to handle both simple and complex elements in the same way.
All elements defined by the Composite template share a standard interface. Using this interface, the client does not need to worry about the specific class of objects he is working with.
How to implement?
- Make sure the base model of your application can be represented as a tree structure. Try to break it down into simple elements and containers. Remember that containers must contain both simple elements and other containers.
- Declare a component interface with a list of methods that make sense for both simple and complex components.
- Create a leaf class to represent simple elements. A program can have several different target classes.
- Create a container class to represent complex elements. In this class, provide an array field to store subelement references. The array should store both leaves and containers, so make sure it is declared with the component’s interface type. When implementing the bean interface methods, remember that the container should delegate most of the work to subelements.
- Finally, define methods for adding and removing children in the container. Be aware that these operations can be declared in the component’s interface. This will violate the principle of interface separation because the methods will be empty in the final class. However, the client will treat all elements in the same way, even when drawing up a tree.
System for collecting rewards.
Create a basic RewardItem interface with a method that will return a list of DTO rewards.
public interface RewardItem {
List<?> rewardFor();
}
Next, we define the base reward class BaseReward
public abstract class BaseReward implements RewardItem {
private final String type; BaseReward(String type){
this.type = type;
} public abstract List<?> rewardFor(); public String getType() {
return type;
}
}
For example, I will set the type of reward in the constructor of the base type. And there are several types of GoldReward, GemReward and ChestReward rewards.
public class GoldReward extends BaseReward {
GoldReward(){
super(“GOLD”);
} @Override
public List<?> rewardFor() { /*
* We can return list of needed DTO objects
* Now we only print it
*/
System.out.println(“rewardFor: “ + this.getType()); return Collections.emptyList();
}
}
public class GemReward extends BaseReward {
GemReward(){
super(“GEM”);
} @Override
public List<?> rewardFor() {
System.out.println(“rewardFor: “ + this.getType());
return Collections.emptyList();
}
}
public class ChestReward extends BaseReward { private List<BaseReward> rewards; ChestReward(){
super(“CHEST”);
} @Override
public List<?> rewardFor() {
System.out.println(“rewardFor: “ + this.getType());
return rewards.stream().flatMap(reward ->
reward.rewardFor()
.stream())
.collect(Collectors.toList());
} public void setRewards(List<BaseReward> rewards) {
this.rewards = rewards;
}
}
You can see that in ChestReward we run through all the internal rewards and collect them in the resulting list.
Let’s test our rewards by creating several of them and calling the rewardFor method.
public class Tester { public static void main(String[] args) {
GoldReward goldReward = new GoldReward();
GemReward gemReward = new GemReward();
ChestReward chestReward = new ChestReward();
ChestReward smallChestReward = new ChestReward(); smallChestReward.setRewards(List.of(gemReward, goldReward));
chestReward.setRewards(List.of(goldReward, gemReward, smallChestReward));
chestReward.rewardFor();
}
}
Based on this pattern, you can build a fairly flexible reward system.