You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无源码场景下多服务多类型对象的多态设计方案咨询

问题

希望用多态实现多服务处理不同类型对象的简洁设计,但无法获取Car、Computer等目标类的源码。具体场景:

  • PhysicalDetailsService的calculateWeight方法需计算不同对象重量:
    • Car:重量 = 4 * tierWeight + frameWeight
    • Computer:重量 = processorWeight + casingWeight + powerBankWeight
  • PriceService的calculatePrice方法需以类似逻辑计算Car和Computer的价格

需要解决的核心问题:

  1. 如何设计服务方法的参数类型及相关类型?
  2. 无法修改Car、Computer类实现接口的情况下,是否需要定义接口并实现适配器?
  3. 避免大量if判断对象类型,是否适合用抽象工厂模式创建服务?
  4. 如何配置Spring Bean服务实现上述设计?
解决方案

一、参数类型与接口定义

先定义业务导向的接口,只暴露核心业务方法,不依赖原有类:

public interface WeightCalculable {
    double calculateWeight();
}

public interface PriceCalculable {
    BigDecimal calculatePrice();
}

服务方法直接使用这些接口作为参数,实现与具体对象的解耦:

@Service
public class PhysicalDetailsService {
    public double calculateWeight(WeightCalculable calculable) {
        return calculable.calculateWeight();
    }
}

@Service
public class PriceService {
    public BigDecimal calculatePrice(PriceCalculable calculable) {
        return calculable.calculatePrice();
    }
}

二、用适配器模式适配原有类

因为无法修改Car、Computer源码,用适配器模式包装原有对象,让它们实现上述业务接口,完成逻辑适配:

Car的适配器实现

public class CarWeightAdapter implements WeightCalculable {
    private final Car car;

    public CarWeightAdapter(Car car) {
        this.car = car;
    }

    @Override
    public double calculateWeight() {
        return 4 * car.getTierWeight() + car.getFrameWeight();
    }
}

public class CarPriceAdapter implements PriceCalculable {
    private final Car car;

    public CarPriceAdapter(Car car) {
        this.car = car;
    }

    @Override
    public BigDecimal calculatePrice() {
        // 此处实现Car的价格计算逻辑
        return new BigDecimal(car.getBasePrice()).multiply(new BigDecimal("1.2"));
    }
}

Computer的适配器实现

public class ComputerWeightAdapter implements WeightCalculable {
    private final Computer computer;

    public ComputerWeightAdapter(Computer computer) {
        this.computer = computer;
    }

    @Override
    public double calculateWeight() {
        return computer.getProcessorWeight() + computer.getCasingWeight() + computer.getPowerBankWeight();
    }
}

public class ComputerPriceAdapter implements PriceCalculable {
    private final Computer computer;

    public ComputerPriceAdapter(Computer computer) {
        this.computer = computer;
    }

    @Override
    public BigDecimal calculatePrice() {
        // 此处实现Computer的价格计算逻辑
        return computer.getComponentPrice().add(computer.getAssemblyFee());
    }
}

三、用类型映射器替代抽象工厂,消除if判断

抽象工厂在此场景下过于厚重,更轻量的方案是类型映射器:定义一个Bean存储「原有类型 -> 适配器实例」的映射,自动匹配对应适配器:

@Component
public class WeightAdapterMapper {
    private final Map<Class<?>, Function<Object, WeightCalculable>> adapterMap;

    public WeightAdapterMapper() {
        adapterMap = new HashMap<>();
        adapterMap.put(Car.class, obj -> new CarWeightAdapter((Car) obj));
        adapterMap.put(Computer.class, obj -> new ComputerWeightAdapter((Computer) obj));
    }

    public WeightCalculable getAdapter(Object obj) {
        Function<Object, WeightCalculable> adapterFactory = adapterMap.get(obj.getClass());
        if (adapterFactory == null) {
            throw new IllegalArgumentException("不支持的对象类型: " + obj.getClass().getName());
        }
        return adapterFactory.apply(obj);
    }
}

同理可实现PriceAdapterMapper,调用时只需通过映射器获取适配器,再传入服务方法:

// 调用示例
@Autowired
private PhysicalDetailsService physicalDetailsService;
@Autowired
private WeightAdapterMapper weightAdapterMapper;

public double getObjectWeight(Object obj) {
    WeightCalculable adapter = weightAdapterMapper.getAdapter(obj);
    return physicalDetailsService.calculateWeight(adapter);
}

后续新增类型时,仅需在映射器中添加新的映射关系,完全避免if/else判断。

如果要遵循开闭原则,可通过Spring自动收集适配器工厂:

@Component
public class WeightAdapterMapper {
    private final Map<Class<?>, WeightAdapterFactory> factoryMap;

    // 构造函数注入所有WeightAdapterFactory类型的Bean
    public WeightAdapterMapper(List<WeightAdapterFactory> factories) {
        factoryMap = factories.stream()
                .collect(Collectors.toMap(WeightAdapterFactory::getSupportedType, Function.identity()));
    }

    public WeightCalculable getAdapter(Object obj) {
        WeightAdapterFactory factory = factoryMap.get(obj.getClass());
        if (factory == null) {
            throw new IllegalArgumentException("不支持的对象类型: " + obj.getClass().getName());
        }
        return factory.createAdapter(obj);
    }

    // 适配器工厂接口
    public interface WeightAdapterFactory {
        Class<?> getSupportedType();
        WeightCalculable createAdapter(Object obj);
    }
}

每个适配器对应一个工厂Bean:

@Component
public class CarWeightAdapterFactory implements WeightAdapterMapper.WeightAdapterFactory {
    @Override
    public Class<?> getSupportedType() {
        return Car.class;
    }

    @Override
    public WeightCalculable createAdapter(Object obj) {
        return new CarWeightAdapter((Car) obj);
    }
}

这种方式新增类型时仅需添加适配器和对应的工厂Bean,无需修改原有映射器代码。

四、Spring Bean配置

  • 服务类PhysicalDetailsService、PriceService用@Service注解,Spring自动扫描注册
  • 映射器类WeightAdapterMapper、PriceAdapterMapper用@Component注解,自动注册
  • 适配器工厂类(如CarWeightAdapterFactory)用@Component注解,自动被映射器收集

适配器类无需注册为Bean,因为它们是针对具体对象的包装,需要按需创建。

内容的提问来源于stack exchange,提问作者sjakovac

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 20:40:25