当包含类是单例模式时,使用合成创建许多对象

时间:2014-09-19 03:36:05

标签: java singleton single-instance object-composition

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();
    }

是否有任何设计模式可以解决这个问题?

5 个答案:

答案 0 :(得分:6)

我有点困惑你到底想要达到的目标。 看起来就像你想要一个工厂,即一个隐藏如何创建某个类的对象的类。除非你有其他原因,否则在这种情况下不需要单身人士。

关于你的问题:

  1. 您也不是返回class实例,而是返回该类的对象。我认为这就是重点:创建POI对象蚂蚁返回它。我猜这个命名法有些混乱,所以请解释一下你的实例,以及为什么你不想归还它。

  2. 在工厂方法createNewPOI()中,您只是覆盖对最后创建的POI对象的引用,而不是对象本身。除非您的工厂类(或您的Singleton)正在使用POI对象本身,否则无需保留引用。您将对象返回给方法的调用者。之后你可以忘掉它:

  3. 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

http://www.javaworld.com/article/2074979/java-concurrency/double-checked-locking--clever--but-broken.html

从这个角度来看,安全有效的模式被称为按需初始化持有者,如下所示:

来自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()时才会这样。