Skip to content

Commit bc2562c

Browse files
committed
Initial commit
0 parents  commit bc2562c

File tree

9 files changed

+328
-0
lines changed

9 files changed

+328
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.idea/
2+
__pycache__/

monostate.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
class Monostate:
2+
"""
3+
The Monostate or Borg pattern. This is another way to represent the
4+
Singleton pattern in Python. Instances will share the same state among
5+
instances,they do not occupy the same memory location. Changes to the
6+
shared state in one instance will be reflected in all instances. This
7+
allows instances to share data while maintaining their individuality.
8+
Usually, each instance will have its own dictionary, but the Borg
9+
pattern modifies this so that all instances have the same dictionary.
10+
While being individuals in their own right. It is often referred to as
11+
the Borg pattern in reference to The Borg in Star Trek. They all share
12+
the same collective consciousness (in this case, _shared_state).
13+
"""
14+
15+
_shared_state = {'comment': 'Resistance is futile'}
16+
17+
def __init__(self):
18+
self.__dict__ = self._shared_state
19+
20+
21+
def process_monostate():
22+
""" This method will create 3 monostate objects.
23+
"""
24+
borg1 = Monostate()
25+
borg2 = Monostate()
26+
borg3 = Monostate()
27+
28+
# these are different objects, different memory locations
29+
30+
print(f"different, expected for Monostate objects correctly: ")
31+
print('Borg1 object:{}'.format(borg1))
32+
print('Borg2 object:{}'.format(borg2))
33+
print('Borg3 object:{}'.format(borg3))
34+
35+
borg1.counter = 27
36+
37+
# counter has been set to 27 for only 1 borg object, however all
38+
# instances have the value 27 as they all share the same internal state.
39+
40+
print('Borg1 state:{}'.format(borg1.__dict__))
41+
print('Borg2 state:{}'.format(borg2.__dict__))
42+
print('Borg3 state:{}'.format(borg2.__dict__))
43+
44+
45+
def main():
46+
process_monostate()
47+
48+
49+
if __name__ == '__main__':
50+
main()

observer.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
class Observer:
2+
"""Observer interface"""
3+
4+
def __init__(self):
5+
self.name = None
6+
7+
def update(self):
8+
print("Subclass must implement update()")
9+
10+
11+
class Subscriber(Observer):
12+
""" Subscriber class implemented the Observer interface"""
13+
14+
def __init__(self, name: str) -> None:
15+
super().__init__()
16+
self.name = name
17+
18+
def update(self):
19+
s = F"new youtube video released {self.name}"
20+
print(s)
21+
return s
22+
23+
class Publisher:
24+
def register(self, subscriber: Subscriber) -> None:
25+
raise NotImplemented("Subclass must implement register()")
26+
27+
def unregister(self, subscriber: Subscriber) -> None:
28+
raise NotImplemented("Subclass must implement unregister()")
29+
30+
def notify(self) -> None:
31+
raise NotImplemented("Subclass must implement notify()")
32+
33+
34+
class ChannelPublisher(Publisher):
35+
def __init__(self) -> None:
36+
super().__init__()
37+
self.subscribers = [] # observers
38+
39+
def register(self, subscriber: Subscriber) -> None:
40+
self.subscribers.append(subscriber)
41+
42+
def unregister(self, subscriber: Subscriber) -> None:
43+
self.subscribers.remove(subscriber)
44+
45+
def notify(self) -> None:
46+
for subscriber in self.subscribers:
47+
subscriber.update()
48+
49+
50+
def main():
51+
bob = Subscriber("Bob")
52+
cody = Subscriber("Cody")
53+
channelPublisher = ChannelPublisher()
54+
55+
channelPublisher.register(bob)
56+
channelPublisher.register(cody)
57+
channelPublisher.notify() # new youtube released, notify all
58+
# subscribers/observers
59+
channelPublisher.unregister(cody)
60+
channelPublisher.notify()
61+
channelPublisher.unregister(bob)
62+
channelPublisher.notify()
63+
64+
65+
if __name__ == '__main__':
66+
main()

