Singleton returning two instances

风流意气都作罢 提交于 2019-12-25 12:51:24

问题


I'm trying to use a singleton (PhotoStorage) to provide an arrayList of Photo objects, but it seems that the PhotoStorage instance is not behaving as a singleton (two instances).

I am using dagger to inject this singleton into a class named PhotoInteractor. The objectGraph seems A-OK up to this point. The same PhotoInteractor instance is used in three fragments in a viewpager. These fragments are all instantiated at runtime:

RecentFragment:

HistoryFragment:

Notice how the instance @4067 of the PhotoInteractor is the same for both fragments.

Also:

  • mAppContext@4093: same
  • photoStorage@4094: same

When I click a photo object (grid image) from RecentFragment, the PhotoStorage.addPhoto(url) method is called. This correctly adds the photo object to the photoStorage instance array (4094). That much is OK.

Problem:

When I close the applicaton, it is intended that the PhotoStorage.savePhotosToFile method serialzes this arrayList object into JSON on the filesystem.

The following method is called from the same PhotoInteractor instance:

@Override
public void savePhotos(){
    photoStorage.get(mAppContext).savePhotosToFile();
}

When I debug the application, the PhotoStorage.get method already has a singleton instance, but what appears to be a 2nd instance!

//Singleton enforcement
public static PhotoStorage get(Context c){
    if(sPhotoStorage == null){
        sPhotoStorage = new PhotoStorage(c.getApplicationContext());
    }
    return sPhotoStorage;
}

This means that the ArrayList of photos will always be empty since it is a new instance of PhotoStorage. I’m not sure where it is instantiating itself from.

Edit - Added PhotoStorage.class:

public class PhotoStorage{
    private ArrayList<Photo> mPhotos;
    private PhotoJSONer mSerializer;
    private static PhotoStorage sPhotoStorage;
    private static Context mAppContext;
    private static final String PHOTOS_DATABASE = "photos.json";
    public static final String TAG = PhotoStorage.class.getSimpleName();

public PhotoStorage(Context appContext){
    mSerializer = new PhotoJSONer(appContext, PHOTOS_DATABASE);
    try{
        mPhotos = mSerializer.loadPhotos();
    }catch(Exception e){
        mPhotos = new ArrayList<Photo>();
    }
}

//Singleton enforcement
public static PhotoStorage get(Context c){
    if(sPhotoStorage == null){
        sPhotoStorage = new PhotoStorage(c.getApplicationContext());
    }
    return sPhotoStorage;
}

public ArrayList<Photo> getPhotos(){
    return mPhotos;
}

public Photo getPhoto(String url){
    for(Photo p: mPhotos){
        if(p.getUrl() == url)
            return p;
    }
    return null;
}

public void deletePhoto(String url){
    Log.i(TAG, "deleted photo");
    mPhotos.remove(url);
}

public void addPhoto(Photo photo){
    Log.i(TAG, "added photo");
    mPhotos.add(photo);
}

public boolean savePhotosToFile(){
    try{
        mSerializer.savePhotos(mPhotos);
        return true;
    }catch (Exception e){
        return false;
    }
}

}


回答1:


You are not executing Singletton pattern in the correct way,

The Singleton design pattern addresses all of these concerns. With the Singleton design pattern you can:
Ensure that only one instance of a class is created
Provide a global point of access to the object

In your case, we don't see PhotoStorage class but this call comes from an instance, what is not allowed by Singletton pattern:

photoStorage.get(mAppContext).savePhotosToFile();
//↑ instance call WRONG!!

This line works, but as your get method is static is not a good practice as Karakuri pointed and also breaks the Singletton pattern definition.

public static PhotoStorage get(Context c){

SOLUTION
To make photoStorage.get() invalid and create a correct Singletton pattern you must:

  • declare the getInstance() method static in the class (here PhotoStorage)
  • hide default constructor to avoid instances of the class
  • create private constructors if necessary
  • call getInstance() it in a static way:

class PhotoStorage { 
    // hidding default constructor
    private PhotoStorage () {};

    // creating your own constructor but private!!!
    private PhotoStorage(Context appContext){
        mSerializer = new PhotoJSONer(appContext, PHOTOS_DATABASE);
        try{
            mPhotos = mSerializer.loadPhotos();
        }catch(Exception e){
            mPhotos = new ArrayList<Photo>();
        }
    }

    //Singleton enforcement
    public synchronized static PhotoStorage get(Context c){
        if(sPhotoStorage == null){
            sPhotoStorage = new PhotoStorage(c.getApplicationContext());
        }
        return sPhotoStorage;
    }
}

Then you can make static call from everywhere the class scope allows:

@Override
public void savePhotos(){
      PhotoStorage.get(mAppContext).savePhotosToFile();
    //↑ static call CORRECT!!
}

UPDATE:
if your app have several threads and singleton getInstance requests may overlapp, there is a double check syncronized singletton pattern you can apply:

//Singleton enforcement
public synchronized static PhotoStorage get(Context c){
    if(sPhotoStorage == null){
        synchronized(PhotoStorage.class) {
            if(sPhotoStorage == null) {
                sPhotoStorage = new PhotoStorage(c.getApplicationContext());
            }
        }
    }
}


来源:https://stackoverflow.com/questions/32096806/singleton-returning-two-instances

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!