工厂模式与抽象澄清

时间:2013-07-05 00:09:57

标签: java design-patterns factory-pattern

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

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

factory method pattern with class registration produces a nullpointer exception

我理解为什么我得到空指针异常(HashMap在使用时没有填充),我知道我可以通过在 class.forName 中修复它strong> main 或工厂实施中的静态块。

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

以下是示例代码:

Main.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();
  }
}

Widget.java

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

WidgetFactory.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;
    }
}

WidgetA.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类吗?

感谢您的帮助!

1 个答案:

答案 0 :(得分:0)

如果没有反射就不能这样做,因为JVM规范强制执行延迟类加载以节省资源。 JVM既没有参数也没有“EagerClassLoader”(它加载所有jar的所有类)文件)。另请参阅this questionthis one以及this one的答案。

但是,您可以使用以下代码通过反射加载所有类的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));
            }
        }
    }

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

我的示例代码的灵感来自this sample code,它也会加载子包类。

编辑:

我使用目录分支扩展了我的实现,以便它以递归方式工作。

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());
        }
    }
}