Java中带参数的单例

Posted

技术标签:

【中文标题】Java中带参数的单例【英文标题】:Singleton with Arguments in Java 【发布时间】:2010-11-06 06:21:02 【问题描述】:

我在阅读 Wikipedia 上的 Singleton 文章时遇到了这个例子:

public class Singleton 
    // Private constructor prevents instantiation from other classes
    private Singleton() 

    /**
     * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
     * or the first access to SingletonHolder.INSTANCE, not before.
     */
    private static class SingletonHolder  
        private static final Singleton INSTANCE = new Singleton();
    

    public static Singleton getInstance() 
        return SingletonHolder.INSTANCE;
    

虽然我真的很喜欢这个 Singleton 的行为方式,但我不知道如何调整它以将参数合并到构造函数中。在 Java 中执行此操作的首选方法是什么?我必须做这样的事情吗?

public class Singleton

    private static Singleton singleton = null;  
    private final int x;

    private Singleton(int x) 
        this.x = x;
    

    public synchronized static Singleton getInstance(int x) 
        if(singleton == null) singleton = new Singleton(x);
        return singleton;
    

谢谢!


编辑:我想我对使用 Singleton 的渴望引发了一场争议风暴。让我解释一下我的动机,希望有人能提出一个更好的主意。我正在使用网格计算框架来并行执行任务。一般来说,我有这样的事情:

// AbstractTask implements Serializable
public class Task extends AbstractTask

    private final ReferenceToReallyBigObject object;

    public Task(ReferenceToReallyBigObject object)
    
        this.object = object;
    

    public void run()
    
        // Do some stuff with the object (which is immutable).
    

发生的情况是,即使我只是将数据的引用传递给所有任务,但当任务被序列化时,数据会被一遍又一遍地复制。我想做的是在所有任务之间共享对象。当然,我可能会像这样修改这个类:

// AbstractTask implements Serializable
public class Task extends AbstractTask

    private static ReferenceToReallyBigObject object = null;

    private final String filePath;

    public Task(String filePath)
    
        this.filePath = filePath;
    

    public void run()
    
        synchronized(this)
        
            if(object == null)
            
                ObjectReader reader = new ObjectReader(filePath);
                object = reader.read();
            
        

        // Do some stuff with the object (which is immutable).
    

如您所见,即使在这里我也有一个问题,即在传递第一个文件路径后传递不同的文件路径没有任何意义。这就是为什么我喜欢在答案中发布的 store 的想法。无论如何,我不想在 run 方法中包含加载文件的逻辑,而是想将此逻辑抽象为一个 Singleton 类。我不会再举一个例子,但我希望你能明白。请让我听听您的想法,以更优雅的方式完成我正在尝试做的事情。再次感谢!

【问题讨论】:

工厂模式就是你想要的。理想情况下,网格任务应该完全独立于其他任何东西,并获得执行和返回结果所需的所有数据。但是,这并不总是最可行的解决方案,因此将数据序列化到文件中并不是一个坏主意。我认为整个单身人士的事情有点像红鲱鱼。你不想要一个单身人士。 很遗憾你使用了这样一个包袱附带的单例这个词。这种模式的正确术语实际上是实习。实习是一种确保抽象值仅由一个实例表示的方法。字符串实习是最常见的用法:en.wikipedia.org/wiki/String_intern_pool。 您可能想看看 Terracotta。它维护整个集群的对象身份。当您发送对集群中已有数据的引用时,它不会被重新序列化。 抛开是否应该使用单例模式的问题,我注意到这里几乎每个答案似乎都假设提供参数的目的是允许“多个单例”被创建,由所述参数的值区分。但另一个可能的目的是提供对外部对象的 访问,该对象是单例类的 unique 实例将永远存在的 only 对象需要。因此,我们需要区分为此类访问提供的参数和旨在创建“多个单例实例”的参数。 “带参数的单例”的另一种情况:一个 Web 应用程序将根据第一个即将到来的请求(线程)附带的信息构建其独特的不可变单例。请求的域可以决定一些单例的行为,例如 【参考方案1】:

我的观点很明确:带参数的单例不是单例

根据定义,单例是您希望实例化不超过一次的对象。如果您尝试将参数提供给构造函数,那么单例的意义何在?

你有两个选择。如果你想用一些数据初始化你的单例,你可以在实例化之后用数据加载它,像这样:

