1

I have a Storage class, which can be initialized with user-defined types such as CD, DVD etc. How do I ensure that there is only one instance of CD, DVD or any other storage type?

The user can do something like this:

DataStore dataStoreCD = new DataStore("CD");
DataStore dataStoreDVD = new DataStore("DVD");
DataStore dataStoreHDD = new DataStore("HDD");

dataStoreCD.write(data);
dataStoreDVD.write(data);
dataStoreHDD.write(data);

I looked at the singleton and the factory design patterns. Singleton will not work in my case. The factory pattern will also not work because the user can create multiple instances of the same storage type.

I tried implementing a singleton pattern and have the user call an Initialize method. But this also will not work.

EDIT 1: My question is, how do I design the Storage class such that it accommodates any storage type and at the same time there is only one instance of each?

These are the various sites I referred to, before posting my question here. Combine-abstract-factory-with-singleton; Inversion-of-Control; Tutorial on factory pattern; Singleton-with-arguments (which is a bad idea and not singleton at all, as per the discussions); Singleton-with-non-default-constructor

Community
  • 1
  • 1
Sarvavyapi
  • 720
  • 2
  • 20
  • 31
  • 6
    Instead of worrying about software patterns and all this architecture ceremony, how about coming up with a simple, sensible class design? – Robert Harvey Jan 11 '14 at 00:29
  • I have edited the question to make it clearer. – Sarvavyapi Jan 11 '14 at 00:37
  • 1
    Yes, that is better. Have you looked at the Multiton pattern? – Robert Harvey Jan 11 '14 at 00:39
  • No, I am new to design patterns and am aware of only singleton and factory. But I will take a look at Multiton. – Sarvavyapi Jan 11 '14 at 00:39
  • 1
    @Sarvavyapi think how would you like to use the code. For example calling your code like this: DataStore.write(data, DataStoreType.DVD) how does it seem to you? ok, nok? Or like this DataStore.write(data, typeof(DVDDataStore)) ? – João Pinho Jan 11 '14 at 00:40
  • I think it is not ok, because the user has to specify the type everytime a write or a read needs to happen. The model where the user specifies the type during initialization and not having to bother about it during I/O operations seems better. – Sarvavyapi Jan 11 '14 at 00:43
  • ok, ok. so tell me something, data is of what type? can I assume that data could be an instance of DVDDataStore or CDDataStore or ... ? – João Pinho Jan 11 '14 at 00:44
  • If I understand your question correctly, no, the data will not be an instance of DVD or CD or HDD. It can be anything and can be written to CD, DVD or HDD (i.e) common to any storage type. – Sarvavyapi Jan 11 '14 at 00:47
  • one more question do you like latter to do something like, DataStore.FindBySerialNumber("some id") and it returns you something like DataStore, that could be of the type DVDDataStore or CDDataStore?! – João Pinho Jan 11 '14 at 00:47
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/44988/discussion-between-sarvavyapi-and-joao-pinho) – Sarvavyapi Jan 11 '14 at 00:48
  • @Sarvavyapi did you compiled it? – João Pinho Jan 11 '14 at 20:16
  • Thanks for your response, but I will be implementing my own version of the suggestions offered here, test it and then will accept the right answer. – Sarvavyapi Jan 13 '14 at 20:25

2 Answers2

2

I've read your comments and chat, and my solution is use Factory for choosing DataStore implementation, where all implementations has IDataStore interface. Check code below

The 'engine':

public interface IDataStore
    {
        void Write(Data data);
        Data Read();
    }

    public class DataStoreCd : IDataStore
    {
        private static DataStoreCd _instance;

        private DataStoreCd()
        {
        }

        public static DataStoreCd Instance
        {
            get { return _instance ?? (_instance = new DataStoreCd()); }
        }

        public void Write(Data data)
        {
            // implementation
        }

        public Data Read()
        {
            // implementation
        }
    }


    public enum StorageType
    {
        Cd = 0,
        Dvd = 1,
        Hdd = 2
    }

    public class DataStoreFactory
    {
        private readonly Dictionary<StorageType, IDataStore> _dataStoresDictionary;

        public DataStoreFactory()
        {
            _dataStoresDictionary = new Dictionary<StorageType, IDataStore>
                {
                    {StorageType.Cd, DataStoreCd.Instance},
                    {StorageType.Dvd, DataStoreDvd.Instance},
                    {StorageType.Hdd, DataStoreHdd.Instance},
                };
        }

        public IDataStore CreateFor(StorageType storageType)
        {
            return _dataStoresDictionary[storageType]; //if you don't have storage type in dictionary
                                            // will throw an exception. You can change it to switch,
                                            // or some better solution
        }
    }

