【问题标题】:Abstract factory design pattern and generics - Java抽象工厂设计模式和泛型 - Java
【发布时间】:2019-01-27 13:41:51
【问题描述】:

这是我第一次使用this design pattern,我遇到了一些困难。以此图为参考:

我有两个 AbstractProduct 我想为其创建一个工厂,因为它们属于不同的“家族”。一种产品是另一种产品的集合。

public abstract class Frame{
    ...
}

public abstract class FrameMap<F extends Frame>{
    protected TreeMap<Integer, F> map;

    public F readByKey(Integer key){
        return this.map.get(key);
    }

    public void put(Integer key, F frame){
        this.map.put(key,frame);
    }
    ...
}

注意FrameMapF 的集合(为简单起见,我省略了大部分其他方法),因此它需要能够获取和放置元素。 以一个家庭为例(App1 的那个)这些是具体的产品

public class App1Frame extends Frame{
    ...
}

public class App1FrameMap extends FrameMap<App1Frame>{
    ...
}

然后我创建了AbstractFactory

public abstract class ApplicationFactory{
    //Frame factory methods
    public abstract Frame makeFrame();

    //FrameMap factory methods
    public abstract FrameMap<? extends Frame> makeFrameMap();
}

以及 App1 的 ConcreteFactory

public class App1Factory extends ApplicationFactory{
    //Frame factory methods
    public Frame makeFrame(){
        return new App1Frame();
    }

    //FrameMap factory methods
    public FrameMap<App1Frame> makeFrameMap(){
        return new App1FrameMap();
    }
}

所有这些都可以编译,但我不太喜欢它,因为我的客户无法执行以下操作:

ApplicationFactory factory = new App1Factory();
FrameMap<Frame> frameMap = factory.makeFrameMap(); //This doesn't compile!

至少在概念上,这是该模式的目标,即让客户端只使用抽象类。为了让它工作,我必须做这样的事情:

App1Factory factory = new App1Factory();
FrameMap<App1Frame> frameMap = factory.makeFrameMap(); //This compiles!

这似乎违背了使用设计模式的目的。

您是否知道在像我这样的情况下实现此模式的更好方法,即在 AbstractProduct 中使用泛型,以便让客户端只使用抽象类而不知道底层实现?

【问题讨论】:

  • 工厂模式已经过时,有利于依赖注入/控制反转。您是否考虑过改用它?
  • 你能再具体一点吗?我对 DI 不太熟悉,但乍一看我不明白它如何解决我的问题。

标签: java generics design-patterns abstract-factory


【解决方案1】:

您可以将泛型类型添加到ApplicationFactory

public class MainJava {
    interface Frame{}
    interface FrameMap<T extends Frame>{}

    static class App1Frame implements Frame{}
    static class App2Frame implements Frame{}
    static class App1FrameMap implements FrameMap<App1Frame>{}
    static class App2FrameMap implements FrameMap<App2Frame>{}

    interface ApplicationFactory<T extends Frame> {
        T makeFrame();
        FrameMap<T> makeFrameMap();
    }

    static class App1Factory implements ApplicationFactory<App1Frame> {
        @Override public App1Frame makeFrame() {
            return new App1Frame();
        }
        @Override public FrameMap<App1Frame> makeFrameMap() {
            return new App1FrameMap();
        }
    }

    public static void main(String... args) {
        ApplicationFactory<? extends Frame> factory = new App1Factory();
        Frame frame = factory.makeFrame();
        FrameMap<? extends Frame> map = factory.makeFrameMap();
    }
}

还要注意ApplicationFactory 对象应该是injected 进入客户端。如果客户端直接调用new App1Factory(),那么抽象是没有意义的。客户端还不如在创建后使用App1Factory

【讨论】:

  • 是的,我知道注射,但这不是现在的主要问题。我想过这个解决方案,但是这样我仍然无法将Frame 对象添加到map;理想情况下,我希望能够将factory.makeFrame() 返回的Frame 添加到factory.makeFrameMap() 返回的FrameMap;在我的问题中,我省略了方法(稍后我将尝试对其进行编辑),但是作为集合 FrameMap 需要能够从中添加和检索对象。
  • 看看What is PECS (Producer Extends Consumer Super)? 简而言之,不能使用有界泛型参数创建读/写集合。
  • 我知道,这就是为什么我已经放弃了你的解决方案。也许这种模式不适合我的目的?
  • 是的,我认为这是一个 XY 问题。设计缺陷是可变集合。消除可变性,其他一切都会到位。
  • @Alessandro 如果您同时调用 makeFramemakeFrameMap 希望将一段代码包含在方法中(静态或非静态),您可以使用类型参数参数来约束 @框架和地图的 987654337@ 是同一个,在这种情况下它应该可以工作。无论集合是读取还是读取/写入,这都会起作用。
【解决方案2】:

除了@jaco0646 解决方案,还有一种简单的方法可以解决将新创建的框架添加到地图的问题,即使您在运行时不知道实际的子类型或“族”。只要您可以将 frame 和 frame-map 类型参数绑定为在这样的方法中具有相同的值:

public static <T extend Frame> FrameMap<T> makeAndAdd(final AbstractFactory<T> factory) {
    T frame = factory.makeFrame();
    FrameMap<T> map = factory.makeFrameMap();
    map.add(frame);
    return map;
}

当不可能将两个动作放在同一个方法中时(例如,在不同的服务器调用上异步发生)。您仍然可以通过使用另一个具有类型参数并且具有这些操作的不同方法的类来实现这种联系......这有点复杂,需要根据实际问题进行调整,但想法是相同的从某种意义上说,您使用类型参数来绑定在运行时赋予不同对象的类型参数值。

【讨论】:

  • 也许我遗漏了一些东西,但我不明白这个解决方案能实现什么。工厂的重点不是我不想想指定T吗? T是抽象产品Frame扩展的具体产品;客户端只能使用Frame
  • 从您在 @jaco0646 上的 cmets 交换答案看来,您可能无法在返回的地图上使用返回的帧。此答案解释了该问题的解决方案。
  • 这里的 T 保证是 Frame 加三是通过方法类型参数保证 Frame T 与地图 T 相同,因此您可以将框架添加到地图中。
  • 任何你不需要指定的T...用户可以用它喜欢的任何工厂调用这个方法...T很可能是?,试试看。跨度>
猜你喜欢
  • 2010-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多