SingletonObj singleton = SingletonObj.getInstance();
singleton.init(paramA, paramB); // init the object with data

如果您的单例正在执行的操作是重复的,并且每次使用不同的参数,您不妨将参数传递给正在执行的 main 方法:

SingletonObj singleton = SingletonObj.getInstance();
singleton.doSomething(paramA, paramB); // pass parameters on execution

在任何情况下,实例化始终是无参数的。否则你的单例就不是单例。

【讨论】:

+1 这就是我在编码时可能会这样做的方式。在 C# 中,我只是使用属性。 Java,大概是这样的。 抱歉,这不是真的。在某些情况下,您必须传入动态创建的参数,这些参数对于空洞应用程序运行时保持不变。所以你不能在单例中使用常量,但必须在创建时传递该常量。在经过一次后,它的洞时间常数相同。如果您需要构造函数中的特定常量,setter 将无法完成这项工作。 如果您在应用程序的整个生命周期中只需要一个类的实例,但您需要在启动时为该实例提供一个值,为什么这不再是单例? 一个与您的假设相反的例子是 android 中的数据库助手类。最好的做法是让这个类有一个单例来维护一个与数据库的连接,但它需要一个参数(Context)。 "如果你试图将参数提供给构造函数,那么单例的意义何在?" - 也可以说:“如果您将整个应用程序设为单个实例,那么命令行参数的意义何在?”,答案是它很有意义。现在可以说这与单例类完全不同,除非该类实际上是从 main 方法接收 args[] 的 Main 类 - 那么它甚至是同一件事。最后一个可能成立的论点是,这是一个相当特殊的情况。【参考方案2】:

我认为您需要像 factory 这样的东西来实例化和重用具有各种参数的对象。它可以通过使用同步的HashMapConcurrentHashMap 将参数(例如Integer)映射到您的“单例”参数化类来实现。

尽管您可能会使用常规的非单例类来代替(例如,需要 10.000 个不同参数化的单例)。

以下是此类商店的示例:

public final class UsefulObjFactory 

    private static Map<Integer, UsefulObj> store =
        new HashMap<Integer, UsefulObj>();

    public static final class UsefulObj 
        private UsefulObj(int parameter) 
            // init
        
        public void someUsefulMethod() 
            // some useful operation
        
    

    public static UsefulObj get(int parameter) 
        synchronized (store) 
            UsefulObj result = store.get(parameter);
            if (result == null) 
                result = new UsefulObj(parameter);
                store.put(parameter, result);
            
            return result;
        
    

为了更进一步,Java enums 也可以被视为(或用作)参数化单例,尽管只允许固定数量的静态变体。

但是,如果您需要分布式1 解决方案,请考虑一些横向缓存解决方案。例如:EHCache、Terracotta 等

1 是指跨越可能多台计算机上的多个虚拟机。

【讨论】:

是的,这正是我所需要的。非常感谢!我同意我在示例中处理参数的方式没有多大意义,但我没有想到这一点。请参阅我在 oxbow_lakes 答案的 cmets 中的解释。 不是单例;你现在有不止一个。哈哈 @Scott:我建议像 Yuval 下面的建议。这更有意义,你有一个“真正的”单身人士。 edit 我希望没有人介意我编辑代码中的名称;我可以想象这对新手来说真的很困惑。如果您不同意,则回滚 是的,我们可以称它们为 Multitron,并且仍然可以实现 OP 一开始就想要实现的相同目标,恕我直言。【参考方案3】:

您可以添加一个可配置的初始化方法,以便将实例化与获取分开。

public class Singleton 
    private static Singleton singleton = null;
    private final int x;

    private Singleton(int x) 
        this.x = x;
    

    public static Singleton getInstance() 
        if(singleton == null) 
            throw new AssertionError("You have to call init first");
        

        return singleton;
    

    public synchronized static Singleton init(int x) 
        if (singleton != null)
        
            // in my opinion this is optional, but for the purists it ensures
            // that you only ever get the same instance when you call getInstance
            throw new AssertionError("You already initialized me");
        

        singleton = new Singleton(x);
        return singleton;
    


然后您可以调用Singleton.init(123) 一次进行配置,例如在您的应用启动时。

【讨论】:

【参考方案4】:

