Static vs Singleton
- Static class is implicitly abstract, meaning a static class object can neither be created nor instantiated whereas singleton pattern allows us to create a single (only one) instance of a class.
- Static class is a stateless class whereas singleton class is state full
- Static class instance is created whenever it is first accessed and becomes accessible throughout the application or per appdomain whereas Singleton pattern provides flexibility that allows us to control instantiation based on the conditions; for example, instance of singleton class can be created per context like single instance of ShoppingCart can be created per customer
- Data stored in the static variable or class is shared and accessed globally throughout the application whereas singleton instance created per context is shared and accessible per context meaning data stored with in the instance created in context will not be shared or accessible from instance created in another context for example items stored in ShoppingCart will be shared and accessible among different login session of a customer but will not be accessible to another customer.
- Static class cannot be inherited or derived from any other class whereas singleton can implement interfaces and inherit from another class.
- Static class cannot be passed as a parameter to any method whereas instance of singleton class can be passed as a parameter to a method or constructor of another class.
- Only static members/fields/properties can be defined within the static class whereas singleton class allows both static and non-static members/fields/properties.
We will now look at and understand the below mentioned use case implementation that demonstrates all of the above facts.
Use Case
Let’s think about the e-commerce online web portal to which thousands of customer already have subscribed. There could be a scenario when several hundred people login at the same time out of which a few people login in to the web portal from multiple applications; i.e., web portal, mobile app or background processes then logged in customers search and add items into their shopping cart. Now think of those customers who have logged in from multiple applications and add items into the shopping cart from multiple applications.
Now the request is to provide a solution that every customer should be able to view all the items of their own private shopping cart but a specific user should be able to view all the items added in the shopping cart from different applications.
Solution
First of all we will implement a solution to have different instances of shopping cart per customer so we need three entities in the solution as mentioned below:
- ShoppingCart class that represents shopping cart. This class has a private field to store items added into the shopping cart.
- Customer class that represents a customer.
- LoggedinCustomers class that stores list a list of logged in customers
Is it possible to create instance of shopping cart per customer with static class implementation? The answer is No because static class is implicitly abstract meaning Static class object can neither be created nor instantiated. .Net CLR implicitly creates one instance of static class internally whenever it is first accessed and the same instance is shared among all other objects in an application.
Now the question arises – Is it possible to create an instance of shopping cart per customer with singleton pattern implementation? The answer is YES, we can use or implement Singleton pattern to restrict one instance created per customer, meaning instance of shopping cart that will be created for customer one will be different from instance created for customer two. Then later the same instance of shopping cart can be passed to different login sessions of customer.
Let’s follow the below mentioned steps to have this implementation in place,
- Implement a Singleton pattern in the ShoppingCart class that ensures or restricts that only one instance of shopping cart is created.
- To meet our requirement of one instance per customer, add a Boolean flag to determine whether we need to create an instance of ShoppingCart or not.
- Declare a collection object variable of type List to store items added into the Shopping Cart.
-
-
-
-
-
- public sealed class ShoppingCart
- {
- private static volatile ShoppingCart _instance;
- private static readonly object SyncRoot = new Object();
- private readonly List<string> _itemsAdded = new List<string>();
-
-
-
-
-
- private ShoppingCart() { }
-
-
-
-
-
-
- public static ShoppingCart Instance(bool bCreateInstance)
- {
- if ((_instance == null) || (bCreateInstance == true))
- {
- lock (SyncRoot)
- {
- if ((_instance == null) || (bCreateInstance == true))
- _instance = new ShoppingCart();
- }
- }
- return _instance;
- }
-
-
-
-
-
- public void AddItemToShoppingCart(string itemCode)
- {
- _itemsAdded.Add(itemCode);
- }
-
-
-
-
-
- public List<string> GetShoppingCartItems()
- {
- return _itemsAdded;
- }
- }
- Now define a Customer class with a field to store an instance of ShoppingCart.
- Within the constructor of customer class create an instance of ShoppingCart class.
- Within the Customer class define a method “AddItemToShoppingCart” to add item into the ShoppingCart and method “PrintShoppingCartItem” to print all the items currently added into the shopping cart
-
-
-
- public class Customer
- {
-
- private readonly ShoppingCart _shoppingCartInstance = null;
-
- #region Properties
-
-
-
-
- public string CustomerName { get; set; }
-
- #endregion
-
- #region Constructor
-
-
-
-
-
- public Customer(string customerName)
- {
- CustomerName = customerName;
- _shoppingCartInstance = ShoppingCart.Instance(true);
- }
- #endregion
-
-
-
-
-
-
- public void AddItemToShoppingCart(string itemCode)
- {
- _shoppingCartInstance.AddItemToShoppingCart(itemCode);
- }
-
-
-
-
- public void PrintShoppingCartItem()
- {
- List<string> itemsAdded = _shoppingCartInstance.GetShoppingCartItems();
-
- StringBuilder itemsAddedToShoppingCart = new StringBuilder();
- foreach (var item in itemsAdded)
- {
- if (itemsAddedToShoppingCart.Length > 0)
- itemsAddedToShoppingCart.Append(", ");
-
- itemsAddedToShoppingCart.Append(item);
- }
- Console.WriteLine("Items Added in {0}'s Cart : {1}", CustomerName, itemsAddedToShoppingCart.ToString());
- }
- }
- Now define a class named LoggedinCustomers.
- Define a field “_customers” within the LoggedinCustomers class which contains a collection of logged in Customer objects. Defined a method named “AddCustomer” to add a customer into the collection object field. Defined a method named “” to get the list of customer objects stored in the collection object
- public class LoggedinCustomers
- {
- private readonly List<Customer> _customers = new List<Customer>();
-
-
-
-
-
-
- public Customer GetCustomer(string customerName)
- {
- Customer customer = _customers.FirstOrDefault(x => x.CustomerName == customerName);
- return customer;
- }
-
-
-
-
-
- public void AddCustomer(Customer customer)
- {
- _customers?.Add(customer);
- }
-
-
-
-
-
- public List<Customer> GetCustomers()
- {
- return _customers;
- }
- }
Now write a program that instantiates an instance of Customer class for different users whereas retrieves an existing instance of customer class from a list of logged in customers maintained. This way we will be able to create different instances of shopping cart per customer and add item into the shopping cart.
Code Snippet
- public static void StartProcess()
- {
- LoggedinCustomers loggedinCustomers = new LoggedinCustomers();
- while (true)
- {
- Console.Clear();
- PrintLoggedinCustomers(loggedinCustomers);
-
- Console.Write("Enter 1 to login or 0 to logout from the application: ");
- string input = Console.ReadLine();
- if (input == "1")
- {
- var currentCustomer = LoginCustomer(loggedinCustomers);
-
- Console.WriteLine("{0} logged in sucessfully!! Let's Start Shopping ", currentCustomer.CustomerName);
- StartShopping(currentCustomer);
- }
- else
- {
- break;
- }
- }
- }
-
- private static Customer LoginCustomer(LoggedinCustomers loggedinCustomers)
- {
- Console.Write("Enter Customer Name to login: ");
- string customerName = Console.ReadLine();
-
- var currentCustomer = loggedinCustomers.GetCustomer(customerName);
-
- if (null == currentCustomer)
- {
- currentCustomer = new Customer(customerName);
- loggedinCustomers.AddCustomer(currentCustomer);
- }
- return currentCustomer;
- }
-
- private static void PrintLoggedinCustomers(LoggedinCustomers loggedinCustomers)
- {
- var customers = loggedinCustomers.GetCustomers();
- if (0 >= customers.Count)
- {
- Console.WriteLine("No Customer is currently logged in to the application");
- }
- else
- {
- foreach (Customer customer in customers)
- {
- Console.WriteLine("{0} is currently logged in", customer.CustomerName);
- customer.PrintShoppingCartItem();
- }
- }
- }
-
- private static void StartShopping(Customer customer)
- {
- Console.WriteLine("Enter 0 to logout from the application");
- Console.WriteLine("Enter 1 to Print Shopping Cart Item");
- Console.WriteLine("Enter 2 to add item into the shopping cart");
- string line;
- while ((line = Console.ReadLine()) != "0")
- {
- if (line == "1")
- {
- customer.PrintShoppingCartItem();
- }
- else if (line == "2")
- {
- Console.WriteLine("Enter 9 to Stop Shopping else add Item Name to Add into the shopping cart else");
- Console.WriteLine("");
- string itemCode;
- while ((itemCode = Console.ReadLine()) != "9")
- {
- customer.AddItemToShoppingCart(itemCode);
- }
-
- Console.WriteLine("Enter 0 to logout from the application");
- Console.WriteLine("Enter 1 to Print Shopping Cart Item");
- Console.WriteLine("Enter 2 to add item into the shopping cart");
- }
- }
- }
Output
Two customers named “User1” and “User2” logged into the application. Both of them added different items into the shopping cart and we can view their shopping cart in the below image.
Now we will make the changes to share the same instance for all the login session of a customer. For this we will require CustomerLoginSession class that represents a login session for a customer. A customer can login from different platforms or applications i.e., Web Application or Mobile Application
Let’s follow the below mentioned steps to have this implementation in place:
- Define a class named “CustomerLoginSession” to represent customer login session. This class will have a field named “_shoppingCartInstance” of type ShoppingCart to hold a reference of ShoppingCart instance created in Customer Class. Customer Class will pass an object as a parameter to the constructor of CustomerLoginSession class.
- Within the CustomerLoginSession class define a method “AddItemToShoppingCart” to add item into the ShoppingCart.
- public class CustomerLoginSession
- {
-
- private readonly ShoppingCart _shoppingCartInstance = null;
- private readonly string _customerName;
- public string ApplicationId { get; set; }
-
-
-
-
-
-
-
- public CustomerLoginSession(ShoppingCart instance, string customerLoginApplicationId, string customerName)
- {
- _shoppingCartInstance = instance;
- ApplicationId = customerLoginApplicationId;
- _customerName = customerName;
- }
-
-
-
-
-
- public void AddItemToShoppingCart(string itemCode)
- {
- _shoppingCartInstance.AddItemToShoppingCart(itemCode);
- Console.WriteLine("{0} added into {1} Cart from {2} ", itemCode, _customerName, ApplicationId);
- }
- }
- Define a field “_customerLoginSessions” within the Customer class which contains a collection of CustomerLoginSession objects because Customer can have multiple login sessions (1-to-many * multiplicity
-
-
- private readonly List<CustomerLoginSession> _customerLoginSessions = new List<CustomerLoginSession>();
- Define a property named “CurrentCustomerLoginSession” of type CustomerLoginSession within the Customer class to store current login session. In real implementation we don’t require this property. I have created to keep this project simple.
- private CustomerLoginSession CurrentCustomerLoginSession { get; set; }
- Update method “AddItemToShoppingCart” within the Customer class to make a call to the method “AddItemToShoppingCart” to CurrentCustomerLoginSession object that will further add item into the ShoppingCart.
- public void AddItemToShoppingCart(string itemCode)
- {
- CurrentCustomerLoginSession.AddItemToShoppingCart(itemCode);
- }
- Add method “InitializeCustomerLoginSession” within the Customer class to instantiate CustomerLoginSession object and pass a reference of shopping cart instance created in the customer class so that each customer login session has access to the same shopping cart instance.
- public void InitializeCustomerLoginSession(string applicationId)
- {
- CustomerLoginSession loginSession = _customerLoginSessions.FirstOrDefault(x => x.ApplicationId == applicationId);
-
- if (null == loginSession)
- {
- loginSession = new CustomerLoginSession(_shoppingCartInstance, applicationId, CustomerName);
- _customerLoginSessions.Add(loginSession);
- }
-
- CurrentCustomerLoginSession = loginSession;
- }
Now write a program that instantiates an instance of Customer class for different customers whereas retrieves an existing instance of customer class from a list of logged in customers maintained but initializes customer login session for different platforms. This way we will be able to create different instances of shopping cart per customer but share the same instance of shopping cart among different customer login sessions. Customers will be able to add items into the shopping cart from different session and able to see all the items added from different sessions.
- public static void StartProcess()
- {
- LoggedinCustomers loggedinCustomers = new LoggedinCustomers();
- while (true)
- {
- Console.Clear();
- PrintLoggedinCustomers(loggedinCustomers);
-
- Console.Write("Enter 1 to login or 0 to exit: ");
- string input = Console.ReadLine();
- if (input == "1")
- {
- var currentCustomer = LoginCustomer(loggedinCustomers);
- StartShopping(currentCustomer);
- }
- else
- {
- break;
- }
- }
- }
-
- private static Customer LoginCustomer(LoggedinCustomers loggedinCustomers)
- {
- Console.Write("Enter Customer Name to login: ");
- string customerName = Console.ReadLine();
-
- Console.Write("Enter Application Name Like Web Or Mobile: ");
- string application = Console.ReadLine();
-
-
- var currentCustomer = loggedinCustomers.GetCustomer(customerName);
-
- if (null == currentCustomer)
- {
- currentCustomer = new Customer(customerName);
- loggedinCustomers.AddCustomer(currentCustomer);
- }
-
- currentCustomer.InitializeCustomerLoginSession(application);
- Console.WriteLine("{0} logged in sucessfully from {1} !! Let's Start Shopping ", currentCustomer.CustomerName, application);
-
-
- return currentCustomer;
- }
-
- private static void StartShopping(Customer customer)
- {
- Console.WriteLine("Enter 0 to logout from the application");
- Console.WriteLine("Enter 1 to Print Shopping Cart Item");
- Console.WriteLine("Enter 2 to add item into the shopping cart");
- string line;
- while ((line = Console.ReadLine()) != "0")
- {
- if (line == "1")
- {
- customer.PrintShoppingCartItem();
- }
- else if (line == "2")
- {
- Console.WriteLine("Enter 9 to Stop Shopping else add Item Name to Add into the shopping cart else");
- Console.WriteLine("");
- string itemCode;
- while ((itemCode = Console.ReadLine()) != "9")
- {
- customer.AddItemToShoppingCart(itemCode);
- }
-
- Console.WriteLine("Enter 0 to logout from the application");
- Console.WriteLine("Enter 1 to Print Shopping Cart Item");
- Console.WriteLine("Enter 2 to add item into the shopping cart");
- }
- }
- }
-
- private static void PrintLoggedinCustomers(LoggedinCustomers loggedinCustomers)
- {
- var customers = loggedinCustomers.GetCustomers();
- if (0 >= customers.Count)
- {
- Console.WriteLine("No Customer is currently logged in to the application");
- }
- else
- {
- foreach (Customer customer in customers)
- {
- List<CustomerLoginSession> loginSessions = customer.GetCustomerLoginSessions();
- string applications = string.Join(", ", from item in loginSessions select item.ApplicationId);
- Console.WriteLine("{0} is currently logged in from {1}", customer.CustomerName, applications);
- customer.PrintShoppingCartItem();
- }
- }
- }
Output
Customer named “User 1” logged into the application from Web and Mobile application at the same time. User added Mobile, Shoes into the shopping cart from Web Application whereas user added Jeans from Mobile Application. Finally, user could view all the items added into the shopping cart irrespective of which item was added from which application or login session.
Overall Class Structure
Conclusion
Based on the above example, I would propose to use Singleton over Static whenever we need to create instance based on some context and use Static over Singleton whenever we need the same instance or data throughout the application.