2

It's the first time I'm using this design pattern and I'm having some difficulties. Taking this image as reference:

enter image description here

I have two AbstractProduct that I would like to create a factory for, since they come in different "families". One of the products is a collection of the other.

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

Note that FrameMap is a collection of F (I omitted most of the other methods for simplicity), so it needs to be able to get and put elements. Taking one family as an example (the one for App1) these are the concrete Product:

public class App1Frame extends Frame{
    ...
}

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

Then I have created the AbstractFactory:

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

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

And the ConcreteFactory for App1:

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

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

All of this compiles, but I'm not liking it very much because my Client cannot do something like:

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

which is, at least conceptually, the goal of the pattern, i.e. letting the client only use the abstract classes. In order to make it work, I have to do something like this:

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

which seems going against the purpose of using the design pattern.

Do you know any better way of implementing this pattern in a situation like mine, i.e. using generics in the AbstractProduct, in order to let the Client only use the abstract classes without knowing the underlying implementation?

Alessandro
  • 451
  • 2
  • 4
  • 16
  • 1
    The factory pattern has gone out of fashion in favour of Dependency Injection / Inversion of Control. Have you considered using this instead? – lance-java Jan 27 '19 at 13:55
  • Can you be a bit more specific? I'm not too familiar with DI, but at first glance I don't see how it could solve my problem. – Alessandro Jan 27 '19 at 15:15

2 Answers2

0

You can add the generic type to the 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();
    }
}

Also note the ApplicationFactory object should be injected into the client. If the client directly calls new App1Factory() there is no point to the abstraction. The client might as well use App1Factory after creating it.

jaco0646
  • 15,303
  • 7
  • 59
  • 83
  • Yes, I know about the injection, but that is not the main problem for now. I thought about this solution, but this way I still cannot add a `Frame` object to `map`; ideally, I'd like to be able to add a `Frame` returned by `factory.makeFrame()` to the `FrameMap` returned by `factory.makeFrameMap()`; in my question I omitted the methods (I'll try to edit it later), but being a collection `FrameMap` needs to be able to add and retrieve objects from it. – Alessandro Jan 27 '19 at 16:43
  • Take a look at [What is PECS (Producer Extends Consumer Super)?](https://stackoverflow.com/q/2723397/1371329) In short, you cannot create a read/write collection using bounded generic parameters. – jaco0646 Jan 27 '19 at 20:33
  • I know that, that's why I had already discarded your solution. Maybe this pattern is not suitable for my purpose? – Alessandro Jan 27 '19 at 21:29
  • 1
    Yes, I think this turned out to be an XY problem. The design flaw is the mutable collection. Eliminate mutability and everything else falls into place. – jaco0646 Jan 27 '19 at 21:47
  • @Alessandro if you are calling both ```makeFrame``` and ```makeFrameMap``` wishing a piece of code that be enclosed in an method (static or not) you can use a type-param argument to constraint the ```T``` of both the frame and the map to be the same one and in that case it should work. This would work regardless of whether the collection is read or read/write. – Valentin Ruano Jan 28 '19 at 17:28
0

Further to @jaco0646 solution, there is a simple way to resolve the problem of adding a freshly created frame to a map even if you don't know in runtime the actual subtype or "family". That is as long as you can tie both the frame and frame-map type parameter to have the same value within a method like so:

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

When it is not possible to put both actions in the same method (e.g. the occur assyncronously on different server calls). You can still achieve this tie by using a yet another class with a type-parameter and that have different methods of those actions... this is a bit more complex that would need to be tailored to the actual problem but the idea is the same in the sense that you use a type-parameter to tie the type-parameter values given to different object in run-time.

Valentin Ruano
  • 2,726
  • 19
  • 29
  • Maybe I'm missing something, but I don't understand what this solution achieves. Isn't the point of the factory the fact that I **don't** want to specify `T`? `T` is the concrete product that extends the abstract product `Frame`; the client should only work with `Frame`. – Alessandro Jan 28 '19 at 19:50
  • From your comments exchange on @jaco0646 answer it seems that there is a pending concern that you cannot use the returned frame on the returned map. This answer explains a solution to that issue. – Valentin Ruano Jan 28 '19 at 20:35
  • T here is guarantee to be Frame plus three is a guarantee that the frame T is the same as the map T thru the method type-parameter therefore you can add the frame to the map. – Valentin Ruano Jan 28 '19 at 20:36
  • Any you don't need to speficy T... the user would call this method with whatever factory it pleases... the T could well be ```?```, just try it out. – Valentin Ruano Jan 28 '19 at 20:39