2

我试图澄清工厂模式,如下所述(带有示例): http ://www.oodesign.com/factory-pattern.html

当我尝试实现“没有反射的类注册”示例时,我得到一个空指针异常。这与此处描述的相同:

具有类注册的工厂方法模式产生空指针异常

我理解为什么会出现空指针异常(HashMap 在使用时未填充),并且我知道我可以通过在工厂实现中的main或静态块中使用class.forName来修复它。

但这不是违背了使用这种模式的目的吗?我认为这个想法是创建的对象进行了注册 - 如果您被迫手动加载类以强制静态块运行,这是否违反了打开关闭原则?

这是示例代码:

主.java

public class Main {
  public static void main(String[] args) {
    Widget widgetA = WidgetFactory.getInstance().createWidget("WidgetA");
    Widget widgetB = WidgetFactory.getInstance().createWidget("WidgetB");

    widgetA.doSomething();
    widgetB.doSomething();
  }
}

小部件.java

public abstract class Widget {
    protected abstract Widget createWidget();
    public abstract void doSomething();
}

小部件工厂.java

public class WidgetFactory {

    private static WidgetFactory instance;

    public static synchronized WidgetFactory getInstance() {
        if (instance == null) {
            instance = new WidgetFactory();
        }
        return instance;
    }
    private HashMap<String, Widget> widgets = new HashMap<>();

    public void registerWidget(String id, Widget widget) {
        widgets.put(id, widget);
    }

    public Widget createWidget(String id) {
        Widget widget = widgets.get(id).create();
        return widget;
    }
}

小部件A.java

public class WidgetA extends Widget {

    static {
        WidgetFactory.getInstance().registerWidget("WidgetA", new WidgetA());
    }

    @Override
    protected Widget createWidget() {
        return new WidgetA();
    }

    @Override
    public void doSomething() {
        System.out.println("WidgetA");
    }

}

WidgetB.java

public class WidgetB extends Widget {

    static {
        WidgetFactory.getInstance().registerWidget("WidgetB", new WidgetB());
    }

    @Override
    protected Widget createWidget() {
        return new WidgetB();
    }

    @Override
    public void doSomething() {
        System.out.println("WidgetB");
    }

}

如前所述,为了让它工作,我可以把它放在 Main 或 WidgetFactory 类中:

static {
    try {
        Class.forName(WidgetA.class.getName());
        Class.forName(WidgetB.class.getName());
    } catch (ClassNotFoundException e) {
        e.printStackTrace(System.err);
    }
}

再次,有人可以澄清一下这个模式应该如何实现或分享一种技术来使这个模式工作,而不必在每次添加新的 Widget 子类时更新 Main 或 WidgetFactory 类?

谢谢您的帮助!

4

1 回答 1

0

你不能在没有反射的情况下做到这一点,因为 JVM 规范强制延迟类加载以节省资源。既没有 JVM 的参数,也没有“EagerClassLoader”(它加载所有 jar 文件的所有类)。另请参阅此问题的答案以及此问题此问题答案。

但是,您可以使用以下代码通过反射加载 ONE 包的所有类:

public static void loadAllClassesOfPackage(String packageName) throws IOException, ClassNotFoundException {
        ClassLoader classLoader = Thread.currentThread()
                .getContextClassLoader();
        assert classLoader != null;
        String path = packageName.replace('.', '/');
        Enumeration<URL> resources = classLoader.getResources(path);
        File packageDir = new File(resources.nextElement().getFile());
        for (File file : packageDir.listFiles()) {
            if (file.isFile() && file.getName().endsWith(".class")) {
                Class.forName(packageName
                        + '.'
                        + file.getName().substring(0,
                                file.getName().length() - 6));
            }
        }
    }

只要您只加载具有“合理”数量的类的一个包的类,就不会占用太多资源。

我的示例代码受到这个示例代码的启发,它也加载了子包的类。

编辑:

我用一个目录分支扩展了我的实现,以便它递归地工作。

public static void loadAllClassesOfPackageRecursively(String packageName)
        throws IOException, ClassNotFoundException {
    ClassLoader classLoader = Thread.currentThread()
            .getContextClassLoader();
    assert classLoader != null;
    String path = packageName.replace('.', '/');
    Enumeration<URL> resources = classLoader.getResources(path);
    File packageDir = new File(resources.nextElement().getFile());
    for (File file : packageDir.listFiles()) {
        if (file.isFile() && file.getName().endsWith(".class")) {
            Class.forName(packageName
                    + '.'
                    + file.getName().substring(0,
                            file.getName().length() - 6));
        } else if (file.isDirectory()) {
            loadAllClassesOfPackageRecursively(packageName + '.' + file.getName());
        }
    }
}
于 2013-07-08T09:32:11.597 回答