Reputation: 231
I'm learning java design patterns and I wonder if I can apply some with following problem. I have class Solider and some child classes, for example: General and Sergeant. I'm creating Solider object and in runtime I want to change this object to General or Sergeant object, or create new Sergeant or General object using created earlier Solider object:
Solider s = new Solider(...);
.....
if (generalCondition) {
General g = createGeneralFromSolider(s);
//or better:
//General g = promoteSoliderToGeneral(s);
} else if (sergeantCondition) {
Sergeant sr = createSergeantFromSolider(s);
//or better:
//Sergeant sr = promoteSoliderToSergeant(s);
}
Firstly I decided to create additional constructor in General/Sergeant Class:
Class General extends Solider {
General(Solider s, Map<String, String> generalSpecificParams) {
//first we are going to copy all solider params to general params (bad idea if we have a lot of params)
this.setParamX(s.getParamX());
....
//then we can assign the rest of general-specific params
this.setGeneralSpecificParams(generalSpecificParams);
}
}
and use it in methods createGeneralFromSolider but I'm not sure if it is elegant way. Main disadvantage is that I create new object, so after calling createGeneralFromSolider I have 2 object in memory. I would rather have one object in memory: General/Sergeant promoted from Solider (object General/Sergeant which earlier was the Solider object). I wonder if I can use some design patter to resolve it. I remember that in C++ there has been something like copying constructors which copying all params from one object to another by assigning all params, one after another. In Java I didn't hear about anything similar.
Upvotes: 0
Views: 4534
Reputation: 2180
What you want could be achieved using Reflections.
That way you can automatically copy fields from the instance of parent to child class.
Your code would look something like this:
public static void copyObject(Object src, Object dest)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, SecurityException {
for (Field field : src.getClass().getFields()) {
dest.getClass().getField(field.getName()).set(dest, field.get(src));
}
}
public static General createGeneral (Solider solider, String devision) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
General general = new General();
copyObject(solider, general);
general.setDevision(devision);
return general;
}
The Field import is java.lang.reflect.Field;
========================================================================
Another way would be to use the Apache Bean Utils.
Than, you can use it's cloneBean(Object Bean) method like this:
General general = cloneBean(solider);
to copy the fields from solider to general and after that all the fields that are specific to child class (General).
========================================================================
EDIT: It would also be wise to introduce another child class that would be used for "ordinary" soliders if you intended to use the parent class Solider for "ordinary" soliders (which I suppose you do according to your commented method name promoteSoliderToGeneral(Solider s).
So, for example you would have a parent class called MilitaryMan and 3 child classes that extend it: Solider, General and Sergeant.
This way, you can uniformly handle all of the MilitaryMan. And, you can check if the MilitaryMan is a Solider, General or Sergeant with:
if (militaryMan instanceOf Solider) {
// do solider specific processing
...
} else if (militaryMan instanceof General) {
...
} else if (militaryMan instanceof Sergeant) {
...
}
I think it would be cleaner this way.
Upvotes: 1
Reputation: 21
First of all, when constructing child classes, use super
as the first statement of the constructor like so:
class Soldier {
private String rank; // e.g. Pvt, PFC, etc.
private int yearsOfService;
// ... (Standard constructor)
public Soldier(Soldier s) {
this.rank = s.rank; this.yearsOfService = s.yearsOfService;
}
// ... (Getters and Setters)
}
class Sergeant extends Soldier {
private int subordinates;
public Sergeant(Soldier s) {
super(s)
this.rank = "Sergeant"; // overwrites this Sergeant's rank
this.subordinates = 0;
}
}
You could easily encapsulate this in a promoteSoldierToSergeant
method. However, this can lead to telescoping constructors if classes with many attributes are designed naively, or necessitate your map-based workaround. To resolve this, I'm personally a big fan of the Builder pattern, but you can also consider the Factory pattern.
Your question regarding "copying constructors" is perhaps best addressed by reading up on the Clonable
interface, but be aware of the differences between shallow and deep copies, and the implications for your classes and data structures.
Upvotes: 2
Reputation: 817
You would probably need to use a Factory pattern for this kind of situation. For example:
public class SoldierFactory {
//use getSoldier method to get object of type Soldier
public Soldier getSoldier(String soldierType){
if(soldierType == null){
return null;
}
if(soldierType.equals("case1")){
return new General();
} else if(soldierType.equals("case2")){
return new Sergeant();
} else if(.....
}
return null;
}
}
public class FactoryPatternDemo {
public static void main(String[] args) {
SoldierFactory soldierFactory = new SoldierFactory();
Soldier s1 = soldierFactory.getsoldier("case1");
}
}
I think its better to not create the Soldier ahead of calling Soldier factory. You're going to change it regardless during run-time right?
Upvotes: 2
Reputation: 2404
I think your approach is totally acceptable. If you have an object X, that you want to turn into Y, you can do it in Y constructor, copying all necessary fields.
You could as well use a builder, or static factory methods, but either way you'll have to copy fields, as there's no automatic copy constructor in java (except if you use some dedicated library such as lombok, which can provide full-args constructors from annotations)
You worry about having 2 objects in memory. But if you remove every reference of the original Soldier, the garbage collector will destroy it.
Last thing, as mentionned by @tsolakp , is it a good design to have General inheriting from Soldier? Couldn't it be just a "grade" variable, or something like that, to reflect this state? It's a common mistake to overuse inheritance where composition would be sufficient, and would cause less troubles.
Upvotes: 1