如果您想显示某些参数是强制性的,您也可以使用 Builder 模式。

    public enum EnumSingleton 

    INSTANCE;

    private String name; // Mandatory
    private Double age = null; // Not Mandatory

    private void build(SingletonBuilder builder) 
        this.name = builder.name;
        this.age = builder.age;
    

    // Static getter
    public static EnumSingleton getSingleton() 
        return INSTANCE;
    

    public void print() 
        System.out.println("Name "+name + ", age: "+age);
    


    public static class SingletonBuilder 

        private final String name; // Mandatory
        private Double age = null; // Not Mandatory

        private SingletonBuilder()
          name = null;
        

        SingletonBuilder(String name) 
            this.name = name;
        

        public SingletonBuilder age(double age) 
            this.age = age;
            return this;
        

        public void build()
            EnumSingleton.INSTANCE.build(this);
        

    



然后您可以按如下方式创建/实例化/参数化它:

public static void main(String[] args) 
    new EnumSingleton.SingletonBuilder("nico").age(41).build();
    EnumSingleton.getSingleton().print();

【讨论】:

【参考方案5】:

很惊讶没有人提到如何创建/检索记录器。例如,下面显示了如何检索Log4J logger。

// Retrieve a logger named according to the value of the name parameter. If the named logger already exists, then the existing instance will be returned. Otherwise, a new instance is created.
public static Logger getLogger(String name)

有一些间接级别,但关键部分在method 下面,它几乎说明了它是如何工作的。它使用哈希表来存储现有的记录器,并且密钥来自名称。如果给定名称不存在记录器,它会使用工厂创建记录器,然后将其添加到哈希表中。

69   Hashtable ht;
...
258  public
259  Logger getLogger(String name, LoggerFactory factory) 
260    //System.out.println("getInstance("+name+") called.");
261    CategoryKey key = new CategoryKey(name);
262    // Synchronize to prevent write conflicts. Read conflicts (in
263    // getChainedLevel method) are possible only if variable
264    // assignments are non-atomic.
265    Logger logger;
266
267    synchronized(ht) 
268      Object o = ht.get(key);
269      if(o == null) 
270        logger = factory.makeNewLoggerInstance(name);
271        logger.setHierarchy(this);
272        ht.put(key, logger);
273        updateParents(logger);
274        return logger;
275       else if(o instanceof Logger) 
276        return (Logger) o;
277       
...

【讨论】:

【参考方案6】:

带参数的单例不是单例”陈述不完全正确。我们需要从应用程序的角度而不是从代码的角度来分析这一点。

我们构建单例类以在一个应用程序运行中创建一个对象的单个实例。通过使用带参数的构造函数,您可以在代码中构建灵活性,以便在每次运行应用程序时更改单例对象的某些属性。这不违反单例模式。如果从代码的角度来看,这看起来像是违规。

设计模式可以帮助我们编写灵活和可扩展的代码,而不是阻碍我们编写好的代码。

【讨论】:

这不是对 OP 问题的回答,这应该是评论。【参考方案7】:

使用 getter 和 setter 设置变量并使默认构造函数私有。然后使用:

Singleton.getInstance().setX(value);

【讨论】:

不明白为什么这被否决了。这是一个有效的答案。 :// 因为这是一个垃圾答案。例如,想象一个系统,其中初始管理员的初始用户名和密码是构造函数参数。现在,如果我把它变成一个单例并按照你说的做,我会为管理员获取 getter 和 setter,这并不是你想要的。因此,虽然您的选择在某些情况下可能有效,但它并不能真正回答问题的一般情况。 (是的,我正在研究我描述的系统,不,如果不是因为作业说“在此处使用单例模式”,我不会使用单例模式)【参考方案8】:

使用Bill Pugh's initialization on demand holder idiom 的单例模式的修改。这是线程安全的,没有专门的语言结构(即 volatile 或 synchronized)的开销:

public final class RInterfaceHL 

    /**
     * Private constructor prevents instantiation from other classes.
     */
    private RInterfaceHL()  

    /**
     * R REPL (read-evaluate-parse loop) handler.
     */
    private static RMainLoopCallbacks rloopHandler = null;

    /**
     * SingletonHolder is loaded, and the static initializer executed, 
     * on the first execution of Singleton.getInstance() or the first 
     * access to SingletonHolder.INSTANCE, not before.
     */
    private static final class SingletonHolder 

        /**
         * Singleton instance, with static initializer.
         */
        private static final RInterfaceHL INSTANCE = initRInterfaceHL();

        /**
         * Initialize RInterfaceHL singleton instance using rLoopHandler from
         * outer class.
         * 
         * @return RInterfaceHL instance
         */
        private static RInterfaceHL initRInterfaceHL() 
            try 
                return new RInterfaceHL(rloopHandler);
             catch (REngineException e) 
                // a static initializer cannot throw exceptions
                // but it can throw an ExceptionInInitializerError
                throw new ExceptionInInitializerError(e);
            
        

        /**
         * Prevent instantiation.
         */
        private SingletonHolder() 
        

        /**
         * Get singleton RInterfaceHL.
         * 
         * @return RInterfaceHL singleton.
         */
        public static RInterfaceHL getInstance() 
            return SingletonHolder.INSTANCE;
        

    

    /**
     * Return the singleton instance of RInterfaceHL. Only the first call to
     * this will establish the rloopHandler.
     * 
     * @param rloopHandler
     *            R REPL handler supplied by client.
     * @return RInterfaceHL singleton instance
     * @throws REngineException
     *             if REngine cannot be created
     */
    public static RInterfaceHL getInstance(RMainLoopCallbacks rloopHandler)
            throws REngineException 
        RInterfaceHL.rloopHandler = rloopHandler;

        RInterfaceHL instance = null;

        try 
            instance = SingletonHolder.getInstance();
         catch (ExceptionInInitializerError e) 

            // rethrow exception that occurred in the initializer
            // so our caller can deal with it
            Throwable exceptionInInit = e.getCause();
            throw new REngineException(null, exceptionInInit.getMessage());
        

        return instance;
    

    /**
     * org.rosuda.REngine.REngine high level R interface.
     */
    private REngine rosudaEngine = null;

    /**
     * Construct new RInterfaceHL. Only ever gets called once by
     * @link SingletonHolder.initRInterfaceHL.
     * 
     * @param rloopHandler
     *            R REPL handler supplied by client.
     * @throws REngineException
     *             if R cannot be loaded.
     */
    private RInterfaceHL(RMainLoopCallbacks rloopHandler)
            throws REngineException 

        // tell Rengine code not to die if it can't
        // load the JRI native DLLs. This allows
        // us to catch the UnsatisfiedLinkError
        // ourselves
        System.setProperty("jri.ignore.ule", "yes");

        rosudaEngine = new JRIEngine(new String[]  "--no-save" , rloopHandler);
    

【讨论】:

我认为在getInstance 中使用finally RInterfaceHL.rloopHandler = null; 是个好主意,因为如果我们不小心,静态引用可能会导致内存泄漏。在您的情况下,这看起来不是问题,但我可以想象传入的对象很大并且仅由 RInterfaceHL ctor 用于获取一些值而不是保留对它的引用的场景。 想法:return SingletonHolder.INSTANCEgetInstance 中也能正常工作。我认为这里不需要封装,因为外部类已经知道内部类的内部,它们是紧密耦合的:它知道rloopHandler 在调用之前需要 init。私有构造函数也没有任何作用,因为内部类的私有内容对外部类是可用的。 链接已损坏。你指的是en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom 吗?【参考方案9】:

如果你想创建一个作为 Context 的 Singleton 类,一个好方法是拥有一个配置文件并从 instance() 中的文件中读取参数。

如果提供给 Singleton 类的参数是在程序运行期间动态获取的,则只需使用静态 HashMap 在 Singleton 类中存储不同的实例,以确保为每个参数创建一个实例。

【讨论】:

【参考方案10】:

您无法理解如何完成您正在尝试做的事情的原因可能是您正在尝试做的事情并没有真正的意义。你想用不同的参数调用getInstance(x),但总是返回相同的对象?当你调用getInstance(2) 然后getInstance(5) 时你想要什么行为?

如果你想要同一个对象但它的内部值不同,这是它仍然是单例的唯一方式,那么你根本不需要关心构造函数;您只需在对象退出时设置getInstance() 中的值。当然,您知道您对单例的所有其他引用现在都具有不同的内部值。

另一方面,如果您希望 getInstance(2)getInstance(5) 返回不同的对象,那么您不是在使用单例模式,而是在使用工厂模式。

