不依赖外部库,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
以后新增形状,只需要:
- 写一个
Forme的子类 - 在这个配置文件里加一行子类的全限定名
完全不用碰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
相关产品推荐
相关产品推荐

