Полиморфный factory/getInstance() в Java

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

Я могу использовать метод factory:

    // 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(), который используется для настройки объекта из соответствующего источника.

Скажем, node с именем 'login.java' узнает, что он имеет зависимость от 'checkpasswd.sqlpl':

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

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

Ответ 1

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

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

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

Ответ 2

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

Ответ 3

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

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

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

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

Ответ 4

Класс Class параметризуется типом экземпляра, который он может создать. (ex: Class <String> является factory для экземпляров 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);
}

Ответ 5

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

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

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

Ответ 6

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

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

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

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

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

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

Перефразируя R. A. Heinlein, TANSTAAFL - Нет такой вещи как бесплатный обед - где-то знание о том, как создавать различные типы, должно существовать в приложении. Поэтому ваш выбор состоит в том, чтобы поместить это знание в класс Factory, как выразились некоторые другие ответы, или отделить решение о том, какая реализация создается из того, как она создана. Похоже, есть возможность сканирования файловой системы и сбора node от нее. Этот код будет (или может) знать тип node (ответ на что), который должен быть создан.

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

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


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

Вы можете рассмотреть Decorator или Template Design Patterns в качестве альтернативы.

Ответ 7

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

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

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