【讨论】:

【参考方案11】:

在您的示例中,您没有使用单例。请注意,如果您执行以下操作(假设 Singleton.getInstance 实际上是静态的):

Singleton obj1 = Singleton.getInstance(3);
Singleton obj2 = Singleton.getInstance(4);

那么 obj2.x 的值为 3,而不是 4。如果您需要这样做,请将其设为普通类。如果值的数量很小且固定,您可以考虑使用enum。如果您遇到过多的对象生成问题(通常不是这种情况),那么您可以考虑缓存值(并检查源或获得帮助,因为很明显如何构建缓存而不会有内存泄漏的危险)。

您可能还想read this article,因为单例很容易被过度使用。

【讨论】:

【参考方案12】:

单例是反模式的另一个原因是,如果根据建议编写,使用私有构造函数,它们很难子类化和配置以在某些单元测试中使用。例如,在维护遗留代码时需要。

【讨论】:

【参考方案13】:

这并不完全是单例,但可能可以解决您的问题。

public class KamilManager 

  private static KamilManager sharedInstance;

  /**
   * This method cannot be called before calling KamilManager constructor or else
   * it will bomb out.
   * @return
   */
  public static KamilManager getInstanceAfterInitialized() 
    if(sharedInstance == null)
        throw new RuntimeException("You must instantiate KamilManager once, before calling this method");

    return sharedInstance;


  public KamilManager(Context context, KamilConfig KamilConfig) 
    //Set whatever you need to set here then call:
  s  haredInstance = this;
  

【讨论】:

【参考方案14】:

我们不能这样做吗:

public class Singleton 

    private int x;

    // Private constructor prevents instantiation from other classes
    private Singleton() 

    /**
     * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
     * or the first access to SingletonHolder.INSTANCE, not before.
     */
    private static class SingletonHolder  
        private static final Singleton INSTANCE = new Singleton();
    

    public static Singleton getInstance(int x) 
        Singleton instance = SingletonHolder.INSTANCE;
        instance.x = x;
        return instance;
    

【讨论】:

【参考方案15】:

我害怕发布这个作为答案,但我不明白为什么没人考虑这个,也许这个答案也已经给出了我只是不明白。

public class example  
    private volatile static example instance;

    private String string;
    private int iInt = -1; //any number you know you don't want to use here

  private example() 

    //In case someone uses the private method to create a new Instance
    if (instance != null)
      throw new RuntimeException("Use getInstance() method to get the single instance of this class.");
    
  

  public synchronized static example getIsntance()
    if(instance == null)
      instance = new example();
    
    return instance;
  

public void methodDoingWork()
    if(checkInit())
      //DoSome
    
  

  private boolean checkInit()
    boolean filled = (this.string != null) && (this.iInt != -1);
    return filled;
  

  public void setString(String string) 
    if(this.string == null)
      this.string = string;
    else
      throw new RuntimeException("You try to override an already setValue"); 
    
  

  public void setiInt(int iInt) 
    if(this.iInt == -1)
      this.iInt = iInt;
    else
      throw new RuntimeException("You try to override an already setValue");
    
  

由于getInstance() 每次都返回相同的实例,我认为这可以工作。 如果这太错误了我会删除它,我只是对这个话题感兴趣。

【讨论】:

【参考方案16】:

如果我们把问题理解为“如何用状态制作单例”,那么就没有必要将状态作为构造函数参数传递。我同意在获取单例实例后初始化状态或使用 set 方法的帖子。

另一个问题是:有状态的单例很好吗?

【讨论】:

【参考方案17】:

尽管有些人可能会断言,这是一个在构造函数中带有参数的单例

public class Singleton 

    private static String aParameterStored;

    private static final Singleton instance = new Singleton("Param to set");

    private Singleton() 
        // do nothing
    

    private Singleton(String param) 
        aParameterStored = param;
    

    public static Singleton getInstance() 
        return instance;
    

    /*
     * ... stuff you would like the singleton do
     */

单例模式说:

确保单例类只存在一个实例 提供对该实例的全局访问。

在本示例中受到尊重。

为什么不直接设置属性呢?这是一个教科书案例,展示了我们如何获得一个具有带参数的构造函数的单例,但在某些情况下它可能很有用。例如在继承情况下强制单例设置一些超类属性。

