Reputation: 9065
I have some java beans(which have private
attributes and getter/setter
methods). And could build instances with a Map<String, Object>
like this in the constructor:
public class A {
private String name;
private String gender;
...
public A(Map<String, Object> m){
BeanInfo beanInfo = Introspector.getBeanInfo(this.getClass());
for(PropertyDescriptor pd: beanInfo.getPropertyDescriptors()){
if(pd.getName().equals("class")){
continue;
}
Method setter = pd.getWriteMethod();
if(setter != null && m.get(pd.getName())!=null){
setter.invoke(this,m.get(pd.getName()));
}
}
}
getter()/setter()
...
}
But when there are some children classes that extend class A
, there should be a corresponding constructor every time I write a new subclass:
public class B extends A {
public B(Map<String, Object> m){
super(m);
}
}
I find this annoying and I want to build a static factory method in the parent class A
to do this once for all (Maybe some code like this in class A
?):
public static fromMap(Map<String, Object>){
return new // I really don't have a clue how to write this.
}
And could someone give me a hint on how to write this factory method? Or is that possible? Maybe some generic trick?
Upvotes: 0
Views: 2040
Reputation: 9315
As other answers describe, the super-class cannot know the type of the sub-class unless it's passed in runtime.
If the issue you're trying to avoid is to have to create the map-constructor in each sub-class, I see only two solutions, each with its flaws:
1) Use no-arg constructor and an init()
method taking the Map:
public class Animal {
public void init(Map<String, Object> map) {
...
}
}
public class Dog extends Animal {
public void bark() { ... }
}
static void test {
Dog dog = new Dog().init(map);
}
The downside of this is that Animals are no longer immutable, unless you enforce a check in init()
such as a flag boolean isInitialized
, but it's still kind of ugly. You also don't get the return type back from init, so you can't do things you would be able to do with a direct constructor+initializer such as new Dog(map).bark()
. This can be addressed by making the super-class generic, taking the concrete sub-class as the parameter:
public class Animal<A extends Animal> {
public A init(Map<String, Object> map) {
...
}
}
public class Dog extends Animal<Dog> {
public void bark() { ... }
}
static void test {
new Dog().init(map).bark();
}
Still, this approach suffers from the API having a sense of being incomplete, since it's possible to construct objects that don't have a map because init()
is called separately. If that's an issue for you of course depends on if this is a published API for third parties to use or just some internal classes where you can live with this pattern.
2) A static factory method taking a Class<? extends Animal>
and relying on reflection to create the class instance:
public class Animal {
public static class Factory {
public static <A extends Animal> A create(Class<A> animalClass, Map<String, Object> map) {
try {
A a = animalClass.newInstance();
a.init(map);
return a;
} catch (InstantiationException e) {
} catch (IllegalAccessException e) {
}
return null;
}
}
void init(Map<String, Object> map) {
// ...
}
}
public class Dog extends Animal {
public void bark() { ... }
}
static void test() {
Animal.Factory.create(Dog.class, map).bark();
}
This pattern relies on each sub-class having an accessible no-args constructor, which also isn't very pretty if the purpose is to hide class construction and avoid being able to construct incompletely initialized objects. The syntax is also a bit verbose, having to pass the Class
object.
If all of this is really worthwhile just to avoid having to generate constructors calling super on each sub-class (which any decent IDE would be able to do automatically) is quite debatable...
Upvotes: 0
Reputation: 5463
I prefer using a helper class, something like the following:
package empty;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class FromMap {
public static class First {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override public String toString() {
return "First [name=" + name + ", age=" + age + "]";
}
}
public static void main(String[] args) {
Map<String, Object> v = new HashMap<String, Object>();
v.put("name", "My Name");
v.put("age", 100);
try {
First f = FromMap.fromMap(v, new First());
System.out.println(f);
} catch (Exception e) {
e.printStackTrace();
}
}
public static <T> T fromMap(Map<String, Object> m, T t) throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException, IntrospectionException {
BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass());
for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
if (pd.getName().equals("class")) {
continue;
}
Method setter = pd.getWriteMethod();
if (setter != null && m.get(pd.getName()) != null) {
setter.invoke(t, m.get(pd.getName()));
}
}
return t;
}
}
The usage is a bit awkward that can be taken care by a bit more work.
Upvotes: 0
Reputation: 425208
To answer your question, and assuming the fully qualified class name in the "class" entry of the map:
public static Object fromMap(Map<String, Object> map) throes Exception
return Class.forName((String)map.get("class")).getConstructor(Map.class).newInstance(map);
}
Upvotes: 0
Reputation: 8353
The approach you want to use is not the best one. Indeed, if you put the factory method inside the super class, then it will have to know which are its subclasses. So, this approach breaks the abstraction principle. For example, if you give to the base class the responsibility to build it's subclasses, you will have to change it every time you will add a new subtype. Moreover, this violates the Single Responsiblity Principle.
You can use a factory method, but you have to extract it and put to a dedicated class.
Having to call a constructor of the super class inside a subclass is a garantee of the fact that the subclasses are a refinement of the super class.
Finally, to have a lower level of coupling between the classes of your hierarchy, I suggest you to use inheritance only from an abstract type, such as abstract
classes or interface
s.
Upvotes: 3