readme.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Using Classes for encapsulation compared to global variables
2+
3+
This is quick reference for using alternatives to global variables.
4+
It is not a guide as to which pattern is the best.
5+
6+
The choice between using classes and encapsulation versus global variables depends on the specific requirements and design principles of your code.
7+
8+
In general, using classes and encapsulation is often considered a better practice than relying heavily on global variables.
9+
10+
## Modularity and Organisation:
11+
Classes allow you to encapsulate related data and behaviour into a single unit, promoting a modular and organised code structure.
12+
13+
With global variables, it can be challenging to maintain a clear structure, and it's easier for variables to be modified unexpectedly, leading to potential bugs.
14+
15+
## Code Reusability:
16+
Classes promote code reusability since you can create instances of a class in different parts of your code, and each instance can maintain its state independently.
17+
18+
19+
Global variables can lead to code that is tightly coupled and less reusable,
20+
as any part of the code can modify them, making it harder to reason about the behavior of the program.
21+
22+
## Encapsulation:
23+
Encapsulation, a fundamental principle of object-oriented programming (OOP),
24+
allows you to hide the implementation details of a class and expose only what is necessary. This helps in reducing dependencies between different parts of the code.
25+
Global variables lack encapsulation, making it harder to control access to and modification of data, which can lead to unintended side effects.
26+
27+
## Testing and Debugging:
28+
29+
Classes make it easier to test and debug code because you can isolate and test individual components (methods) of a class independently.
30+
Global variables can make testing and debugging more challenging, as changes to global state can affect the behavior of different parts of the code.
31+
32+
## Namespace Pollution:
33+
34+
Overuse of global variables can lead to namespace pollution,
35+
where variable names clash and cause unintended consequences.
36+
Classes provide a way to create isolated namespaces,
37+
reducing the risk of naming conflicts.
38+
39+
40+
While classes and encapsulation are generally recommended,
41+
it's essential to consider the specific requirements of your project.
42+
In some cases, using global variables might be appropriate, but it's crucial to be mindful of the potential drawbacks mentioned above. Striking a balance and following good design principles will contribute to more maintainable and scalable code.
43+
44+
45+
## Singleton pattern
46+
47+
48+
The singleton pattern is a software design pattern that restricts the instantiation of a class to a singular instance.
49+
One of the well-known "Gang of Four" design patterns.
50+
A singleton implementation may use lazy initialization in which the instance is created when the static method is first invoked.
51+
52+
## Monostate pattern
53+
54+
The monostate pattern will create instances or objects with their own identity that all share the same internal state.
55+
It is often referred to as the Borg pattern in reference to The Borg in Star Trek.
56+
While being individuals in their own right, they all share the same collective consciousness or shared state.

singleton.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
class Singleton:
2+
"""A singleton class. Lazy instantiation, it is only created when needed"""
3+
__instance = None
4+
5+
def __init__(self):
6+
if Singleton.__instance is not None:
7+
raise Exception(
8+
"Singleton already created, use get_instance() instead")
9+
else:
10+
Singleton.__instance = self
11+
12+
@staticmethod
13+
def get_instance():
14+
""" this static method is associated with the class"""
15+
if Singleton.__instance is None:
16+
Singleton()
17+
return Singleton.__instance
18+
19+
20+
def main():
21+
singleton1 = Singleton.get_instance()
22+
singleton2 = Singleton.get_instance()
23+
singleton3 = Singleton.get_instance()
24+
25+
print(singleton1)
26+
print(singleton2)
27+
print(singleton3)
28+
if singleton1 == singleton2 == singleton3:
29+
print("same memory location, pattern implemented correctly")
30+
31+
singleton1.counter = 27 # one instance is updated
32+
print(singleton2.counter) # change reflected in others
33+
34+
35+
if __name__ == '__main__':
36+
main()

tests/__init__.py

Whitespace-only changes.

