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

不依赖外部库,Java中动态处理抽象类子类以遵循开闭原则的实现方案

不依赖外部库,Java中动态处理抽象类子类以遵循开闭原则的实现方案

嘿,我完全懂你的烦恼——每次新增一个形状就得去Main类里加一堆硬编码的判断分支,这完全踩了开闭原则的红线对吧?别担心,咱们用Java原生的机制就能解决,不用依赖任何外部库,下面给你两个可行的方案:


方案一:使用Java SPI(服务提供者接口)机制(官方推荐)

SPI是Java自带的服务发现机制,专门用来实现“新增服务无需修改主代码”的场景,完美匹配你的需求。

步骤1:准备你的抽象类与子类

假设你的Forme抽象类(带原型模式的克隆方法)是这样的:

public abstract class Forme {
    protected String style;

    public Forme(String style) {
        this.style = style;
    }

    // 原型模式核心:克隆方法
    public abstract Forme cloner();
    public abstract void dessiner();
}

子类比如Cercle的实现:

public class Cercle extends Forme {
    public Cercle(String style) {
        super(style);
    }

    @Override
    public Forme cloner() {
        return new Cercle(this.style);
    }

    @Override
    public void dessiner() {
        System.out.println("绘制圆形");
    }
}

步骤2:创建SPI配置文件

在项目的src/main/resources/META-INF/services/目录下,新建一个以Forme抽象类全限定名命名的文件(比如你的Forme在com.example.shapes包下,就创建文件com.example.shapes.Forme),文件里每一行写一个子类的全限定名:

com.example.shapes.Cercle
com.example.shapes.Triangle
com.example.shapes.Rectangle

以后新增形状,只需要:

  1. 写一个Forme的子类
  2. 在这个配置文件里加一行子类的全限定名
    完全不用碰Main类!

步骤3:修改Main类用ServiceLoader动态加载

替换原来的硬编码判断,用ServiceLoader自动加载所有Forme子类:

import java.util.*;
import java.util.ServiceLoader;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        Palette pal = new Palette();
        Editeur editeur = new Editeur(pal);

        // 自动加载所有Forme的子类
        ServiceLoader<Forme> formeLoader = ServiceLoader.load(Forme.class);

        for (Forme formePrototype : formeLoader) {
            String shapeName = formePrototype.getClass().getSimpleName();
            System.out.printf("Add %s (0/1)? ", shapeName);
            int input = scanner.nextInt();
            if (input == 1) {
                // 用原型模式的克隆方法创建新实例,符合原型设计模式要求
                pal.ajouter(formePrototype.cloner());
            }
        }

        editeur.dessiner();
        editeur.afficherDessin();
        scanner.close();
    }    
}

方案二:自定义包扫描(反射实现)

如果你不想维护SPI配置文件,也可以用反射扫描指定包下的所有Forme子类,自己实现类似Python.__subclasses__()的效果。

步骤1:编写包扫描工具类

这个工具类会遍历指定包下的所有类,筛选出Forme的子类:

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Enumeration;
import java.util.stream.Collectors;

public class ShapeScanner {
    public static List<Class<? extends Forme>> getFormeSubclasses(String packageName) throws Exception {
        String packagePath = packageName.replace('.', '/');
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> resources = classLoader.getResources(packagePath);
        List<File> dirs = new ArrayList<>();

        // 收集包对应的文件目录
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();
            dirs.add(new File(resource.getFile()));
        }

        List<Class<?>> allClasses = new ArrayList<>();
        for (File dir : dirs) {
            allClasses.addAll(findClassesInDir(dir, packageName));
        }

        // 筛选出Forme的子类,排除抽象类本身
        return allClasses.stream()
                .filter(clazz -> Forme.class.isAssignableFrom(clazz) && !clazz.equals(Forme.class))
                .map(clazz -> (Class<? extends Forme>) clazz)
                .collect(Collectors.toList());
    }

    private static List<Class<?>> findClassesInDir(File dir, String packageName) throws ClassNotFoundException {
        List<Class<?>> classes = new ArrayList<>();
        if (!dir.exists()) return classes;

        File[] files = dir.listFiles();
        if (files == null) return classes;

        for (File file : files) {
            if (file.isDirectory()) {
                // 递归扫描子包
                classes.addAll(findClassesInDir(file, packageName + "." + file.getName()));
            } else if (file.getName().endsWith(".class")) {
                // 加载类
                String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);
                classes.add(Class.forName(className));
            }
        }
        return classes;
    }
}

注意:这个基础版本只支持文件系统中的类,如果你的项目打包成Jar包,需要额外处理Jar中的类加载逻辑。

步骤2:修改Main类使用扫描工具

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        Palette pal = new Palette();
        Editeur editeur = new Editeur(pal);

        try {
            // 扫描指定包下的所有Forme子类(替换成你的实际包名)
            List<Class<? extends Forme>> shapeClasses = ShapeScanner.getFormeSubclasses("com.example.shapes");

            for (Class<? extends Forme> shapeClass : shapeClasses) {
                String shapeName = shapeClass.getSimpleName();
                System.out.printf("Add %s (0/1)? ", shapeName);
                int input = scanner.nextInt();
                if (input == 1) {
                    // 反射创建实例(匹配你原来的new Cercle(null)的构造参数)
                    Forme newShape = shapeClass.getConstructor(String.class).newInstance(null);
                    pal.ajouter(newShape);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        editeur.dessiner();
        editeur.afficherDessin();
        scanner.close();
    }    
}

以后新增形状,只需要把新的Forme子类放在指定包下即可,完全不用修改Main类。


方案对比

方案优点缺点适用场景
SPI机制Java官方标准,支持Jar包环境,无需额外代码维护需要手动维护配置文件生产环境、需要规范扩展的场景
自定义包扫描无需配置文件,更灵活需要处理不同环境的类加载逻辑小型项目、快速迭代的场景

备注:内容来源于stack exchange,提问作者MOB

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 17:20:29