Java中从父类对象列表调用子类方法的可行方案
刚好碰到过类似的场景,给你几个实用的方案,你可以根据业务复杂度和扩展性需求来选:
1. 直接用instanceof判断+强制类型转换(最简单直接)
如果你的子类数量不多、业务逻辑不复杂,这个方案最省心。直接遍历列表时判断类型,强转后调用特有方法:
public class DeviceHandler { private List<Device> devices = new ArrayList<>(); public void callSubclassMethods() { for (Device device : devices) { if (device instanceof Phone) { ((Phone) device).makeCall(); // Phone的特有方法 } else if (device instanceof Printer) { ((Printer) device).printDocument(); // Printer的特有方法 } // 其他子类同理 } } }
缺点:如果后续新增子类,需要修改这个方法的判断逻辑,违反开闭原则,但小项目或者稳定的场景下完全够用。
2. 父类添加默认方法,子类重写
如果子类的特有方法可以抽象出一个通用的行为名称(比如“执行设备专属操作”),可以在父类中定义这个方法并给默认实现,子类按需重写:
public abstract class Device { // 父类定义通用方法,默认空实现 public void executeSpecificAction() {} } public class Phone extends Device { @Override public void executeSpecificAction() { this.makeCall(); } public void makeCall() { // 打电话逻辑 } } public class Printer extends Device { @Override public void executeSpecificAction() { this.printDocument(); } public void printDocument() { // 打印逻辑 } }
然后在DeviceHandler里直接调用父类方法就行:
public void callSubclassMethods() { for (Device device : devices) { device.executeSpecificAction(); } }
优点:完全符合开闭原则,新增子类只需要重写方法即可;缺点:如果子类的特有方法差异极大,很难抽象出通用名称,这个方案就不适用。
3. 改进访问者模式(支持返回值)
你之前说访问者模式无法返回值,其实可以通过泛型让访问者支持返回值。比如定义泛型访问者接口,让每个visit方法返回指定类型:
// 泛型访问者接口 public interface DeviceVisitor<T> { T visitPhone(Phone phone); T visitPrinter(Printer printer); // 其他子类对应的visit方法 } // 父类添加accept方法,接收泛型访问者 public abstract class Device { public abstract <T> T accept(DeviceVisitor<T> visitor); } // Phone子类实现accept public class Phone extends Device { public void makeCall() { /* ... */ } @Override public <T> T accept(DeviceVisitor<T> visitor) { return visitor.visitPhone(this); } } // Printer子类实现accept public class Printer extends Device { public void printDocument() { /* ... */ } @Override public <T> T accept(DeviceVisitor<T> visitor) { return visitor.visitPrinter(this); } }
然后实现一个带返回值的访问者,比如获取设备操作结果:
public class SpecificActionVisitor implements DeviceVisitor<String> { @Override public String visitPhone(Phone phone) { phone.makeCall(); return "电话已拨打"; } @Override public String visitPrinter(Printer printer) { printer.printDocument(); return "文档已打印"; } }
在DeviceHandler里使用:
public List<String> executeAllSpecificActions() { List<String> results = new ArrayList<>(); for (Device device : devices) { String result = device.accept(new SpecificActionVisitor()); results.add(result); } return results; }
优点:适合需要对不同子类做多维度操作的场景,扩展性好;缺点:新增子类时需要修改访问者接口和所有实现类,代码量会增加。
4. 策略模式+类型映射(扩展性最优)
可以在DeviceHandler中维护一个Class到处理逻辑的映射,把每个子类的特有方法封装成策略,这样新增子类只需要添加映射关系,不用修改原有代码:
public class DeviceHandler { private List<Device> devices = new ArrayList<>(); // 维护子类Class到处理逻辑的映射 private Map<Class<? extends Device>, Consumer<Device>> actionMap = new HashMap<>(); // 初始化映射 public DeviceHandler() { actionMap.put(Phone.class, device -> ((Phone) device).makeCall()); actionMap.put(Printer.class, device -> ((Printer) device).printDocument()); // 新增子类时在这里加一行就行 } public void executeAllSpecificActions() { for (Device device : devices) { // 根据设备类型获取对应的处理逻辑并执行 Consumer<Device> action = actionMap.get(device.getClass()); if (action != null) { action.accept(device); } } } }
优点:完全符合开闭原则,新增子类只需要添加映射,原有代码零修改;缺点:需要提前维护映射关系,如果子类动态生成的话不太适用。
我之前在维护一个IoT设备管理系统的时候,就用策略模式解决过类似问题,新增设备类型的时候只需要往Map里加一行配置,非常方便。
内容的提问来源于stack exchange,提问作者user8770209