【讨论】:

【参考方案18】:

添加一些东西,如果希望参数只初始化一次并且不能被覆盖,那么只需执行检查并在有人尝试再次初始化它们时抛出异常。例如:

public class Service 

private String host = null;
private String port = null;

private Service() 


private static class ServiceSingletonHolder 

    private static final Service INSTANCE = new Service();


public static Service getInstance() 
    return ServiceSingletonHolder.INSTANCE;


public void initilize(String host, String port) 
    if (this.host != null && host != null) 
        throw new IllegalArgumentException("host can not be overwritten");
    

    if (this.port != null && port != null) 
        throw new IllegalArgumentException("port can not be overwritten");
    

    this.host = host;
    this.port = port;


【讨论】:

【参考方案19】:

我认为这是一个常见问题。将单例的“初始化”与单例的“获取”分开可能会起作用(此示例使用双重检查锁定的变体)。

public class MySingleton 

    private static volatile MySingleton INSTANCE;

    @SuppressWarnings("UnusedAssignment")
    public static void initialize(
            final SomeDependency someDependency) 

        MySingleton result = INSTANCE;

        if (result != null) 
            throw new IllegalStateException("The singleton has already "
                    + "been initialized.");
        

        synchronized (MySingleton.class) 
            result = INSTANCE;

            if (result == null) 
                INSTANCE = result = new MySingleton(someDependency);
             
        
    

    public static MySingleton get() 
        MySingleton  result = INSTANCE;

        if (result == null) 
            throw new IllegalStateException("The singleton has not been "
                    + "initialized. You must call initialize(...) before "
                    + "calling get()");
        

       return result;
    

    ...

【讨论】:

我想在初始化方法中总是可以返回“结果”。【参考方案20】:

当然,单例是一种“反模式”(假设定义了具有可变状态的静态)。

如果你想要一组固定的不可变值对象,那么枚举就是你要走的路。对于大量的、可能是开放式的值集,您可以使用某种形式的存储库 - 通常基于 Map 实现。当然,当你处理静态时要小心线程(要么同步足够广泛,要么使用ConcurrentMap,要么检查另一个线程没有打败你,要么使用某种形式的期货)。

【讨论】:

如果使用不当,只有反模式,尽管这是反模式的定义。仅仅因为您在过去不属于他们的地方见过他们并不意味着他们没有位置。 正确使用单例是为了演示不称职的代码。【参考方案21】:

Singleton 通常被认为是anti-patterns,不应使用。它们不会使代码易于测试。

带参数的单例无论如何都没有意义 - 如果你这样写会发生什么:

Singleton s = SingletonHolder.getInstance(1);
Singleton t = SingletonHolder.getInstance(2); //should probably throw IllegalStateException

您的单例也不是线程安全的,因为多个线程可以同时调用getInstance,从而导致创建多个实例(可能具有不同的x 值)。

【讨论】:

是的,这值得商榷;因此我使用了“一般”这个词。我认为可以公平地说,它们通常被认为是一个坏主意 这是值得商榷的——有些人声称所谓的“反模式”符合模式的定义,只是它们是不好的模式。 我知道他们很糟糕。我正在做分布式计算,需要在多个任务之间共享一个对象。我不想确定性地初始化静态变量,而是将逻辑抽象为单例。我想我可以让 getInstance 同步。这行得通吗?我需要做的是为许多任务加载一次文件,并且仅在发送第一个任务之后。 (我不希望我的数据序列化。)我想我会让我的 AbstractFileReader 成为 getInstance 方法的参数,以使 Singleton 更灵活。我重视您的意见。 我想你可能误解了“分布式”是什么意思?还有其他方法可以实现您想要的:您是否考虑过依赖注入?还是 JNDI? 如果你想实例化多个文件阅读器并重用它们,为什么不直接使用 Map,键入文件名?您可以根据需要实例化它们并将它们存储在映射中(通过适当的同步或使用 java.util.concurrent 映射)。

以上是关于Java中带参数的单例的主要内容,如果未能解决你的问题,请参考以下文章

带参数的单例

CodeWorldkotlin带参数的单例模式封装实现

CodeWorldkotlin带参数的单例模式封装实现

CodeWorldkotlin带参数的单例模式封装实现

使Daniel Brodie&ŧ039;的单例配方使用关键字参数

spring的单例模式