Featured image of post CP5 - Singleton - Learn Design Pattern From Simple Things

CP5 - Singleton - Learn Design Pattern From Simple Things

Apply singleton to maintain a single phone line between the two sides indefinitely instead of repeatedly establishing and disconnecting multiple phone lines for efficiency.

Singleton is a creational design pattern: šŸ–¤šŸ–¤šŸ–¤šŸ–¤šŸ¤

What is Singleton?

Singleton is a technique that ensures only one instance can be created and accessed globally throughout a system. It’s commonly used for coordination and centralization of resources or services, and it helps improve efficiency and consistency.

Singleton diagram

Why use Single?

  • Ensures that only one instance of a class exists throughout the system.
  • Allows global access to the instance.
  • Promotes coordination and centralization of resources or services.
  • Helps improve efficiency and consistency of the system.

Question: Why do some people consider Singleton to be an anti-pattern?

Answer: because it can introduce problems such as tight coupling, poor testability, and hidden dependencies that can make it harder to maintain and scale a system. Therefore, some designers prefer to use other design patterns or avoid Singleton in favor of more flexible and modular approaches.

Singleton

When to use Singleton?

Question: When do I use Singleton?

Answer: When you need to ensure that only one instance of a class is created to avoid the overhead of creating and destroying objects frequently, or you want to provide global access to it.

Input:

  • A class PhoneLine that need to restrict to a single instance.
1
2
3
4
5
6
class PhoneLine(metaclass=PhoneLineSingleton):
    def __init__(self):
        sleep(10)

    def talk(self):
        print(f"talking on the {id(self)} line")

Expected Output:

  • phoneline instance is the only instance that is used by all employee services.
1
2
talking on the 69 line
talking on the 69 line

How to implement Singleton?

Non-Singleton implementation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from time import sleep


class PhoneLine:
    def __init__(self):
        sleep(10)

    def talk(self):
        print(f"talking on the {id(self)} line")


class Employee:
    def __init__(self, phone_line):
        self.phone_line = phone_line

    def call_data_center(self):
        self.phone_line.talk()


# Example usage:
phone_line = PhoneLine()
employee1 = Employee(phone_line)
employee2 = Employee(phone_line)

# Both employees use the same phone line object but not the same instance
employee1.call_data_center()
employee2.call_data_center()

Singleton Implementation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from threading import Lock
from time import sleep


class PhoneLineSingleton(type):
    _instances = {}
    _lock = Lock()

    def __call__(cls, *args, **kwargs):
        """
        __call__ is defined in this metaclass(SingletonMeta), not in the normal_class(Singleton) itself.
        When you create an instance of the Singleton class using Singleton(), Python internally calls the __call__ method of the SingletonMeta metaclass, not the __call__ method of the Singleton class.
        In this way, the __call__ method in the metaclass is used to control the creation of instances of the Singleton class, while the __call__ method in the Singleton class can be used for other purposes, if needed.
        This separation of concerns between the metaclass and the class itself is a key feature of the singleton pattern implementation in the given code. It allows us to enforce the singleton pattern without modifying the interface or behavior of the Singleton class itself.
        """
        with cls._lock:
            if cls not in cls._instances:
                instance = super().__call__(*args, **kwargs)
                cls._instances[cls] = instance
        return cls._instances[cls]


class PhoneLine(metaclass=PhoneLineSingleton):
    def __init__(self):
        sleep(10)

    def talk(self):
        print(f"talking on the {id(self)} line")


# Example usage:
phone_line_1 = PhoneLine()
phone_line_2 = PhoneLine()

# Both objects refer to the same instance
phone_line_1.talk()
phone_line_2.talk()

Source Code: Singleton Implement

Made with the laziness šŸ¦„
by a busy guy