2010-03-17 97 views
18

这里是我的样品抽象单例类:如何在Java中实现抽象单例类?

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,然后调用它。

我在Ubuntu 32bit上使用sun-java6-jre来运行这些测试。

回答

19

摘要Singleton?对我来说听起来不可行。 Singleton模式需要一个private构造函数,这已经使得子类化不可能。你需要重新考虑你的设计。 Abstract Factory pattern可能更适合于特定的目的。

+0

Singleton中的私有构造函数的目的是为了防止其他人实例化它。我已经在这里实现了 - 你不能实例化一个抽象类,并且子类有一个私有构造函数。 – Simon 2010-03-17 00:08:07

+0

它不会强制子类仅为抽象类型,只有私有构造函数。 – BalusC 2010-03-17 00:13:15

+0

Singleton不需要_require_一个私有构造函数(请参阅我的答案,以便用公共构造函数解决此问题)。 – ryvantage 2015-09-13 18:26:42

4

A.getInstance()将永远不会调用派生实例,因为它是静态绑定的。

我会从实际对象本身中分离对象的创建,并创建一个适当的factory返回一个特定的类类型。目前尚不清楚你是如何参数化的,给出你的示例代码 - 是通过某个参数进行参数化还是静态类选择?

你可能想重新考虑单身人士,顺便说一句。这是一个常见的反模式并且使得测试(特别是)痛苦,因为被测试的类将把它们自己的那个类的实例作为单例提供。您不能提供虚拟实现,也不能为每个测试(轻松)创建新实例。

+0

A.getInstance()不需要调用派生实例,因为它返回实例变量。派生实例(类B)设置实例变量。除了静态代码块没有运行。 如果我不能解决这个问题,那么Factory模式可能是我唯一的选择。 – Simon 2010-03-17 00:16:02

4

单身人士是一种yucky。摘要坚持继承,如果可能的话,你往往不想avoid。总的来说,我会重新考虑你所要做的是否是simplest possible way,如果是的话,那么一定要使用工厂而不是单身(单身人士很难在unit tests中替代,而工厂可以被轻松地替换测试实例)。

一旦你开始将它作为一个工厂来实现,抽象的东西就会自我排序(或者它显然是必要的,或者它可能容易取代接口)。

+0

+1指出单身人士是反模式。 – 2010-03-17 04:59:56

+1

单身人士也有自己的位置,但无论你在做什么,通常都会有更好的设计模式。日志可能是我可以为单身人士想到的最好例子 - 这样您就不必传递变量并可以从任何地方访问它。 – 2013-04-17 19:30:01

+0

注射可以使日志变得更好。单例日志不能很容易地告诉你该行来自哪个类,而Log l = new Log(this.getClass())或其某个变量可以。注射它只是@inject日志或类似的东西。即使是伐木,单身也很难测试。再次单身人士只是最糟糕的选择(但承认是可用的) – 2013-04-17 21:42:29

2

除了其他问题所指出的那样,具有Ainstance场意味着你只能在整个虚拟机一个单。如果你也有:

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

...然后取其BC被载入最后会成功,其他的单一实例都将丢失。

这只是一个不好的做事方式。

+0

谢谢 - 和其他人说同样的事情。这突出表明我真正的问题是我如何选择我应该公开的接口的几个实现中的哪一个。工厂或外观看起来很不错。 – Simon 2010-03-17 00:35:52

8

从你的文章中可以看出,即使没有明确说明,听起来像是你想要抽象类扮演两种不同的角色。 的角色是:

  • 的 (单)服务,它可以有 多个替代 实现抽象工厂角色,
  • 服务 接口的作用,

加上你想要的服务在整个系列的类中是单例执行的“单线态”,由于某些原因,缓存服务实例是不够的。

这很好。

有人会说它闻起来非常糟糕,因为“违反了关注点分离”和“单身和单元测试不一致”。

别人会说它是好的,因为你给家庭本身实例化正确的孩子的责任,也暴露更流利的界面整体,因为你不需要调解一个工厂,除了暴露静态方法。

什么是错误的是,你想让孩子们负责选择父厂方法应该返回什么样的实现。 这是错误的设计方面,因为你委托给所有的孩子什么可以简单地推到集中到抽象的超类,它也表明你正在混合在不同的上下文中使用的模式,抽象工厂(父母决定什么家庭班级客户将获得)和工厂方法(儿童工厂选择客户将得到什么)。

工厂方法不仅不是必需的,而且工厂方法也是不可能的,因为它集中于实现或重写“实例”方法。对于静态方法和构造函数都没有这样的重写。

因此,回到抽象单例的初始好的或坏的想法,选择哪种行为暴露有几种方法来解决初始问题, 可能是以下几种方式,看起来不好,但我想接近什么您正在查找:

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()和将缓存子实例强制客户端始终获得相同的服务实例。