The executable class:

public class DataStoreExecutable
{
    private DataStoreFactory _dataStoreFactory;

    public void Write(Data data, StorageType storageType)
    {
        IDataStore dataStore = _dataStoreFactory.CreateFor(storageType);
        dataStore.Write(data);
    }
}

Sample usage:

public class Usage
{
    public void TestUsage()
    {
        var dataStoreExecutable = new DataStoreExecutable();
        var data = new Data();

        dataStoreExecutable.Write(data, StorageType.Cd);
    }
}
michalczukm
  • 8,196
  • 5
  • 35
  • 47
2

Your DataStore could have a private constructor (or protected, ask me why if didn't get it), so no one besides you can instantiate it, then in the static constructor create an instance of DataStore (this is the singleton creation)

Your CD, DVD, HDDVD, etc inherit from MediaStorage. Your DataStore instance, will hold a List

When you want a new media to write to, you write: DataStore.Create(typeof(DVD)) this method adds a new instance of DVD to the List a new call to DataStore.Create(typeof(DVD)) would return an InvalidOperationException("DVD currently being written/read, please wait"

OR you could implement a lock mechanism, so that the thread calling this code would be locked

Let me elaborate the DataStore.Create

DataStore.Create(typeof(DVD)) would do something like: dvdMediaMutex.Lock(); MediaStorage obj = (instanciation of media of type passed as parameter) return obj //casting to the passed type

Don't worry with the language, we are trying to find a pattern for the problem

So assuming create, creates a lock over that media type that media type will be lock... new call to Create will be putted on hold now, because Create returns you an instance you can write to your file calling obj.Write this method could be generic being declared in the MediaStorage, MediaStorage could be abstract so that some methods are implemented and other are not...

Left to the specific class programmer to implement when you are done with the class we have to release that lock so, for that you DataStore can have a method called Dispose() that receives MediaStorage and it's role is to release the Lock so other can write when release the first thread to enter closes the lock so now the new thread is writing and the other are waiting again...

So the DataStore here implements the Singleton the Singleton almost implements the Composite but because DataStore is different type from MediaStorage it's just similar at any time if you loose the reference to your object

That would do

foreach(MediaStorage md in mediaStorages) 
    if(md is the_type_passed) return md;

you would call DataStore.Get(typeof(DVD))

This envolves some threading handling and type instantiation ... seems fun!

Let's see it in action, have done you a working sample. It reflects what we have spoken earlier only! It works, and I hope it solves your problem.

Notes: I develop the code in a Class Library and then added it has reference to my Console app for testing.

The MediaStorage Class

namespace MediaLibrary
{
    public abstract class MediaStorage
    {
        internal readonly Mutex OpenLock;
        internal readonly Mutex AccessLock;

        public MediaStorage() {
            OpenLock = new Mutex(false, string.Format("{0} - OpenMutex",        
                this.GetType().FullName));
            AccessLock = new Mutex(false, string.Format("{0} - OpenMutex",   
                this.GetType().FullName));
            Data = new StringBuilder();
        }

        public StringBuilder Data { get; set; }
    }
}

The DataStore Class

namespace MediaLibrary
{
    public class DataStore
    {
        #region "Static Class Logic"
        static DataStore instance;

        public static DataStore Instance {
            get {
                return instance = instance ?? new DataStore();
            }
        }
        #endregion

        private readonly object openLock;
        private readonly object closeLock;
        Dictionary<Type, MediaStorage> mediaStorage;

        private DataStore() {
            openLock = new Object();
            closeLock = new Object();
            mediaStorage = new Dictionary<Type, MediaStorage>();
        }

        public MediaStorage Open(Type type) {
            lock (openLock) {
                MediaStorage md = null;
                bool alreadyCreated = mediaStorage.TryGetValue(type.GetType(), out md);

                //has never been created.
                if (md == null) {
                    md = (MediaStorage)Activator.CreateInstance(type, true);
                    mediaStorage.Add(type.GetType(), md);
                }

                //created or pulled from the dictionary, let's try to to get the object lock.

                 md.OpenLock.WaitOne(-1);

                //success, object obtained, don't forget everyone is blocked trying to get it,
                //release as soon as possible.
                return md;
            }
        }

        public void WriteData(Type type, string data) {
            //i assume the media is never removed if it's please protect this 
            //call with lock mechanism.
            MediaStorage media = GetMedia(type);

            lock (media.AccessLock) {
                //simulate some delay
                Thread.Sleep(1000);
                media.Data.AppendFormat(
                    "thread:{0},data:{1}|\n", Thread.CurrentThread.ManagedThreadId, data);
            }
        }

        public string ReadData(Type type) {
            //i assume the media is never removed if it's please protect 
            //this call with lock mechanism.
            MediaStorage media = GetMedia(type);
            string data;

            lock (media.AccessLock) {
                //simulate some delay
                Thread.Sleep(new Random(Convert.ToInt32(DateTime.Now.Ticks)).Next(2000));
                data = media.Data.ToString();
            }

            return data;
        }

        private MediaStorage GetMedia(Type mediaType) {
            MediaStorage md = null;
            mediaStorage.TryGetValue(mediaType.GetType(), out md);
            return md;
        }

        public void Close(MediaStorage media) {
            lock (closeLock) {
                if (media.OpenLock.SafeWaitHandle.IsClosed) return;
                media.OpenLock.ReleaseMutex();
            }
        }
    }
}

The CDMedia Concrete Type Class, can implement DVD, HDDVD, etc...

namespace MediaLibrary
{
    public class CDMedia: MediaStorage
    {
        private CDMedia() { }

        //anything specific of CD's
    }
}

The Console App Code to Unit Test

    static void Main(string[] args) {
        DataStore storage = DataStore.Instance;
        Console.WriteLine("Main program waking up...\n\n");

        Console.WriteLine("Launching Thread of Writers...");
        Thread writters1 = new Thread(new ThreadStart(delegate() {
            Console.WriteLine("Writters thread, openning media...\n");
            CDMedia cd = (CDMedia)storage.Open(typeof(CDMedia));

            Thread writer1 = new Thread(new ThreadStart(delegate() {
                Console.WriteLine("Writters thread, writer 1 writting...");
                //safe write
                storage.WriteData(typeof(CDMedia), "DataWriter 1, I love to write data all day long, specially on sunny days..."); 
            }));

            Thread writer2 = new Thread(new ThreadStart(delegate() {
                Console.WriteLine("Writters thread, writer 2 writting...");
                //safe write
                storage.WriteData(typeof(CDMedia), "DataWriter 2, I love to write data all day long, specially on windy days...");
            }));

            Thread writer3 = new Thread(new ThreadStart(delegate() {
                Console.WriteLine("Writters thread, writer 3 writting...");
                //safe write
                storage.WriteData(typeof(CDMedia), "DataWriter 3, I love to write data all day long, specially on rainy days...");
            }));

            writer1.Start();
            writer2.Start();
            writer3.Start();

            writer1.Join();
            writer2.Join();
            writer3.Join();

            //all done release media
            Console.WriteLine("\nWritters thread, ended, releasing resource\n");
            storage.Close(cd);
        }));

        Console.WriteLine("Launching Thread of Writers 2...");
        Thread writters2 = new Thread(new ThreadStart(delegate() {
            Console.WriteLine("Writters thread, openning media...\n");
            CDMedia cd = (CDMedia)storage.Open(typeof(CDMedia));

            Thread writer1 = new Thread(new ThreadStart(delegate() {
                Console.WriteLine("Writters thread, writer 4 writting...");
                //safe write
                storage.WriteData(typeof(CDMedia), "DataWriter 4, I love to write data all day long, specially on sunny days...");
            }));

            Thread writer2 = new Thread(new ThreadStart(delegate() {
                Console.WriteLine("Writters thread, writer 5 writting...");
                //safe write
                storage.WriteData(typeof(CDMedia), "DataWriter 5, I love to write data all day long, specially on windy days...");
            }));

            Thread writer3 = new Thread(new ThreadStart(delegate() {
                Console.WriteLine("Writters thread, writer 6 writting...");
                //safe write
                storage.WriteData(typeof(CDMedia), "DataWriter 6, I love to write data all day long, specially on rainy days...");
            }));

            writer1.Start();
            writer2.Start();
            writer3.Start();

            writer1.Join();
            writer2.Join();
            writer3.Join();

            //all done release media
            Console.WriteLine("\nWritters thread, ended, releasing resource\n");
            storage.Close(cd);
        }));

        writters1.Start();
        writters2.Start();

        writters1.Join();
        writters2.Join();

        Console.WriteLine("Main program, let's check CDMedia Content shall we?!\n");

        CDMedia cdMedia = (CDMedia)storage.Open(typeof(CDMedia));
        Console.WriteLine("CD content: \n" + cdMedia.Data.ToString() + "\n");
        storage.Close(cdMedia);

        Console.WriteLine("The End!");
        Console.ReadKey();
    }

Regards.

João Pinho
  • 3,460
  • 1
  • 15
  • 27