java version 1.7.0_65
我有一个单例设计模式类。这将始终返回最初创建的同一实例。
但是,我遇到的问题是这个类需要从另一个类创建许多其他对象。我已经使用了合成(POI
中的ArlabFacade
类的实例)。从这个单例实例中,客户端应该能够创建许多POI对象。而且我不想公开POI类的内部工作,一切都必须通过单例实例。
private static ArlabFacade mArlabFacade = null;
private POI mPoi; /* Should be able to create many object of this class */
private ArlabFacade(Context context) {
/* initialize properties */
mContext = context;
mPoi = null;
}
public static ArlabFacade getInstance(Context context) {
/* Create a synchronised singleton instance */
ReentrantLock lock = new ReentrantLock();
lock.lock();
if(mArlabFacade == null) {
mArlabFacade = new ArlabFacade(context);
}
lock.unlock();
return mArlabFacade;
}
我尝试过这样的事情,但它有两个问题。
1) I don't want to return the class instance of POI
2) because I only have a single instance the mPoi will be overwritten by the next client call to this function.
此功能只会覆盖:
public POI createNewPOI() {
return mPoi = new POI();
}
是否有任何设计模式可以解决这个问题?
答案 0 :(得分:6)
我有点困惑你到底想要达到的目标。 看起来就像你想要一个工厂,即一个隐藏如何创建某个类的对象的类。除非你有其他原因,否则在这种情况下不需要单身人士。
关于你的问题:
您也不是返回class
实例,而是返回该类的对象。我认为这就是重点:创建POI对象蚂蚁返回它。我猜这个命名法有些混乱,所以请解释一下你的实例,以及为什么你不想归还它。
在工厂方法createNewPOI()
中,您只是覆盖对最后创建的POI对象的引用,而不是对象本身。除非您的工厂类(或您的Singleton)正在使用POI对象本身,否则无需保留引用。您将对象返回给方法的调用者。之后你可以忘掉它:
public POI createNewPOI() {
return new POI();
}
您的代码还有一个问题:您在getInstance()
方法中的锁定将无效。要让ReentrantLock完成其工作,必须在多个线程之间共享。在你的情况下,每个线程都会创建自己的锁副本,而不知道其他的主题。
最简单的方法是让方法同步:
public static synchronized ArlabFacade getInstance(Context context) {
if(mArlabFacade == null) {
mArlabFacade = new ArlabFacade(context);
}
return mArlabFacade;
}
答案 1 :(得分:6)
看看:What is so bad about singletons?
如果您有理由,您应该只使用代码模式。例如:opular模式和使用它们的原因是:
创作模式
- 抽象工厂创建多个类系列的实例
- 构建器将对象构造与其表示分开
- 工厂方法创建多个派生类的实例
- 原型要复制或克隆的完全初始化的实例
- Singleton 只有一个实例可以存在的类
结构模式
- 适配器匹配不同类别的接口
- Bridge 将对象的界面与其实现分开
- 复合简单和复合对象的树结构
- 装饰器动态添加对象
- Facade 代表整个子系统的单个类
- Flyweight 用于高效共享的细粒度实例
- 代理表示其他对象的对象
行为模式
- 响应链。在对象链之间传递请求的方法
- 命令将命令请求封装为对象
- 口译员在程序中包含语言元素的方法
- 迭代器按顺序访问集合的元素
- Mediator 定义类之间的简化通信
- Memento 捕获并恢复对象的内部状态
- 观察者一种通知更改多个类的方法
- 状态更改对象在状态更改时的行为
- 策略将算法封装在类
中- 模板方法将算法的确切步骤推迟到子类
- 访问者定义不更改的类的新操作
来源:dofactory
答案 2 :(得分:3)
非常简单,但首先请注意,必须在静态上下文中创建锁,以便每个线程都使用相同的锁实例(如果为每个线程使用不同的实例,则根本不会有同步)
现在,这里是代码
public class ArlabFacade {
private static ArlabFacade mArlabFacade = null;
/* Create a synchronised singleton instance */
private static final ReentrantLock lock = new ReentrantLock();
private ArlabFacade(Context context) {
/* initialize properties */
mContext = context;
}
public static ArlabFacade getInstance(Context context) {
lock.lock();
if(mArlabFacade == null) {
mArlabFacade = new ArlabFacade(context);
}
lock.unlock();
return mArlabFacade;
}
public NotAPOI createNewPOI() {
return new NotAPOIImpl(new POI());
}
public void doSomething(NotAPOI val) {
if(!(val instanceof NotAPOIImpl)) {
throw new IllegalArgumentException("Illegal implementation of NotAPOI");
}
NotAPOIImpl impl = (NotAPOIImpl) val;
POI poi = val.poi;
// do something with poi here
}
private static class NotAPOIImpl implements NotAPOI {
private POI poi;
private NotAPOIImpl(POI poi) {
this.poi = poi;
}
}
}
// As you don't like to expose the POI just hide it behind the interface
public interface NotAPOI {
}
另一种可能的解决方案是允许通过一个更高级别的抽象来处理POI,这更加优雅
public class ArlabFacade {
private static ArlabFacade mArlabFacade = null;
/* Create a synchronised singleton instance */
private static final ReentrantLock lock = new ReentrantLock();
private ArlabFacade(Context context) {
/* initialize properties */
mContext = context;
}
public static ArlabFacade getInstance(Context context) {
lock.lock();
if(mArlabFacade == null) {
mArlabFacade = new ArlabFacade(context);
}
lock.unlock();
return mArlabFacade;
}
public NotAPOI createNewPOI() {
return new NotAPOIImpl(new POI());
}
private static class NotAPOIImpl implements NotAPOI {
private POI poi;
private NotAPOIImpl(POI poi) {
this.poi = poi;
}
public void doSomething() {
poi.doSomething();
}
}
}
// As you don't like to expose the POI just hide it behind the interface
public interface NotAPOI {
void doSomething();
}
答案 3 :(得分:2)
如果我理解正确,您希望所有调用者都获得相同的单例类,但每个调用者都要使用自己的POI对象。但是这个POI对象应该隐藏在Singleton-Class中。
你可以这样做:
每个Callsite / Client首先必须致电ArlabFacade.getInstance().generateToken()
,以便获得一个唯一的令牌,因此他会获得一个POI保留供使用。当他完成后,他应该调用releaseToken()
private static ArlabFacade mArlabFacade = null;
private HashMap<String, POI> poiMap = new ConcurrentHashMap<>();
private ArlabFacade(Context context) {
/* initialize properties */
mContext = context;
}
public static synchronized ArlabFacade getInstance(Context context) {
/* Create a synchronised singleton instance */
if(mArlabFacade == null) {
mArlabFacade = new ArlabFacade(context);
}
return mArlabFacade;
}
private AtomicInteger uniqueStringCounter = new AtomicInteger(0);
public String createUniqueString() {
return "TOKEN"+uniqueStringCounter.getAndIncrement();
}
public String generateToken() {
String token = createUniqueString();
poiMap.add(token, new POI());
}
public void releaseToken(String token) {
poiMap.remove(token);
}
public void doStuffOnPOI(String token, int someParameter) {
POI mPoi = poiMap.get(token);
mPoi.doStuff(someParam);
}
答案 4 :(得分:1)
在java中编写延迟加载(按需)单例时,您必须注意一些问题:
在多线程环境中,双重检查锁定模式被视为不安全。
/*
* unsafe and broken Double-Checked Locking pattern
*/
public class ArlabFacade {
private ArlabFacade mArlabFacade;
public ArlabFacade getInstance() {
if (mArlabFacade == null) {
synchronized(this) {
if (mArlabFacade == null) {
mArlabFacade = new ArlabFacade(...);
}
}
}
return mArlabFacade;
}
}
由于语言规范中描述的 Java Memory Model ,双重检查锁定模式很糟糕。 见以下链接:
http://en.wikipedia.org/wiki/Double-checked_locking
从这个角度来看,安全有效的模式被称为按需初始化持有者,如下所示:
来自wiki的片段:&#34; [...]在所有版本的Java中,这个习惯用法可以实现安全,高度并发的延迟初始化,具有良好的性能[...] &#34 ;
请参阅:
http://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
/*
* fully safe and performant holder pattern
*/
public class ArlabFacade {
private static class Holder {
private ArlabFacade instance;
private List<POI> mPOIs;
static {
instance = new ArlabFacade();
mPOIs = new ArrayList();
mPOIs.add( new POI(...) );
mPOIs.add( new POI(...) );
....
}
}
public static ArlabFacade getInstance() {
return Holder.instance;
}
}
上面的持有者模式保证安全和高效因为静态类持有者只加载一次(JVM规范)并且懒惰 - 只有在第一次调用getInstance()时才会这样。