In a complex system it is understandable that you may have to dynamically perform an action without statically knowing whether this object supports the action. In my experience, it is best to ask the object – not by casting, but by guarding access to the method. For example:
@FunctionalInterface
interface ReloadAction { void reload(); }
public abstract class Weapon {
public abstract void attack();
public ReloadAction getReloadAction() { return null; }
}
public ReloadableWeapon extends Weapon {
...
@Override
public ReloadAction getReloadAction() {
return () -> { this.ammo = this.maxAmmoCapacity; };
}
}
In client code:
void handleReloadEvent(Weapon w) {
ReloadAction reload = w.getReloadAction();
if (reload != null) reload.reload();
else showErrorMessage("this weapon can't be reloaded");
}
Ideally, the UI adapts itself to prevent impossible events.
To make the signature more explicit you could return a Optional<ReloadAction>
.
How does this relate to your suggestions?
This is similar in effect to a downcast but is a bit more typesafe. It is also far more extensible (in the sense of the Open/Closed Principle), since it is now the Weapon
object itself and not the client code which decides whether a weapon is reloadable. In particular, you can now have different kinds of weapons that are reloadable without having to inherit from Reloadable
. This composability also allows you to handle weapons that support a combination of multiple interfaces, without having to create a new class for each combination: the main Weapon
class is often sufficient if you supply the available actions through the constructors.
This solution is the exact opposite of using a Visitor. With the visitor you cannot add arbitrary new classes but can add more actions through visitors. This is because the visitor interface describes which classes are supported. But here we have added a new method to the Weapon
interface. So we cannot add arbitrary new actions, but we can create more subclasses that implement these actions. Whether a visitor or these method objects are more appropriate depends on how you expect to extend your code in the future: is it more likely to add new actions (then prefer a visitor) or more likely to add new implementations (then prefer method objects)?
Related concepts:
- Entity-Attribute-Value systems: describing a data model dynamically instead of using the type system of the host language.
- Object Adapter Pattern: these method objects behave like adapters from the Weapon class to the Action interface.
- Type Object Pattern: composition instead of inheritance, especially for complex behaviours that are common in games.
- Virtual Constructor idiom, especially in C++: use methods instead of downcasting. Related to the Template Method Pattern.