Question

First of all this question will be kind of long but in order to explain my problem in full i feel that i have to give you alot of information on my project so please bear with me!

I am working for a company that uses charts alot so to avoid the trouble of always have to create charts from scats i have decided to create a new java project and create a "package" that me and my co-workers can use to create these charts. You could call them Generic.

So this project uses a builder pattern and alot of interfaces and abstract classes. The idea is that that the client (developer) uses these interfaces, patterns and classes and override the methods to fit the flow. The only thing that would remain for the client (developer) would be to fill these methods and create to UI him self adn the publish the program.

The program is shaping up nicely and i have created alot of features that i am very proud of (since im a student) i thought that i have planned the whole process well and nice but i ran into a few problems!

First of all let me show you the classes and explain the dataflow (il do this as short as possible):

First off is the GUI (This is a class that the user will always have to create him self but he can use the build in package to create his chart by using the following code):

ChartBuilder cb = new LineChartBuilder();
                  Director d = new Director();
d.buildTypeOne(cb, "Hello", PeriodSelection.HOUR,"");

With this the director is now ready to build a chart.

The PeroidSelection.Hour is an enum that sets a standard time in this case it sets the charts category axis to our opening ours and therefore data that is collected know that it has to get data on an hourly basis (from 8.00 - 19.00 in this case) The reason why this is an Enum is that these type of periods are kinda final only thing that can change is our opening days and hours which we will then be able to change quite easily! here is a preview of the PeriodSelection enum:

    public enum PeriodSelection{
    HOUR(new String[]{"8:00", "9:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00", "17:00", "18:00", "19:00"}),
    MONTH(new String[]{"Jan", "Feb", "Marts", "April", "Maj", "Juni", "Juli", "Agu", "Sep", "Oct", "Nov", "Dec"}),
    DAYS(new String[]{"Mandag", "Tirsdag", "Onsdag","Torsdag","Fredag","Lørdag","Søndag"});

    private String[] timeIntervals;
    private PeriodSelection(String[] timeIntervals){
        this.timeIntervals = timeIntervals;
    }
    public String[] getTimeIntervals(){
        return timeIntervals;
    }
}

