Как я могу реализовать абстрактный класс Singleton в Java? - PullRequest
23 голосов
/ 17 марта 2010

Вот мой пример абстрактного синглтон-класса:

public abstract class A {
    protected static A instance;
    public static A getInstance() {
        return instance;
    }
    //...rest of my abstract methods...
}

А вот конкретная реализация:

public class B extends A {
    private B() { }
    static {
        instance = new B();
    }
    //...implementations of my abstract methods...
}

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

Class c = B.class;
A.getInstance() - returns null;

и это

ClassLoader.getSystemClassLoader().loadClass("B");
A.getInstance() - return null;

При запуске обоих в отладчике eclipse статический код никогда не выполняется. Единственный способ найти статический код, который я смог найти, - это изменить доступность конструктора B на public и вызвать его.

Я использую sun-java6-jre в Ubuntu 32bit для запуска этих тестов.

Ответы [ 6 ]

20 голосов
/ 17 марта 2010

Аннотация Синглтон? Не кажется жизнеспособным для меня. Для шаблона Singleton требуется конструктор private, и это уже делает невозможным создание подклассов. Вам нужно переосмыслить свой дизайн. Шаблон Abstract Factory *1003* может быть более подходящим для конкретной цели.

8 голосов
/ 16 октября 2010

Что ж, из вашего поста, даже если он не ясно сформулирован, звучит так, будто вы хотите, чтобы абстрактный класс играл две разные роли. Роли:

  • абстрактная фабричная роль для (Singleton) сервис, который может иметь множественно заменяемый реализации,
  • услуга роль интерфейса,

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

Это хорошо.

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

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

Что неправильно, так это то, что после того, как вы хотите, чтобы дети отвечали за выбор того, какую реализацию должен возвращать родительский метод фабрики. Это неправильно с точки зрения дизайна, потому что вы делегируете всем дочерним элементам то, что может быть просто перенесено и централизовано в абстрактный суперкласс, а также показывает, что вы смешиваете шаблоны, которые используются в разных контекстах, Абстрактная фабрика (родительский класс решает, какое семейство классов клиенты получат) и Factory Method (детские фабрики выбирают, что получат клиенты).

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

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

public abstract class A{
    public static A getInstance(){
      if (...)
         return B.getInstance();
      return C.getInstance();
    }

    public abstract void doSomething();

    public abstract void doSomethingElse();

}

public class B extends A{
    private static B instance=new B();

    private B(){
    }

    public static B getInstance(){
        return instance;
    }

    public void doSomething(){
        ...
    }
    ...
}

//do similarly for class C

Родитель также может использовать отражение.

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

4 голосов
/ 17 марта 2010

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

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

4 голосов
/ 17 марта 2010

A.getInstance() никогда не будет вызывать производный экземпляр, поскольку он статически связан.

Я бы отделил создание объекта от самого фактического объекта и создал бы фабрику , возвращающую определенный тип класса. Непонятно, как бы вы параметризовали это, учитывая ваш пример кода - он параметризован через какой-то аргумент или выбор класса статичен?

Вы можете переосмыслить синглтон, кстати. Это обычный antipattern , который делает тестирование (в частности) болезненным, так как тестируемые классы будут предоставлять свой экземпляр этого класса в виде одиночки. Вы не можете предоставить фиктивную реализацию или (легко) создать новый экземпляр для каждого теста.

3 голосов
/ 17 марта 2010

В дополнение к проблемам, указанным другими, наличие поля instance в A означает, что вы можете иметь только один синглтон во всей ВМ. Если у вас также есть:

public class C extends A {
    private C() { }
    static {
        instance = new C();
    }
    //...implementations of my abstract methods...
}

... тогда, какой бы из B или C не загрузился последним, победит, а экземпляр синглтона другого будет потерян.

Это просто плохой способ делать вещи.

1 голос
/ 10 апреля 2019

Я нашел лучший способ использовать Singleton в абстрактном классе, который использует статическую карту для поддержки экземпляра подкласса.

public abstract class AbstractSingleton {

    private static Map<String, AbstractSingleton> registryMap = new HashMap<String, AbstractSingleton>();

    AbstractSingleton() throws SingletonException {
        String clazzName = this.getClass().getName();
        if (registryMap.containsKey(clazzName)) {
            throw new SingletonException("Cannot construct instance for class " + clazzName + ", since an instance already exists!");
        } else {
            synchronized (registryMap) {
                if (registryMap.containsKey(clazzName)) {
                    throw new SingletonException("Cannot construct instance for class " + clazzName + ", since an instance already exists!");
                } else {
                    registryMap.put(clazzName, this);
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    public static <T extends AbstractSingleton> T getInstance(final Class<T> clazz) throws InstantiationException, IllegalAccessException {
        String clazzName = clazz.getName();
        if (!registryMap.containsKey(clazzName)) {
            synchronized (registryMap) {
                if (!registryMap.containsKey(clazzName)) {
                    T instance = clazz.newInstance();
                    return instance;
                }
            }
        }
        return (T) registryMap.get(clazzName);
    }

    public static AbstractSingleton getInstance(final String clazzName)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        if (!registryMap.containsKey(clazzName)) {
            Class<? extends AbstractSingleton> clazz = Class.forName(clazzName).asSubclass(AbstractSingleton.class);
            synchronized (registryMap) {
                if (!registryMap.containsKey(clazzName)) {
                    AbstractSingleton instance = clazz.newInstance();
                    return instance;
                }
            }
        }
        return registryMap.get(clazzName);
    }

    @SuppressWarnings("unchecked")
    public static <T extends AbstractSingleton> T getInstance(final Class<T> clazz, Class<?>[] parameterTypes, Object[] initargs)
            throws SecurityException, NoSuchMethodException, IllegalArgumentException,
            InvocationTargetException, InstantiationException, IllegalAccessException {
        String clazzName = clazz.getName();
        if (!registryMap.containsKey(clazzName)) {
            synchronized (registryMap) {
                if (!registryMap.containsKey(clazzName)) {
                    Constructor<T> constructor = clazz.getConstructor(parameterTypes);
                    T instance = constructor.newInstance(initargs);
                    return instance;
                }
            }
        }
        return (T) registryMap.get(clazzName);
    }

    static class SingletonException extends Exception {
        private static final long serialVersionUID = -8633183690442262445L;

        private SingletonException(String message) {
            super(message);
        }
    }
}

От: https://www.cnblogs.com/wang9192/p/3975748.html

...