tests/test_monostate.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import unittest
2+
from monostate import Monostate
3+
4+
5+
class MyTestCase(unittest.TestCase):
6+
@classmethod
7+
def setUpClass(cls):
8+
cls.borg1 = Monostate()
9+
cls.borg2 = Monostate()
10+
cls.borg3 = Monostate()
11+
12+
def test_not_equal(self):
13+
""" different instances, memory will be different"""
14+
15+
self.assertNotEqual(self.borg1, self.borg2)
16+
self.assertNotEqual(self.borg1, self.borg3)
17+
18+
def test_multiple_instances(self):
19+
"""all instances share the same internal state, should be equal"""
20+
21+
self.borg1.some_variable = "Resistance is futile"
22+
23+
self.assertEqual(self.borg1.some_variable, self.borg2.some_variable)
24+
self.assertEqual(self.borg1.some_variable, self.borg3.some_variable)
25+
26+
def test_same_internal_state_dict(self):
27+
"""all instances share the same internal state, should be equal
28+
verify __dict__ are the same for all instances"""
29+
30+
self.borg1.counter = 27
31+
self.assertEqual(id(self.borg1.__dict__), id(self.borg2.__dict__))
32+
self.assertEqual(id(self.borg2.__dict__), id(self.borg3.__dict__))
33+
34+
35+
if __name__ == '__main__':
36+
unittest.main()

tests/test_observer.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import unittest
2+
from observer import ChannelPublisher
3+
from observer import Subscriber
4+
5+
6+
class MyTestCase(unittest.TestCase):
7+
@classmethod
8+
def setUpClass(cls):
9+
print("setUpClass")
10+
cls.channelPublisher = ChannelPublisher()
11+
cls.bob = Subscriber("Bob")
12+
cls.FooBar = Subscriber("FooBar")
13+
14+
def test_register_2_observers(self):
15+
""" register 2 observers """
16+
self.channelPublisher.register(self.bob)
17+
self.assertEqual(len(self.channelPublisher.subscribers), 1)
18+
19+
# register second observer
20+
self.channelPublisher.register(self.FooBar)
21+
self.assertEqual(len(self.channelPublisher.subscribers), 2)
22+
23+
def test_sequence(self):
24+
""" register , unregister, notify"""
25+
self.channelPublisher.notify()
26+
self.channelPublisher.unregister(self.bob)
27+
self.assertEqual(len(self.channelPublisher.subscribers), 1)
28+
subscribers = self.channelPublisher.subscribers
29+
30+
for subscriber in self.channelPublisher.subscribers:
31+
self.assertEqual(subscriber.name, "FooBar")
32+
self.assertEqual(subscriber.update(),
33+
"new youtube video released FooBar")
34+
35+
# verify that bob has actually been removed
36+
self.assertNotIn(self.bob, self.channelPublisher.subscribers)
37+
38+
self.channelPublisher.unregister(self.FooBar)
39+
self.assertNotIn(self.FooBar, self.channelPublisher.subscribers)
40+
41+
# assert that no more subscibers are left
42+
self.assertEqual(len(self.channelPublisher.subscribers), 0)
43+
44+

tests/test_singleton.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import unittest
2+
from singleton import Singleton
3+
4+
5+
class MyTestCase(unittest.TestCase):
6+
@classmethod
7+
def setUpClass(cls):
8+
cls.s1 = Singleton.get_instance()
9+
cls.s2 = Singleton.get_instance()
10+
cls.s3 = Singleton.get_instance()
11+
12+
def test_equal(self):
13+
""" same memory, instances will be same """
14+
15+
self.assertEqual(self.s1, self.s2)
16+
self.assertEqual(self.s1, self.s3)
17+
18+
def test_multiple_instances(self):
19+
""" all instances share the same internal state, should be equal """
20+
21+
self.s1.some_variable = "I'm the only one"
22+
23+
self.assertEqual(self.s1.some_variable, self.s2.some_variable)
24+
self.assertEqual(self.s1.some_variable, self.s3.some_variable)
25+
26+
def test_incorrect_instantiation(self):
27+
"""the singleton can only be created with get_instance, if a singleton
28+
is created using the __init__ an exception will be thrown.
29+
Test to verify the exception is thrown correctly."""
30+
31+
with self.assertRaises(Exception) as context:
32+
s4 = Singleton()
33+
self.assertEqual(str(context.exception), "Singleton already created, "
34+
"use get_instance() instead")
35+
36+
37+
if __name__ == '__main__':
38+
unittest.main()

0 commit comments

Comments
 (0)