CSC/ECE 517 Fall 2007/wiki1b 6 aa
By Qinyi Ding (qding@ncsu.edu) and Xibin Gao (xgao2@ncsu.edu)
Take a case of the Singleton pattern and implement it as succinctly as possible in Ruby and Java.Compare the two implementations in terms of clarity and succinctness.The example should be a "real-world" example. While it may be grossly oversimplified for the purpose of illustration, it should not be totally contrived (i.e., should not raise the question, Why would anyone ever want to do that?).
Singleton Pattern
Definition
The singleton pattern is a kind of design pattern in software engineering. Singleton pattern limits the instantiation of a class to only one object to ensure that no other instantiation exists. Therefore this pattern is useful when the system needs only one instance of a class but multiple accesses from different parts of the system.
Singleton Versus Global Variable
At first glance, singletons are very similar to global variables. One might argue that a static global variable could perform equivalently as a singleton class. However, there are differences between singleton and global variable which make them serve different purposes.
For one thing, although singletons and global variables can be accessed "globally", singleton pattern puts restrict limit in the instantiation of a class. A system cannot instantiate two or more objects of a class when applied singleton. On the contrary, users can always create a number of global variables even if they will produce conflicts. Hence singleton pattern place the responsibility of restricting instantiation on the class itself.
For the other thing, because singleton access is realized by a class, it can be added more functionality to perform more complex logic than a global variable. Global variables reflect the way programmers declare variables, while singletons focus on limiting instantiation.
Pros and Cons of Singleton Pattern
Pros
Direct Control: Singleton design pattern enable classes to have direct control upon the number of instantiation. Usually it is the programmer's responsibility to assure that there is only one instantiation, which will cause potential problems. Utilizing singleton pattern will eliminate such problems. Reduce Namespace: It saves the name space by reducing the number of global variables that store sole instances. Extensibility: Singleton pattern can be easily extended to allow any number of objects to be instantiated. Of course, it is still the class that owns the responsibility.
Cons
Necessity: Sometimes singleton pattern is misused. It is unnecessary to use singleton when it's simpler to pass resource as a reference to the objects rather than letting objects access the resource globally. Since singleton is easy to use and good at avoiding problems, programmers might prefer not to think carefully about the appropriate visibility of an object, and just use singleton. However, proper exposure and protection for an object is critical for maintaining flexibility.
UML Class Diagram
Real World Example
Let's take the example of the Earth. Because we only have one earth, it is logical to implement the Earth class as a singleton. Inside the class, "currentCondition" describes the state (current condition) of the earth.
Ruby Implementation
require 'Singleton' class Earth include Singleton # include singleton mixin attr_accessor :currentCondition end
Java Implementation
public class Earth { private Earth() { // override the contructor to make it private } private static Earth ref; // make the method thread safe public static Earth getEarth() { if (ref == null) synchronized (Earth.class) { // for thread-safety if (ref == null) ref = new Earth ( ); } return ref; } // override the clone method and throws "CloneNotSupported Exception" if invoked public Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } private String currentCondition="I was beautiful and clean"; // get the current condition of the earth public String getCurrentCondition(){ return currentCondition; } // set the current condition of the earth public void setCurrentCondition(String str){ currentCondition=str; } }
Comparison
Ruby can implement singleton pattern by simply referencing the singleton module and all its methods will be included. The singleton mixin makes the constructor inaccessible, overrides clone and dup methods, and takes care of all the threading complications.
Java has no mixins, so singleton implementation in Java has to do the following:
- block off access to constructor by making it private
- provide a static method for getting an instance of the singleton,
- prevent cloning by overriding the clone() method explicitly
- ensure thread safety
In terms of succinctness, Ruby obviously wins over Java. Only one line of code "include Singleton" enabled all the functionalities that java use a dozen of lines to accomplish.
In terms of clarity, Ruby also surpasses Java. For Ruby, after including singleton mixin, it is ready to create/get the only object using "instance" method. However, in Java, there is no uniform method to create/get the only object of the singleton class, because it's the developer's responsibility to implement the singleton class and different methods could be used (such as "getInstance" "Instance" "returnInstance") to create/get the object.
Test
We create class "People" to test the singleton class "Earth". In Java, "People" class has "pollute" method, which accepts Earth object as parameter. The parameter type does not matter in Ruby because of its duck-typing. We try to test if there is only one earth object created. The test goes through the following steps:
- create earth1 and earth2 using Earth class's object creation mechanism
- print earth1 and earth2's state (currentCondition) before invoking "People"'s "pollute" method
- invoke "People"'s "pollute" method on earth1
- print earth1 and earth2's state (currentCondition)
Ruby Test Code
class People def pollute(a) # pollute an earth object a.currentCondition="i am dirty and polluted" end def test p=People.new #suppose we have two "earthes" earth1=Earth.instance earth2=Earth.instance earth1.currentCondition="I am beautiful and clean" #both earth1 and earth2 are "beautiful and clean" puts "earth1 says: " + earth1.currentCondition puts "earth2 says: " + earth2.currentCondition # we (people) pollute earth1 p.pollute(earth1) puts "After people pollute earth1" #both earth1 and earth2 are "dirty and polluted", because earth1==earth2 puts "earth1 says: " + earth1.currentCondition puts "earth2 says: " + earth2.currentCondition end end p=People.new p.test
Java Test Code
public class People { public void pollute(Earth earth){ earth.getEarth().setCurrentCondition("I am dirty and polluted"); } public static void main(String args[]){ People p=new People(); // suppose we have two earths earth1 and earth2 Earth earth1= Earth.getEarth(); Earth earth2= Earth.getEarth(); // and both earth1 and earth2 are "beautiful and clean" System.out.println("earth1 says: " +earth1.getCurrentCondition()); System.out.println("earth2 says: " +earth2.getCurrentCondition()); // suppose we only pollute earth1 p.pollute(earth1); System.out.println("After people pollute earth1"); // earth1 will be "dirty and polluted" System.out.println("earth1 says: " +earth1.getCurrentCondition()); // earth2 is also "dirty and polluted" Why? we only have one earth earth1==earth2! System.out.println("earth2 says: " +earth2.getCurrentCondition()); } }
Test Result
Both the java code and the ruby code will produce the following output
earth1 says: I was beautiful and clean earth2 says: I was beautiful and clean After people pollute earth1 earth1 says: I am dirty and polluted earth2 says: I am dirty and polluted
Before invoking People's pollute method, earth1 and earth2 are "beautiful and clean", and after invoking the method on earth1, both earth 1 and earth2 change to "dirty and polluted". This illustrates actually only one Earth object. "We have only one earth"
References
- Programming Ruby: The programmatic programmer’s guide
- Head First Design Patterns
- Design Patterns - Wikipedia
- Singleton Pattern Created By: Deema Temraz