Java中的单例和继承

Posted

技术标签:

【中文标题】Java中的单例和继承【英文标题】:singleton and inheritance in Java 【发布时间】:2011-07-22 14:51:20 【问题描述】:

我有一个基类,它捕获了两个类共有的一些功能。换句话说,我可以创建一个基类并使这两个类成为该基类的子类。但是,对于这些子类中的每一个,可以创建的实例数为 1(即每个子类必须是单例)。 我在谷歌上搜索,发现对此进行了合理的辩论。尽管有多种解决方案可用,但我不确定它们是否适合我的情况。

谁能告诉我应该如何设计这个?

【问题讨论】:

单身人士是软件的不朽者。假设只能有一个,但我们仍然用剑、斧头和叉子来破解我们的代码,以确保发生这种情况...... 【参考方案1】:

您可以将每个类单独设为单例,并使基类抽象。不知道争论的焦点是什么——一般来说,单例不是一个好主意吗?

【讨论】:

我不认为这很容易。见c2.com/cgi/wiki?InheritedJavaSingletonProblem 是的,尝试将“单例”实现为抽象是一个愚蠢的想法——它不起作用。不过,正如我所说,两个恰好共享一个基类的类都单独作为单例实现。 hhmmm,会试试这个。好吧,人们在谈论接口和工厂模式,我真的很困惑 @sura 这是一种观点(只要我不争论,我可以),但是 c2.com Wiki 已经损坏到无法使用。我认为这个和姐妹地方是下一代 c2.com,这就是为什么我在这里闲逛,很少去那里。 “作为抽象的单例”是控制反转框架的优势之一。主要用例是能够在从开发切换到测试、切换到部署平台或部署到不同平台时轻松更改组件的实现。【参考方案2】:

使用Abstract factory pattern。有一个单独的类,其中包含检索单例的方法,并让它在实例变量或映射中保存对单例的引用。

您可能不希望增加复杂性,但创建了像 Spring 这样的框架来解决这类问题(以及其他问题)。

似乎Pico Container 还活着,而且它可能是最简单但仍然可靠的解决方案。查看控制反转主题,让框架在您需要的地方注入单例。

简而言之,不要试图让单身人士管理自己的访问权限。把它委托给别的东西。

具有复杂继承的单例类本身并没有错。事实上,具有私有构造函数(无实例)的类层次结构在许多情况下都非常有用。您只需决定如何管理单例的两个重要方面:创建和访问。

【讨论】:

我发现这种态度——尽管它可能是根深蒂固的——只是悲伤。我们告诉这个可怜的家伙,为了避免写不到十几行代码,他应该包含几兆字节的 jars、一些 XML 配置文件,并创建一个没有培训课程就无法理解的应用程序。 @Ernest 我说“简而言之,不要试图让单身人士管理对自己的访问。将其委托给其他东西。” 我也认为为了“模块化”而强制依赖注入是一个错误的想法。 抽象工厂和 IOC(控制反转)都没有暗示依赖注入,即使大多数 IOC 容器都这样做。 @inor 这些模式是关于结构和使用的。它们是在对类特性没有访问限制的语言中开发的 (Smalltalk) 和使用 (Ruby) 的。单例的特点是用户不调用构造函数,而是使用静态方法或服务来访问单个实例。使构造函数受保护(或私有)会强制执行该模式,但不是必需的。【参考方案3】:

我不知道你是否需要一个例子,但我试了一下。在不知道您的任何详细信息的情况下,此示例非常模糊。我也是来学习的,所以让我们知道你最终实现了什么。

基类:

  public abstract class BaseClass 

        public void someMethod() 
            System.out.println("base class hello: " + this);
        

        public abstract void someOtherMethod(String value);
    

其中一个子类:

public class SubClassOne extends BaseClass 

    private static SubClassOne instance;

    private SubClassOne() 

    public static SubClassOne getInstance() 
        if (instance == null) 
            instance = new SubClassOne();
        
        return instance;
    

    public void someOtherMethod(String value) 
        someMethod();
        System.out.println("sub class hello: " + value + " " + this);
    

    public static void main(String[] args) 
        SubClassOne one = SubClassOne.getInstance();
        SubClassOne two = SubClassOne.getInstance();
        SubClassOne three = SubClassOne.getInstance();
        SubClassOne four = SubClassOne.getInstance();
        one.someOtherMethod("one");
        two.someOtherMethod("two");
        three.someOtherMethod("three");
        four.someOtherMethod("four");
    

