Полиморфная фабрика/getInstance() в Java

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

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

    // 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, триггеры, управляемые сообщениями, и т. д.

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

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

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

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


person slim    schedule 25.11.2008    source источник
comment
Где Синглтон? Я не вижу никого здесь. Также предполагается, что в методе getInstance() отсутствует return.   -  person Ken Gentle    schedule 25.11.2008
comment
Добавил недостающий возврат - спасибо. Я предполагаю, что должно быть другое имя для шаблона, который является вариацией Singleton, где уникальность гарантируется для (класса + идентификатора)   -  person slim    schedule 26.11.2008
comment
Взгляните на этот stackoverflow.com/ questions/11126866/ это подход, использующий шаблон memoizer. Он делает то, что вы хотите, а также является потокобезопасным!   -  person mike    schedule 14.08.2013


Ответы (7)


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

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

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

person Wouter Lievens    schedule 25.11.2008
comment
«вероятно» вызвало у меня подозрения. Похоже ты ошибаешься. Компилятор просматривает объявленный тип экземпляра, чтобы найти реальный статический метод для вызова. Это называется «скрытием», а не «переопределением». (-1) - person xtofl; 26.11.2008

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

person bajafresh4life    schedule 25.11.2008
comment
Хорошее предложение. Большинство контейнеров Dependency Injection/Inversion of Control делают именно то, что вы ищете. - person erickson; 28.11.2008

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

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

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

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

person pvgoddijn    schedule 27.11.2008
comment
+1, потому что это соответствует решению, к которому я пришел после прочтения хороших предложений всех остальных. - person slim; 27.11.2008

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

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

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

person pvgoddijn    schedule 25.11.2008

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

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

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);
}
person Alex Miller    schedule 25.11.2008

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

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

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

person Bill K    schedule 25.11.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, как было выражено в некоторых других ответах, или отделить решение о том, какая реализация создается от как она создается. созданный. Вроде есть возможность сканировать файловую систему и собирать из нее Nodes. Этот код будет (или может) знать тип Node (ответ на вопрос что), который должен быть создан.

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

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


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

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

person Ken Gentle    schedule 25.11.2008
comment
Я изо всех сил пытаюсь понять, как любой из них помогает. Можете ли вы расширить его? - person slim; 25.11.2008
comment
Достаточно справедливо - возможно, если вы немного уточните, чего вы пытаетесь достичь, я могу либо дать вам лучший ответ, либо удалить его. Например, что MySubClass добавляет к MyClass? - person Ken Gentle; 25.11.2008
comment
Хорошо спасибо. Я попытался обобщить, чтобы не увязнуть в конкретике. Но я отредактирую его, чтобы добавить эти особенности сейчас. - person slim; 26.11.2008