Reputation: 31
Is it possible to mix composition and inheritance in Java? Fist I have some generic classes is a HAS-A (or HAS-MANY) relationship (Composition).
Structure, TypeA, TypeB, TypeC, etc., where Structure is in a HAS-A relationship with TypeA, TypeB and TypeC.
But then I also have several sets of subclasses which inherit from the generic classes (Inheritance) and are in the same relationship.
Structure1, TypeA1, TypeB1, TypeC1, etc., where Structure1 is in the same HAS-A relationship with TypeA1, TypeB1 and TypeC1, just like the generic classes.
And: Structure1 extends Structure, TypeA1 extends TypeA, ..., TypeC1 extends TypeC, etc.
StructureX, TypeAX, TypeBX, TypeCX, etc.
Each special structure has exactly ONE specific sub-TypeA, sub-TypeB and sub-TypeC, etc. The generic classes define some code (attributes and methods) which I would like ot reuse when dealing with special structures. The problems I am facing are best explained by the code below. (I do not know if this can somehow be solved by Java "generics" but I think not.)
/***********************************************************************************************
/ Generic Structure of Structure-Class with Instances of Generic other classes (Composition)
/***********************************************************************************************/
class Structure {
// Substructures
TypeA instanceA;
TypeB instanceB;
// Defining many methods using the instances of other generic classes like TypeA
void setGenericAttributeOfA(int value) {
instanceA.genericAttribute = value;
}
void setGenericAttributeOfB(int value) {
instanceB.genericAttribute = value;
}
}
class TypeA {
int genericAttribute;
}
class TypeB {
int genericAttribute;
}
/***********************************************************************************************
/ Specific implementation of a Structure-Class with specific implementation of the other classes (Inheritance)
/***********************************************************************************************/
// In the specific implementations I want to use the generic methods, because I do not want to
// rewrite the code for each and every specific implementation. But they should
class Structure1 extends Structure {
// This will create an additional attribute instanceA of specific TypeA1, so I will end up with two instances:
// (1) TypeA super.instanceA and (2) TypeA1 this.instanceA. But what I would like is to have only
// one instanceA of type TypeA1 that can also be used by the global methods of the generic Structure.
TypeA1 instanceA;
Structure1() {
// This creates an instance of type TypeA1, but it cannot be used in the generic methods
// because it is hold in a separate "local" variable that is not known to the generic Structure methods
instanceA = new TypeA1();
// This creates an instance of type TypeB1, but it cannot access the "local" specific attributes,
// because it is hold in a varable which is statically types as TypeB
instanceB = new TypeB1();
}
void specificMethod() {
setGenericAttributeOfA(42); // would fail, because instanceA of type generic TypeA is null
instanceA.specificAttribute = 13; // works only for "local" specific attributes
setGenericAttributeOfB(42); // works only for generic attributes
instanceB.specificAttribute = 13; // would fail, because instanceB is statically typed as generic TypeB which does not have this attribute
((TypeB1)instanceB).specificAttribute = 13; // works but is an ugly work-around and over-complicated if to be used many times
}
}
class TypeA1 extends TypeA {
int specificAttribute;
}
class TypeB1 extends TypeB {
int specificAttribute;
}
Upvotes: 2
Views: 1255
Reputation: 424
TL;DR Use method forwarding on your types and if that does work because things are too complex then look up SOLID principles and use design patterns to make your types and classes follow that.
Structure
class is trying to serve two purposes. You're trying to make a data object that is responsible for creating and containing objects, while also trying to process the data for the objects. You basically need to pick two design patterns to handle your intention. I would suggest the Bridge and Builder pattern. Make a builder class that builds the Type objects and then make your structural class only have fields of the top class. Then the structural class does not have to access type specific methods or fields. The builder will though, so you need to think about the best SOLID way of making a builder for the different types. You might need a builder for each type or each class depending on how complicated it is but if you have getters and setters that won't be too bad. Also you might want to think about using a factory or abstract factory with the builder so that the factory determines which type (and what class child/parent) the object will be and the builder takes that information to build an object of that specific class with all the specific fields populated.Method forwarding is your friend. You seem to have already had that idea but it wasn't used as much as you could. If you wanted to make general method that sets the values on different objects, than make the classes have overriden/overloaded methods that do the work for you. It's easier to see explain in the example below:
class TypeA {
int genericAttribute;
setAttributes(int genericAttribute){
this.genericAttribute = genericAttribute;
}
}
class TypeB {
int genericAttribute;
setAttributes(int genericAttribute){
this.genericAttribute = genericAttribute;
}
}
class TypeA1 extends TypeA {
int specificAttribute;
setAttributes(int genericAttribute, int specificAttribute){
setAttributes(genericAttribute);
this.specificAttribute = specificAttribute;
}
}
class TypeB1 extends TypeB {
int specificAttribute;
setAttributes(int genericAttribute, int specificAttribute){
setAttributes(genericAttribute);
this.specificAttribute = specificAttribute;
}
}
Continue this pattern and you'll have what you want without even trying.
Upvotes: 1
Reputation: 45309
Your type hierarchy seems unnecessarily complex. It's typically challenging to implement inheritance around data, and this scenario is a typical example.
Points you can consider to redesign your types:
Type
and TypeB
are identical, at least as far as the post is concerned. Unless there's specific behavior to each of these classes, you can make them coalesce:
// Replacement for TypeA and TypeB
class Type {
int genericAttribute;
}
If they have specific behavior, you can make Type
an interface and consider switching to getters and setters instead of a field (which is a better idea any way)
Following that, TypeA1
and TypeB1
can extend the same parent, Type
:
class TypeA1 extends Type {
int specificAttribute;
}
class TypeB1 extends Type {
int specificAttribute;
}
Regarding the Structure
class: you only needed this class because you wanted to implement "generic" behavior related to TypeA
and TypeB
. This distinction shouldn't be needed anymore as these two have been merged into Type
, so you can dispense with this class, to be left with data to deal with. And data can move to Structure1
(there would be no need for a common, separate place for these fields, because Structure1
has both concrete types anyway):
//this doesn't need a parent any more
class Structure1 {
TypeA1 instanceA;
TypeB1 instanceB;
Structure1() {
instanceA = new TypeA1();
instanceB = new TypeB1();
}
//This doesn't have to be implemented like this
//I kept it to show the relation to your original
//design. I would simply add
//setGenericAttribute(int) to Type
void setGenericAttribute(Type type, int value) {
type.genericAttribute = value;
}
void specificMethod() {
setGenericAttribute(instanceA, 42);
instanceA.specificAttribute = 13;
setGenericAttribute(instanceB, 42);
instanceB.specificAttribute = 13;
}
}
Upvotes: 0
Reputation: 270758
Maybe Generics can help?
Declare Structure
as generic:
class Structure<T1 extends TypeA, T2 extends TypeB, T3 extends TypeC> {
T1 instanceA;
T2 instanceB;
T3 instanceC;
}
And your specific structures will just inherit from the generic Structure
by specifying type arguments:
class Structure1 extends Structure<TypeA1, TypeB1, TypeC1> {
// here instanceA will be of type TypeA1, instanceB will be TypeB1 etc.
}
Upvotes: 1