Полиморфная фабрика / getInstance () в Java - PullRequest
11 голосов
/ 25 ноября 2008

Я стремлюсь создать набор объектов, каждый из которых имеет уникальный идентификатор. Если объект с таким идентификатором уже существует, я хочу использовать существующий объект. В противном случае я хочу создать новый. Я пытаюсь не использовать слово Singleton, потому что я знаю, что это грязное слово здесь ...

Я могу использовать фабричный метод:

    // A map of existing nodes, for getInstance.
private static Map<String, MyClass> directory = new HashMap<String, MyClass>();

public static MyClass getInstance(String name) {
    MyClass node = directory.get(name);
    if(node == null) {
       node == new MyClass(name);
    }
    return node;
}

Или, в равной степени, у меня может быть отдельный метод MyClassFactory.

Но я намеревался создать подкласс MyClass:

public class MySubClass extends MyClass;

Если я больше не буду и вызову MySubClass.getInstance ():

MyClass subclassObj = MySubClass.getInstance("new name");

... тогда subclassObj будет простым MyClass, а не MySubClass.

Однако переопределение getInstance () в каждом подклассе кажется хакерским.

Есть ли какое-то аккуратное решение, которое мне не хватает?


Это обобщенная версия вопроса. Больше подробностей, так как ответчики попросили их.

Программа предназначена для генерации ориентированного графа зависимостей между узлами, представляющими части программного обеспечения. Подклассы включают в себя Java-программы, веб-службы, хранимые процедуры SQL, управляемые сообщениями триггеры и т. Д.

Таким образом, каждый класс "is-a" является элементом в этой сети и имеет методы для навигации и изменения отношений зависимости с другими узлами. Разница между подклассами будет заключаться в реализации метода populate(), используемого для настройки объекта из соответствующего источника.

Допустим, узел с именем 'login.java' узнает, что у него есть зависимость от 'checkpasswd.sqlpl':

this.addDependency( NodeFactory.getInstance("checkpasswd.sqlpl"));

Проблема в том, что объект checkpasswd.sqlpl может существовать или не существовать в настоящее время.

Ответы [ 7 ]

5 голосов
/ 25 ноября 2008

Вы смотрели в Guice? Не уверен, что это точно решит вашу проблему, но он действует как универсальный контейнер для фабрики и зависимости и исключает нетипичные безопасные строковые ключи.

4 голосов
/ 25 ноября 2008

Статический метод определен в родительском классе и также вызывается статически. Таким образом, нет способа узнать в методе, который вы вызвали в подклассе. Компилятор java, вероятно, даже разрешает вызов статически для вызова родительского класса.

Таким образом, вам нужно будет либо переопределить статический метод в ваших дочерних классах, как вы предлагаете, либо сделать их не статичными, чтобы вы могли наследовать (в иерархии фабрики объектов , а не классов), или передать параметр для обозначения типа, который вы хотите создать.

Проверьте метод EnumSet.noneOf (). У него такая же проблема, как и у вас, и он решает ее, передавая метод java.lang.Class. Вы можете использовать newInstance в классе. Но лично я бы просто использовал фабричные объекты, а не классы со статическими методами.

2 голосов
/ 27 ноября 2008

после прочтения вами объяснения проблемы, я думаю, что вы делаете это очень трудно для себя, используя подклассы в построении графика. я думаю, что проблема станет намного проще, если вы отделите граф зависимостей от «информации о программе»

используйте интерфейс, такой как:

Public Interface Node<T> {
  public Object<T>    getObject();
  public String       getKey();
  public List<String> getDependencies();
  public void         addDependence(String dep);
}

и затем использовать фабрику для создания экземпляров ваших узлов

1 голос
/ 25 ноября 2008

Кажется, вы подразумеваете, что где-то вы знаете, каким должен быть класс, если он не существует. Если вы реализуете эту логику на своей фабрике, вы должны получить правильные классы.

Таким образом, вам также не нужно знать, какой класс действительно был возвращен с фабрики.

Я бы также сделал MyClass интерфейсом, если вы рассматриваете шаблон Factory.

1 голос
/ 25 ноября 2008

Класс Class параметризован с типом экземпляра, который он может создать. (например: Class - фабрика для экземпляров String.)

Я не вижу способа обойти знание типа экземпляра, который должен быть создан, когда вы получаете getOrCreate с использованием метода factory, поэтому я бы рекомендовал передать его методу и параметризовать генерируемый тип: *

private static Map<String, MyClass> directory = new HashMap<String, MyClass>();

public static <T extends MyClass> T getInstance(String name, Class<T> generatorClass)
{
  MyClass node = directory.get(name);    
  if(node == null) {
    node = generatorClass.getConstructor(String.class).newInstance(name);
    directory.put(name, node);
  }
  return node;
}

Кроме того, я заметил, что вы на самом деле не помещали недавно созданные узлы в каталог - я предполагаю, что это недосмотр. Вы также можете перегрузить этот метод другим, который не использует генератор и жестко запрограммирован на тип по умолчанию:

public static MyClass getInstance(String name) {
  return getInstance(name, MyClass.class);
}
0 голосов
/ 25 ноября 2008

Вы, вероятно, хотите внедрение зависимости. Это обобщило бы то, что вы пытаетесь сделать.

Кроме того, Наследование, вероятно, не совсем то, что вам нужно. Никогда не используйте его, если вы не можете пройти тест "is-a". Если ваш унаследованный класс имеет уникальный строковый тег «Has-a» (по сути, именно таким кажется ваш класс), это не означает, что он «is-a» ...

Впрочем, ты мог бы держать одного за другого. Вероятно, существует довольно много решений, но А) вы не опубликовали весь список требований, поэтому мы можем только догадываться, и Б) что вам, вероятно, нужно, это внедрение зависимостей. :)

0 голосов
/ 25 ноября 2008

Шаблон выглядит как Мухи (структурно, если не идеально подходит для намерения.)

Метод populate, как описано, может быть сопоставлен с шаблоном Template, хотя он не обязательно решает выраженные проблемы.

Я бы предложил обобщение фабрики с create (вместо getInstance, что в любом случае подразумевает для меня Singleton) для различных типов, которые вы ожидаете.

public static MyClass createJavaNode(String name, <differentiator>);
public static MyClass createSqlPlNode (String name, <differentiator>);
.
.
.

Знание того, как name отображается на <differentiator>, действительно является выбором реализации. В идеале, это будет полиморфный create, а дифференциация будет по типу node. Метод create возвращает MyClass, но действительно возвращает подклассы. Я бы настоятельно рекомендовал сделать MyClass интерфейсом или абстрактным классом с методом abstract populate (есть Template).

Из описания действительно видно, что поведение типов отличается между типами, а не поведение самих подклассов, поэтому случай рефакторинга MyClass в интерфейс или абстрактный класс получает сильнее.

Перефразируя Р. А. Хайнлайна, TANSTAAFL - Там нет такого понятия, как бесплатный обед - где-то знание о том, как создавать различные типы, должно существовать в приложении. Таким образом, ваш выбор состоит в том, чтобы поместить эти знания в класс Factory, как выразили некоторые другие ответы, или отделить решение , какая реализация создается от , как создается. Кажется, что есть возможность сканирования файловой системы и сбора Node s из нее. Этот код будет (или может) знать тип из Node (ответ на что ), который должен быть создан.

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

Кстати, если поведение ядра MyClass необходимо расширить или изменить для некоторых подклассов, то здесь Decorator может быть полезным.


Оригинальный ответ:

Вы можете рассмотреть Декоратор или Шаблон Шаблоны проектирования в качестве альтернативы.

...