2015-06-30 2 views
0

У меня есть несколько фасолей java (которые имеют атрибуты private и методы getter/setter). И мог бы построить экземпляры с Map<String, Object>, как это в конструкторе:java статический завод в суперклассе

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() 
     ... 

} 

Но когда есть некоторые дети классов, которые расширяют класс A, должна быть соответствующим конструктором каждый раз, когда я пишу новый подкласс:

public class B extends A { 
    public B(Map<String, Object> m){ 
     super(m); 
    } 
} 

Я нахожу это раздражает, и я хочу, чтобы построить статический фабричный метод родительского класса A сделать это один раз для всех (Может быть, какой-то код, как это в классе A?):

public static fromMap(Map<String, Object>){ 
     return new // I really don't have a clue how to write this. 
    } 

И мог ли кто-нибудь дать мне подсказку о том, как написать этот заводский метод? Или это возможно? Может быть, какой-то общий трюк?

+0

как вы предполагаете знать, какие подклассы будут или какие дополнительные функции у них будут? – Stultuske

+0

Является ли полное имя класса в «классе» на карте? – Bohemian

+0

@Stultuske Так как я использую рефлекс для инициализации класса, без какого-либо определенного метода, определенного подклассом, я думаю * возможно * информация о классе может откладываться на время выполнения, чтобы получить? – armnotstrong

ответ

2

Подход, который вы хотите использовать, не самый лучший. Действительно, если вы поместите заводский метод в суперкласс, тогда он должен будет знать, какие его подклассы. Таким образом, этот подход нарушает принцип абстракции . Например, если вы даете базовому классу ответственность за создание его подклассов, вам придется менять его каждый раз, когда вы добавите новый подтип. Более того, это нарушает принцип единственной ответственности .

Вы можете использовать заводской метод, но вы должны извлечь его и поместить в выделенный класс.

Необходимость вызова конструктора суперкласса внутри подкласса является гарантией того, что подклассы являются уточнением суперкласса.

Наконец, чтобы иметь более низкий уровень связи между классами вашей иерархии, я предлагаю использовать наследование только абстрактного типа, например, abstract классов или interface.

+0

Невозможно или не рекомендуется? Я думаю, что я получу достаточно информации в течение времени о подклассе. – armnotstrong

+0

Ответ отредактирован. Я добавляю, что подклассы * должны вызывать * конструктор суперкласса. Это спецификация Java заставляет это поведение. –

0

Чтобы ответить на ваш вопрос, и предполагая, что полное имя класса в записи «класс» на карте:

public static Object fromMap(Map<String, Object> map) throes Exception 
    return Class.forName((String)map.get("class")).getConstructor(Map.class).newInstance(map); 
} 
+0

Имхо, ты не отвечаешь на вопрос. Каждый подкласс должен вызывать конструктор суперкласса, используя предложенный вами подход. –

0

Я предпочитаю использовать вспомогательный класс, что-то вроде следующего:

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; 
    } 
} 

Использование немного неудобно, что может быть уделено немного больше работы.

0

Как описано в других ответах, суперкласс не может знать тип подкласса, если он не передан во время выполнения.

Если проблема вы пытаетесь избежать, чтобы иметь, чтобы создать карту-конструктор в каждом подклассе, я вижу только два решения, каждый со своими недостатками:

1) не используйте конструктор без аргументов и init() метод взятия карты:

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); 
} 

недостатком этого является то, что животные больше не неизменны, если не применять проверку в init() таких как флаг boolean isInitialized, но это все-таки вроде некрасиво.Вы также не получите возвращаемый тип из init, поэтому вы не можете делать то, что вы могли бы сделать с помощью прямого конструктора + инициализатора, такого как new Dog(map).bark(). Эту проблему можно решить, сделав супер-класса родовое, принимая конкретный суб-класс в качестве параметра:

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(); 
} 

Тем не менее, этот подход страдает от API, имеющего смысл быть неполным, так как можно построить объекты которые не имеют карты, потому что init() называется отдельно. Если это проблема для вас, конечно, зависит от того, является ли это опубликованным API для использования третьими лицами или только некоторыми внутренними классами, где вы можете жить с этим шаблоном.

2) Статический метод завод принимает Class<? extends Animal> и опираясь на отражение для создания экземпляра класса:

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(); 
} 

Эта модель опирается на каждого суб-класса, имеющий доступный не-арг конструктор, который также ISN» t очень красиво, если цель состоит в том, чтобы скрыть построение класса и избежать возможности создания не полностью инициализированных объектов. Синтаксис также является немного подробным, и он должен передать объект Class.

Если все это действительно стоит только, чтобы избежать того, чтобы генерировать конструктор призывающих супер на каждый суб-классе (что любой приличный IDE сможет сделать автоматически) весьма спорен ...

Смежные вопросы