【讨论】:

这只是原始问题的一个例子。他提到他有一个具有通用功能的基类和两个必须是单例的子类。【参考方案4】:

我有一个类似的要求:我有多个具有重复方法和成员的缓存映射,所以我创建了一个抽象类,例如:

public abstract class AbstractCache<T> 

    protected final Map<String, T> cache;

    protected AbstractCache() 
        this.cache = getDefaultExpiringMap(TimeUnit.HOURS.toMillis(4));
    

    public Map<String, T> getCache() 
        return cache;
    

    public T getAll(String id) 
        return cache.get(id);
    

然后我扩展了这个类并创建了一个单例实例:

public final class FooCache extends AbstractCache<Set<Integer>> 

    public static final FooCache INSTANCE = new FooCache();

    private FooCache() 
        super();
    

    public void add(String fooId, Integer value) 
        cache.computeIfAbsent(fooId, k -> new HashSet<>()).add(value);
    

及用法:

public static void main(String[] args) 
    FooCache.INSTANCE.add("a", 1);
    System.out.println(FooCache.INSTANCE.getAll("a"));

【讨论】:

【参考方案5】:

我不是 Java 专家,所以我不知道这在技术上是否是合法的 Java 代码(也许另一位发帖人可以评论):

使基类继承自一个泛型类 Singleton。

例子:

class Singleton<T> 

    protected Singleton(); //constructor

    private static T _instance;



class DerivedOne extends Singleton<DerivedOne>

    protected DerivedOne() //constructor


class DerivedTwo extends Singleton<DerivedTwo>

    protected DerivedTwo() //constructor

【讨论】:

你不能这样做,因为单例有一个私有构造函数。 @sura:您可以使用“受保护”访问修饰符而不是私有修饰符来隐藏构造函数。这样,只有子类可以调用它,但仍然不能被外界调用。 这个想法的问题是你只能制作一个对象,因为你只有一个_instance。如果这就是您所需要的,那没问题,但如果您需要每个子类的确切实例,那么这种方式是不可能的。【参考方案6】:

继承并不是重用通用功能的唯一方法。在一般情况下,遏制可能更可取。 考虑以下解决方案,其中 A 类和 B 类是单例,通用功能在 AB 类中,但 A 和 B 都使用 AB 的实例,而不是扩展 AB,它本身就是单例。

class AB  //common functionality of A and B
   //singleton pattern here
   //common data and functionality here

class A 
   private AB ab = AB.getInstance();
   //singleton pattern here
   //unique functionality and data of A

   //to use any of the functionality in AB delegate to member ab

B 类与 A 类相似。

在此解决方案中,A 和 B(和 AB)的每个数据和功能都有一个实例

注意,如果 A 和 B 的客户端需要访问 AB 中的公共公共方法,那么 AB、A 和 B 应该实现这些公共方法的接口,并且 A 和 B 实现应该将调用委托给 ab。


下面欧内斯特提出的解决方案,在某些情况下可能是捷径,但总的来说是错误的解决方案。

为了解释为什么 Ernest 的解决方案可能是错误的,让我们以不同的方式描述该解决方案。 假设我有一个单例类 A,我发现我需要编写另一个单例类 B,但我需要 B 中 A 的一些功能。所以我将 A 的公共数据和功能分解为抽象类 AB 并制作A 和 B 都扩展了 AB。 一般来说,它出错的原因是因为该解决方案采用了应该只存在一次的数据和功能的子集,并将其放在子类 (AB) 中,有效且可能在每个子类中复制它将创建的类。现在,在获得 A 的实例和 B 的实例之后,您在 AB 中有两个子集数据和功能实例。

例如,如果放置在基类中的通用功能将一些初始数据写入名为“myData”的文件,那么您的两个单例都将执行此代码,即使它只打算执行一次,并且当稍后执行它将清除前者创建的文件。

因此,一般来说,这里描述的解决方案不使用继承,并确保单例封装了常用功能以及使用它的单例类。

【讨论】:

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

枚举、单例和反序列化

Unity单例模式最佳实践(附代码)

双重检查创建线程安全的单例和无锁

单例模式

一个小的单例类。再也不用担心继承类问题。

IOC的单例和多例