2

I have a list of subclasses which are all instance of Super class. My goal is to create a factory, which will return different result depending on subclass.

public class SuperClass {
    ...
}

public class SubClass1 extends SuperClass {
    ...
}

public class SubClass2 extends SuperClass {
    ...
}

public class Factory {
    public static getInstance(SubClass1 item) {
        return new EditText();
    }

    public static getInstance(SubClass2 item) {
        return new CheckBox();
    }
}

public class Generator {
    public Generator() {
        List<SuperClass> list = getList();

        for (SuperClass item : list) {
            Factory.getInstance(item);
        }
    }

    List<SuperClass> getList() {
        ...
    }
}

new Generator();

This code will fail during compilation as it will require getInstance(SuperClass item) overloading, but if I add it then it always will be called.

Is there a way to do this without touching SuperClass, SubClass1, SubClass2?

UPD. In order to clarify what I want to archive here is the original code:

import android.content.Context;
import android.view.View;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.RadioButton;

import com.tom_roush.pdfbox.cos.COSArray;
import com.tom_roush.pdfbox.cos.COSDictionary;
import com.tom_roush.pdfbox.cos.COSName;
import com.tom_roush.pdfbox.pdmodel.PDDocument;
import com.tom_roush.pdfbox.pdmodel.PDDocumentCatalog;
import com.tom_roush.pdfbox.pdmodel.PDPage;
import com.tom_roush.pdfbox.pdmodel.PDPageTree;
import com.tom_roush.pdfbox.pdmodel.common.PDRectangle;
import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import com.tom_roush.pdfbox.pdmodel.interactive.form.PDAcroForm;
import com.tom_roush.pdfbox.pdmodel.interactive.form.PDCheckbox;
import com.tom_roush.pdfbox.pdmodel.interactive.form.PDField;
import com.tom_roush.pdfbox.pdmodel.interactive.form.PDRadioButton;
import com.tom_roush.pdfbox.pdmodel.interactive.form.PDTextField;


public class Page {
    private View view;
    private Context context;
    private PDDocument file;

    public Page(Context _context, View _view, PDDocument _document) {
        view = _view;
        context = _context;
        document = _document;

        renderFields();
    }

    private void renderFields() {
        PDDocumentCatalog docCatalog = document.getDocumentCatalog();
        RelativeLayout ll = view.findViewById(R.id.pageFields);
        ll.removeAllViews();

        PDPageTree pageTree = docCatalog.getPages();
        PDPage page = pageTree.get(pageIndex);

        PDAcroForm acroForm = docCatalog.getAcroForm();
        List<PDField> fields = acroForm.getFields();

        for (PDField field : fields) {
            String fieldName = field.getFullyQualifiedName();
            COSDictionary fieldDict = field.getCOSObject();

            COSArray fieldAreaArray = (COSArray) fieldDict.getDictionaryObject(COSName.RECT);
            PDRectangle mediaBox = new PDRectangle(fieldAreaArray);

            int fieldColor = Color.argb(180, 220, 228, 254);
            // Factory
            View fieldView = FieldFactory.getViewFromPDField(context, field);
            RelativeLayout.LayoutParams fieldLayoutParams = new RelativeLayout.LayoutParams(
                (int) (mediaBox.getWidth() * posRatio),
                (int) (mediaBox.getHeight() * posRatio)
            );
            fieldLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
            fieldLayoutParams.leftMargin = (int) (left * posRatio);
            fieldLayoutParams.topMargin = (int) (top * posRatio);
            fieldView.setBackgroundColor(fieldColor);
            fieldView.setLayoutParams(fieldLayoutParams);

            ll.addView(fieldView, fieldLayoutParams);
        }
    }
}

...

public class FieldFactory {
    public static View getViewFromPDField(Context context, PDTextField field) {
        return new EditText(context);
    }

    public static View getViewFromPDField(Context context, PDCheckbox field) {
        return new CheckBox(context);
    }

    public static View getViewFromPDField(Context context, PDRadioButton field) {
        return new RadioButton(context);
    }
}

3 Answers3

1

Instead make SuperClass abstract and add a new instance method that does what you are looking for, with each subclass overriding it with its different behavior:

public abstract class SuperClass {
    public abstract View getView(Context context);
}
public class SubClass1 extends SuperClass {
    @Override
    public View getView(Context context) {
        ...
    }
}
Vitruvie
  • 2,327
  • 18
  • 25
1

Unfortunately there is no clean way to do that because during compile time we do not know which method to invoke. To do that you can use instanceof keyword or use reflection.

