Могу ли я определить абстрактный класс для всех производных синглетов таким образом?

#c# #.net #c#-4.0 #singleton #abstract-class

#c# #.net #c #-4.0 #синглтон #abstract-class

Вопрос:

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

 public abstract class Singleton<T> where T : Singleton<T>
{
    private static readonly Lazy<T> _instance = new Lazy<T>(() =>
    {
        var constructor = typeof(T).GetConstructor(BindingFlags.NonPublic |
            BindingFlags.Instance, null, new Type[0], null);

        return (T)constructor.Invoke(null);
    });
    public static T Instance { get { return _instance.Value; } }
    public Singleton() { }
}
  

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

 sealed class Server : Singleton<Server>
{
    private Server() { }
    ...
}
  

Правильно ли это, и, если нет, то почему?

Редактировать:

  • Добавлен частный конструктор в примере производного класса и вызов на абстрактной основе.

Редактировать:

  • Переработана инициализация параметра типа.

Комментарии:

1. Разве ваш конструктор не должен быть частным или защищенным?

2. @vc74 Не в этом случае. Попробуйте, и вы увидите, что вы не можете получить без создания конструктора.

3. Если конструктор класса Singleton является закрытым, вы получите сообщение об ошибке компиляции: ‘Singleton<Server>.Singleton()’ недоступен из-за его уровня защиты. Вам нужно сделать конструктор одноэлементного класса защищенным.

4. Статическое _instance поле только для чтения будет общим для всех экземпляров Singleton<T> . Не имеет значения, сколько объектов вы создаете универсального типа, у вас будет один Instance объект. Терминология немного не та, это скорее упрощенный шаблон, чем одноэлементный шаблон.

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

Ответ №1:

Нет, вы не можете, потому что, когда вы хотите использовать new T() , у вас должен быть общедоступный конструктор для него, и он отличается от определения singleton. потому что в singleton у вас должен быть частный конструктор, и в этом случае (общедоступный) каждый может создать новый экземпляр вашего объекта, и это не singleton.

Ответ №2:

Самостоятельно реализованные синглтоны являются антишаблоном. Необходимость наследования и фиксации ваших классов в определенной форме отпадает, если вы просто реализуете factory:

 public class Server {} //Not coupled to any inheritance hierarchy.

public class Factory
{
    private readonly Lazy<Server> _server = new Lazy<Server>(() => new Server());

    public Server Server { get { return _server.Value; } }
}
  

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

 public class MyServerConsumer
{
    public MyServerConsumer(Server server)
    {
      //Do stuff.      
    }
}
  

Регистрация в стиле Виндзор:

  ... 
 Component.For<Server>();
 ...
  

Обратите внимание, что слово singleton никогда не упоминается? Вы по-прежнему получаете «единственный экземпляр объекта», но вам не нужно писать код для поддержания этой связи, и ваши классы не ограничены и не повреждены концепцией «синглтона» с самого начала

Ответ №3:

Этот подход, который вы используете, имеет существенный недостаток. Вы связываете свои классы с одноэлементным классом. Помимо того, что он обычно не является СПЛОШНЫМ, вы теряете возможность наследования от класса, который вам может понадобиться (если класс не является одноэлементным).

Лучшая практика заключается в том, что вам нужно использовать внедрение зависимостей и контейнер IoC. Все контейнеры IoC позволяют указать, является ли класс синглтоном или нет.

Таким образом, класс совершенно не обращает внимания на тот факт, что он создается как синглтон, и это легко изменить на лету без изменения зависимостей.

Ответ №4:

C # не дает вам никакой гарантии того, когда будет создано статическое поле _instance . Это потому, что стандарт C # просто указывает, что классы (которые отмечены в IL как BeforeFieldInit ) могут инициализировать свои статические поля в любое время до обращения к полю. Это означает, что они могут быть инициализированы при первом использовании, они могут быть инициализированы в какое-то другое время раньше, вы не можете быть уверены, когда.

Отбросьте ленивое использование

Я предлагаю отказаться от Lazy конструкции и ввести статический ctor для создания экземпляра. Таким образом, вы воспользуетесь преимуществами стандарта C # BeforeFieldInit , но потеряете леность. Хотя он в некотором роде ленив, он не создается до использования типа. Что хорошо, поскольку большинство синглтонов все равно используются при ссылке.

 public abstract class Singleton<T> where T : class, new()
{
    private static readonly T _instance;
    public static T Instance { get { return _instance; } }
    public static Singleton() 
    {
        _instance = new T();
    }
}
  

Проблема с общедоступным ctor

Проблема, с которой вы столкнулись сейчас, заключается в том, что new T() конструкция вынуждает вас иметь общедоступный ctor. Что не очень хорошо для синглетов. Для решения этой проблемы вы могли бы использовать частный ctor, который вы вызываете через отражение.

Комментарии:

1. Все это не лучше, чем private static readonly T _instance = new T();

Ответ №5:

Это просто комментарий к ответу pjvds (я не мог прокомментировать обычным способом, потому что у меня недостаточно очков …).

Вместо использования частного конструктора через отражение, вы можете использовать частный метод инициализации и вызывать его после «new T()» в статическом методе Singlton.