1
\$\begingroup\$

In order to do its work, a DataProcessor needs Data and otherData.

One instance of Data will be shared over a set of DataProcessor, but each DataProcessor has its own otherData.

For sharing Data instances, fields of Data could not be defined inside DataProcessor, thus, there are two hierarchies: Data and DataProcessor.

Each subClass of DataProcessor relates a subClass of Data.

And I have tried three design to fit the requirement, which way is the best? why?

Is my design bad? Why?

Is there any other solution?

Is there any existing design pattern to solve this kind of problem?

It is said that this is a "parallel inheritance hierarchy". Should I avoid this? How? Or is it a good design for my requirement, and do I not need to avoid?

Data hierarchies

abstract class Data {
    int field1;
}

abstract class AData extends Data {
    int field2;
}

class AAData extends AData {
    int field3;
}

class BData extends Data {
    int field4;
}

DataProcessor hierarchies way 1, each super class instance keeps a private reference to Data

abstract class DataProcessor {
    private Data data;
    protected Object otherData;
    DataProcessor(Data data){
        this.data = data;
    }
    void doSomethingUsingDataAndOtherData(){}
}

abstract class ADataProcessor extends DataProcessor {
    private AData data;
    ADataProcessor(AData data) {
        super(data);
        this.data = data;
    }
    void doSomethingUsingADataAndOtherData(){}
}

class AADataProcessor extends ADataProcessor {
    private AAData data;
    AADataProcessor(AAData data) {
        super(data);
        this.data = data;
    }
    void doSomethingUsingAADataAndOtherData(){}
}

class BDataProcessor extends DataProcessor {
    private BData data;
    BDataProcessor(BData data) {
        super(data);
        this.data = data;
    }
    void doSomethingUsingBDataAndOtherData(){}
}

DataProcessor hierarchies way 2, only top superclass instance keeps a protected reference to Data, when subclass needs to use the data, it has to cast the data to its expected Data subclass. And I don't want to define DataProcessor as DataProcessor, because once introducing generic types, I have to use generic types everywhere, and sometimes using generic types is not so simple (i.e. we can not create a generic array in java).

abstract class DataProcessor {
    protected Data data;
    protected Object otherData;
    DataProcessor(Data data) {
        this.data = data;
    }
    void doSomethingUsingDataAndOtherData(){}
}

abstract class ADataProcessor extends DataProcessor {
    ADataProcessor(AData data) {
        super(data);
    }
    void doSomethingUsingADataAndOtherData(){}
}

class AADataProcessor extends ADataProcessor {
    AADataProcessor(AAData data) {
        super(data);
    }
    void doSomethingUsingAADataAndOtherData(){}
}

class BDataProcessor extends DataProcessor {
    BDataProcessor(BData data) {
        super(data);
    }
    void doSomethingUsingBDataAndOtherData(){}
}

DataProcessor hierarchies way3, only bottom sub class instance keep a priavte reference to Data, super class use a abstract method getData() to retrieve the Data

abstract class DataProcessor {
    protected Object otherData;
    protected abstract Data getData();
    void doSomethingUsingDataAndOtherData(){}
}

abstract class ADataProcessor extends DataProcessor {
    protected abstract AData getData();
    void doSomethingUsingADataAndOtherData(){}
}

class AADataProcessor extends ADataProcessor {
    private AAData data;
    protected AAData getData() {
        return data;
    }
    AADataProcessor(AAData data) {
        this.data = data;
    }
    void doSomethingUsingAADataAndOtherData(){}
}

class BDataProcessor extends DataProcessor {
    private BData data;
    protected BData getData() {
        return data;
    }
    BDataProcessor(BData data) {
        this.data = data;
    }
    void doSomethingUsingBDataAndOtherData(){}
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Well, given the examples, i'd avoid inheritance and implement an interface for the DataProcessor.

package net.icodeapps.examples.ooad;

public interface DataProcessor<T, OtherData> {

    T processData();
}

This could be implement by any class like

package net.icodeapps.examples.ooad;

public class ADataProcessor implements DataProcessor<AData, OtherData> {

    private AData aData;

    private OtherData otherData;

    public ADataProcessor(AData aData, OtherData otherData) {
        this.aData = aData;
        this.otherData = otherData;
    }

    @Override
    public AData processData() {
        return aData;
    }
}

Having a second class implementing the interface

package net.icodeapps.examples.ooad;

public class BDataProcessor implements DataProcessor<BData> {

    private BData aData;

    private OtherData otherData;

    public BDataProcessor(BData aData, OtherData otherData) {
        this.aData = aData;
        this.otherData = otherData;
    }

    @Override
    public BData processData() {
        return aData;
    }
}

With more information, still would skip the abstract class. The reason you use it is providing a contract and avoid code replication. But since the code you try to avoid replicating is the problem itself, i'd skip that and just provide private fields to store references. That way you have the right type in processData.

Keeping the processors stateless and just store the data in a queue could be a better approach though. Depending on the data, you would create a processor on demand. The could be implemented via the command processor pattern. Using that pattern, more complexity would be introduced though.

\$\endgroup\$
4
  • \$\begingroup\$ DataProcessor need not only Data but also otherData. in fact the DataProcessor is a Task, and need to be queued, and later to be executed one by one by a thread, so i must put Data inside DataProcessor. \$\endgroup\$
    – autumn
    Jun 9, 2012 at 19:15
  • \$\begingroup\$ I changed the code because know i have more insight. \$\endgroup\$ Jun 10, 2012 at 8:36
  • \$\begingroup\$ it looks like way3 - only bottom sub class instance keep a priavte reference to Data. and there is another point need to be consider: in the top super class - DataProcessor, i would implement some common logic, so i use abstract class instead of interface. if not for the common logic, i event would not introduce a abstract base class(or base interface). ADataProcessor also has a sub class AADataProcessor. but that is not the key point, the key point is the way of handling Data instance. \$\endgroup\$
    – autumn
    Jun 10, 2012 at 12:06
  • \$\begingroup\$ You could introduce a stateless abstract super class, which contains the common logic. But you can avoid the accessors and fields, since they don't make any sense in the super class. You want to use covariant fields anyway. Extracting that common logic in a strategy would even make it testable and you could avoid the inheritance chain in the processors. \$\endgroup\$ Jun 10, 2012 at 12:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.