Assuming that your model looks more or less like below:

class SuperClass {
    //...
}

class SubClass1 extends SuperClass {
    //...
}

class SubClass2 extends SuperClass {
    //...
}

class Context {
    //...
}

abstract class View {
    private final Context context;

    public View(Context context) {
        this.context = context;
    }

    @Override
    public String toString() {
        return getClass().getSimpleName();
    }
}

class TextBox extends View {

    public TextBox(Context context) {
        super(context);
    }
}

class CheckBox extends View {

    public CheckBox(Context context) {
        super(context);
    }
}

You can implement factory like below:

class FieldFactory {
    static Map<Class<? extends SuperClass>, Class<? extends View>> fieldEditorMap = new HashMap<>();

    static {
        fieldEditorMap.put(SubClass1.class, TextBox.class);
        fieldEditorMap.put(SubClass2.class, CheckBox.class);
    }

    public static View getViewFromPDField(Context context, SuperClass field) {
        Class<? extends View> editorClass = fieldEditorMap.get(field.getClass());
        try {
            return editorClass.getConstructor(Context.class).newInstance(context);
        } catch (Exception e) {
            throw new IllegalArgumentException("Can not create view for " + field.getClass().getSimpleName(), e);
        }
    }
}

Example usage:

public class Main {

    public static void main(String[] args) throws Exception {
        List<SuperClass> fields = Arrays.asList(new SubClass1(), new SubClass2(), new SubClass1());
        for (SuperClass field : fields) {
            System.out.println(FieldFactory.getViewFromPDField(new Context(), field));
        }
    }
}

Above prints:

TextBox
CheckBox
TextBox

Of course this is just an example, you should implement it better using some Reflection libraries or utils. We assume that each View implementation has given constructor, etc.

Michał Ziober
  • 37,175
  • 18
  • 99
  • 146
1

I liked Michał Ziober's solution, and would like to expend it using java generics.

The reasoning behind using generics is to have a single, versatile implementation which can later be used for a multitude of tasks.

I've revised the original solution into a generic one. The Factory must now be instantiated, since it is generic and instantiation provides us with a specific factory for specific classes of our choice.

I've used my online java compiler of choice in order to compile and run the following code:

import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;

public class MyClass {

    static class SuperClass {
        //tagging
    }

    static class SubClass1 extends SuperClass {
        //
    }

    static class SubClass2 extends SuperClass {
        //...
    }

    static class Context {
        //...
    }

    static abstract class View {
        private final Context context;

        public View(Context context) {
            this.context = context;
        }

        @Override
        public String toString() {
            return getClass().getSimpleName();
        }
    }

    static class TextBox extends View {

        public TextBox(Context context) {
            super(context);
        }
    }

    static class CheckBox extends View {

        public CheckBox(Context context) {
            super(context);
        }
    }
    static class Pair<P0,P1> {
        public P0 p0;
        public P1 p1;
        public Pair(P0 p0, P1 p1) {
            this.p0 = p0;
            this.p1 = p1;
        }
    }
    static class FieldFactory<T extends SuperClass, P extends View> {
        Map<Class<T>, Class<P>> fieldEditorMap = new HashMap<>();

        public FieldFactory(List<Pair<Class<T>, Class<P>>> boundClassMapping){

            boundClassMapping.stream().forEach(pair -> fieldEditorMap.put(pair.p0,pair.p1));
        }

        public P getViewFromPDField(Context context, T field) {
            Class<P> editorClass = fieldEditorMap.get(field.getClass());
            try {
                return editorClass.getConstructor(Context.class).newInstance(context);
            } catch (Exception e) {
                throw new IllegalArgumentException("Can not create view for " + field.getClass().getSimpleName(), e);
            }
        }
    }

    public static void main(String args[]) {
        List<Pair<Class<? extends SuperClass>, Class<? extends View>>> mapping = new ArrayList<>();
        mapping.add(new Pair<Class<? extends SuperClass>, Class<? extends View>>(SubClass1.class, TextBox.class));
        mapping.add(new Pair<Class<? extends SuperClass>, Class<? extends View>>(SubClass2.class, CheckBox.class));
        FieldFactory ff = new FieldFactory(mapping);
        List<SuperClass> fields = Arrays.asList(new SubClass1(), new SubClass2(), new SubClass1());
        for (SuperClass field : fields) {
            System.out.println(ff.getViewFromPDField(new Context(), field));
        }
    }
}

As for the outputs :

TextBox
CheckBox
TextBox
Rann Lifshitz
  • 4,040
  • 4
  • 22
  • 42