Going into the Director the director is now ready to build the chart but first it has to collect data from the database:

    public void buildTypeOne(ChartBuilder builder, String title, PeriodSelection selection, String queueName){
    try {
        builder.setObjectList(stat.getData(queueName, start, end));
    } catch (DopeDBException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    builder.selection = selection;
    builder.initiate(title);
    builder.createSeries();
    builder.createSymbol();

    builder.createTable();
}

as you can see the builder is build in a certain way this is because the chart consists of both a table and a chart and those two has to be linked together, i wont go into much detail on the chart because it has no relevence to my question.

Now the stat class as showed in the first line of the buildTypeOne method extends and abstract class called statisticPattern that looks like this:

public abstract class StatisticPattern {


protected ArrayList<ObjectInterface> cq = new ArrayList<>();
protected ObjectInterface contact;
protected ProviderInterface p;
/**
 * 
 * 
 * {@link Constructor}
 */
public StatisticPattern(){
    try {
        p = new Provider();
    } catch (DopeDBException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
/**
 * 
 * @param name
 * @param start
 * @param end
 * @return
 * @throws SQLException
 * @throws DopeDBException
 */
protected ArrayList<ObjectInterface> getData(String name, DateTime start, DateTime end) throws SQLException, DopeDBException{
    return cq;
}
/**
 * 
 * @param contact2
 */
protected void processSingleQueueData(ObjectInterface contact2) {
}
/**
 * 
 * @param queueName
 * @throws SQLException
 */
protected void obtainNewData(String queueName) throws SQLException {

}

/**
 * 
 * @param name
 * @param start
 * @param end
 * @return
 */
protected boolean doIhaveIt(String name, DateTime start, DateTime end) {
    return false;
}
/**
 * 
 * @param start
 * @param end
 * @return
 */
protected boolean checkDatas(DateTime start, DateTime end) {
    return start.toDateMidnight().isEqual(end.toDateMidnight());
}
/**
 * 
 * @param start
 * @param end
 * @return
 */
protected Integer daysBetween(DateTime start, DateTime end) {
    return  end.dayOfYear().get()-start.dayOfYear().get();

}

As stated eailer the purpose of this class is that our developers will extend the class and override the methods so that the director can find these methods and use them how they choose to implement and fill the methods is different from program to program.

In this program the statistic class looks like this:

public class Statistics extends StatisticPattern {
private DateTime start;
private DateTime end;


/**
 *  This class checks whether the program has already collected the data
 * @Override
 */
public ArrayList<ObjectInterface> getData(String name, DateTime start, DateTime end) throws DopeDBException{
    if (this.start.equals(start) && this.end.equals(end)) {
        if (name.equalsIgnoreCase("All")) {
            return cq;
        }else if (doIhaveIt(name, start, end)) {
            return cq;
        }
    }else {
        try {
            obtainNewData(name);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    return cq;
}
@Override
protected void obtainNewData(String queueName) throws SQLException {
    setDates(start, end);
    this.cq = p.obtainData(start, end, queueName);

}

As you can see i have created my own implementation of the getData method this method calls the obtainNewData method that then obtains data from p (which our provider class - also known as the connection to the database).

The provider class as you might have guessed also implements and interface where there is only one method in the obtainData method

public interface ProviderInterface {

public ArrayList<ObjectInterface> obtainData(DateTime start, DateTime end, String name) throws SQLException;

}

Again the developer has to implement this method but can fill it as he likes. The most important thing here is that the return type is an ArrayList of the type ObjectInterface:

public interface ObjectInterface {

HashMap<String, Integer> data = new HashMap<String, Integer>();
String type = "";

public String getType();
public void addData(String key, Integer value);
public Integer getData(Object key);

}

Basicly it has to implement a hashmap that contains all the data that the chart should be filled with. This data will be added when the object is created and then added to the list in the provider, this list will then be returned and the Director will set that to the chartbuilders list of data that will then fill the charts.

in this case the final object that has to fill the charts looks like this:

public class ContactQueue implements ObjectInterface {

    private HashMap<String, Integer> data = new HashMap<String, Integer>();
    private String type;
    public ContactQueue(String type){
        this.type = type;
    }
    public String getType(){
        return type;
    }
    public void addData(String key, Integer value){
        if (data.containsKey(key)) {
            Integer i = data.get(key);
            data.put(key, value+i);
        }else {
            data.put(key, value);
        }

    }
    public Integer getData(Object key){
        return data.get(key);
    }
    public String toString(){
        return type;
    }
}

Now to the question!

All of this works perfectly when there is only 1 type of data to be collected. But the program that i am currently working on has to get data from 5 different tables all of which has to be added on to each of their chart my problem is how would i design it so that it picks database table? and returns a list for that specefic table?

I have already tried with the following code:

        if (name.equalsIgnoreCase("Besvarelse")) {
        return besvarelse25(start, end);
    }else if (name.equalsIgnoreCase("intern")) {
        return intern(start, end);
    }else if (name.equalsIgnoreCase("besvarelseProcent")) {
        return besvarelseProcent(start,end);
    }else if (name.equalsIgnoreCase("Email_data")) {
        return email_data(start, end);
    }else if (name.equalsIgnoreCase("Email_Hånd")) {
        return email_haand(start, end);
    }else if (name.equalsIgnoreCase("Email_Antal")) {
        return email_antal(start, end);
    }
    else if (name.equalsIgnoreCase("Henvendelser")) {
        return henvendelser(start, end);
    }

However that seems kinda redudant and well ugly to say the least.

i have thought about creating an Enum again that the client (developer) had to change each time he wanted to create a new program but i am unsure that this is the right way to do it?

Also i would love to hear what you think about the overall project? have i nailed it or failed it?

Thank you for reading and i will be looking forward to read your responses

Was it helpful?

Solution

Opinion: because you appear to be filtering at the end based on the backing database the solution is simple/elegant. Yes it is redundant and ugly, but so is a schema that has 5 tables of similar information (if I read this right). If it works I don't think you have a problem.

If your concern is that you need to pick the "right type" of table you might be able to find the generic quality of each one:

Something like->

  SELECT 
    generic.specificField AS "genericKey",
    ....
  FROM
    TableOne generic

You can then create a HashMap (or object, your call) that would satisfy some level of consistency for your design.

Again something like ->

  for(Field f : resultSet.values())//Pseudo Code, obviously javax.sql is a bit more complex
    hashMap.put(f.getName(), f.getValue());
...
  return genericProcessing(hashMap, start, end);

Because you would know the keys/methods (as defined by your SQL aliases) you have an easy workaround. However, if this is a public API something more "strict" (interface) is probably a better bet.

If your data is unrelated, then I'd say your design reflects the data. There's nothing wrong with that.

(Second opinion, the factory pattern as suggested above is just another form of abstraction. A lot of people in the Java ecosystem go right for it, and a lot detest it--I'm in the second camp here). Generally speaking I'd try to abstract your problem first, and if you can't abstract it it is probably simple enough. In your case I think you can probably abstract it with a method/query. If you can't then jumping to a type (factories are a way to do this) is probably your best answer.

OTHER TIPS

To be honest I did not read the OP fully and went straight to the question. Apologies...
Any time you have code like this:

if (name.equalsIgnoreCase("Besvarelse")) {
        return besvarelse25(start, end);
    }else if (name.equalsIgnoreCase("intern")) {
        return intern(start, end);
    }else if (name.equalsIgnoreCase("besvarelseProcent")) {
        return besvarelseProcent(start,end);
    }else if (name.equalsIgnoreCase("Email_data")) {
        return email_data(start, end);
    }else if (name.equalsIgnoreCase("Email_Hånd")) {
        return email_haand(start, end);
    }else if (name.equalsIgnoreCase("Email_Antal")) {
        return email_antal(start, end);
    }  

consider using the Factory Pattern. Factory pattern is the standard way to go for this case (read also on Abstract Factory)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top