본문 바로가기
Mastering Python Design Patterns

Other Creational Patterns

by 자동매매 2023. 3. 22.

3 Other Creational Patterns

In the previous chapter, we covered a third creational pattern, that is, builder, which offers a nice way of creating the various parts of a complex object. Besides the factory method, the abstract factory, and the builder patterns covered so far, there are other creational patterns that are interesting to discuss, such as the prototype pattern and the singleton pattern.

What is the prototype pattern? The prototype pattern is useful when one needs to create objects based on an existing object by using a cloning technique.

As you may have guessed, the idea is to use a copy of that object's complete structure to produce the new object. We will see that this is almost natural in Python because we have a copy feature that helps greatly in using this technique.

What is the singleton pattern? The singleton pattern offers a way to implement a class from which you can only create one object, hence the name singleton. As you will understand with our exploration of this pattern, or while doing your own research, there have always been discussions about this pattern, and some even consider it an anti-pattern.

Besides that, what is interesting is that it is useful when we need to create one and only one object, for example to store and maintain a global state for our program, and in Python it can be implemented using some special built-in features.

In this chapter, we will discuss:

  • The prototype pattern
    • The singleton pattern

Other Creational Patterns Chapter 50*

The prototype pattern

Sometimes, we need to create an exact copy of an object. For instance, assume that you want to create an application for storing, sharing, and editing presentation and marketing content for products promoted by a group of salespeople. Think of the popular distribution model called direct selling or network marketing, the home-based activity where individuals partner with a company to distribute products within their social network, using promotional tools (brochures, PowerPoint presentations, videos, and so on).

Let's say a user, Bob, leads a team of distributors within a network marketing organization. They use a presentation video on a daily basis to introduce the product to their prospects. At some point, Bob gets his friend, Alice, to join him and she also uses the same video (one of the governing principles is to follow the system or, as they say, Duplicate what already works). But Alice soon finds prospects that could join her team and help her business grow, if only the video was in French, or at least subtitled. What should they do? The original presentation video cannot be used for the different custom needs that may arise.

To help everyone, the system could allow the distributors with certain rank or trust levels, such as Bob, to create independent copies of the original presentation video, as long as the new version is validated by the compliance team of the backing company before public use. Each copy is called a clone; it is an exact copy of the original object at a specific point in time.

So Bob, with the validation of the compliance team, which is part of the process, makes a copy of the presentation video to address the new need and hands it over to Alice. She could then adapt that version by adding French subtitles.

With cloning, Bob and Alice can have their own copy of a video, and as such, changes by each one of them will not affect the other person's version of the material. In the alternative situation, which is what actually happens by default, each person would hold a reference to the same (reference) object; changes made by Bob would impact Alice and vice versa.

The prototype design pattern helps us with creating object clones. In its simplest version, this pattern is just a clone() function that accepts an object as an input parameter and returns a clone of it. In Python, this can be done using the copy.deepcopy() function.

Real-world examples

A non-computer example that comes to mind is the sheep named Dolly that was created by researchers in Scotland by cloning a cell from a mammary gland.

There are many Python applications that make use of the prototype pattern (j.mp/pythonprot), but it is almost never referred to as "prototype" since cloning' objects is a built-in feature of the language.

Use cases

The prototype pattern is useful when we have an existing object that needs to stay untouched, and we want to create an exact copy of it, allowing changes in some parts of the copy.

There is also the frequent need for duplicating an object that is populated from a database and has references to other database-based objects. It is costly (multiple queries to a database) to clone such a complex object, so a prototype is a convenient way to solve the problem.

Implementation

Nowadays, some organizations, even of small size, deal with many websites and apps via their infrastructure/DevOps teams, hosting providers, or cloud service providers.

When you have to manage multiple websites, there is a point where it becomes difficult to follow. You need to access information quickly such as IP addresses that are involved, domain names and their expiration dates, and maybe details about the DNS parameters. So you need a kind of inventory tool.

Let's imagine how these teams deal with this type of data for daily activities, and touch on the implementation of a piece of software that helps consolidate and maintain the data (other than in Excel spreadsheets).

First, we need to import Python's standard copy module, as follows:

import copy

At the heart of this system, we will have a Website class for holding all the useful information such as the name, the domain name, the description, the author of a website we are managing, and so on.

In the __init__() method of the class, only some parameters are fixed: name, domain, description, and author. But we also want flexibility, and client code can pass more parameters in the form of keywords (name=value) using the kwargs variable-length collection (a Python dictionary).

Note that there is a Python idiom to set an arbitrary attribute named attr with a value val on an object obj, using the setattr() built-in function: setattr(obj, attr, val).

So, we are using this technique for the optional attributes of our class, at the end of the initialization method, this way:

for key in kwargs:

setattr(self, key, kwargs[key])

So our Website class is defined as follows:

class Website:

def __init__(self, name, domain, description, author, **kwargs): '''Examples of optional attributes (kwargs):

category, creation_date, technologies, keywords. '''

self.name = name

self.domain = domain

self.description = description

self.author = author

for key in kwargs:

setattr(self, key, kwargs[key])

def __str__(self):

summary = [f'Website "{self.name}"\n',] infos = vars(self).items()

ordered_infos = sorted(infos)

for attr, val in ordered_infos:

if attr == 'name': continue

summary.append(f'{attr}: {val}\n') return ''.join(summary)

Next, the Prototype class implements the prototype design pattern.

The heart of the Prototype class is the clone() method, which is in charge of cloning the object using the copy.deepcopy() function. Since cloning means we allow setting values for optional attributes, notice how we use the setattr() technique here with the attrs dictionary.

Also, for more convenience, the Prototype class contains the register() and unregister() methods, which can be used to keep track of the cloned objects in a dictionary:

class Prototype:

def __init__(self):

self.objects = dict()

def register(self, identifier, obj): self.objects[identifier] = obj

def unregister(self, identifier): del self.objects[identifier]

def clone(self, identifier, **attrs):

found = self.objects.get(identifier)

if not found:

raise ValueError(f'Incorrect object identifier: {identifier}')

obj = copy.deepcopy(found)

for key in attrs:

setattr(obj, key, attrs[key])

return obj

In the main() function, as shown in the following code, we can clone a first Website instance, site1, to get a second object' site2. Basically, we instantiate the Prototype class and we use its .clone() method. That is what the following code shows:

def main():

keywords = ('python', 'data', 'apis', 'automation')

site1 = Website('ContentGardening', domain='contentgardening.com',

description='Automation and data-driven apps', author='Kamon Ayeva', category='Blog', keywords=keywords)

prototype = Prototype()

identifier = 'ka-cg-1'

prototype.register(identifier, site1) site2 = prototype.clone(identifier, name='ContentGardeningPlayground', domain='play.contentgardening.com',

description='Experimentation for techniques featured on the blog',

category='Membership site', creation_date='2018-08-01')

To end that function, we can use the id() function which returns the memory address of

an object, for comparing both objects' addresses, as follows. When we clone an object using a deep copy, the memory addresses of the clone must be different from the memory addresses of the original object:

for site in (site1, site2):

print(site)

print(f'ID site1 : {id(site1)} != ID site2 : {id(site2)}')

You will find the program's full code in the prototype.py file. Here is a summary of what we do in the code:

  1. We start by importing the copy module.
  2. We define the Website class, with its initialization method (__init__()) and its string representation method (__str__()) as shown earlier.
  3. We define our Prototype class as shown earlier.
  4. Then, we have the main() function, where we do the following:
  • We define the keywords list we need
    • We create the instance of the Website class, called site1 (we use the keywords list here)
      • We create the Prototype object and we use its register() method to register site1 with its identifier (this helps us keep track of the cloned objects in a dictionary)
        • We clone the site1 object to get site2
          • We display the result (both Website objects)

A sample output when I execute the python prototype.py command on my machine is as follows:

Indeed, Prototype works as expected. We can see the information about the original Website object and its clone.

Looking at the output of the id() function, we can see that the two addresses are different.

Singleton

The singleton pattern restricts the instantiation of a class to one object, which is useful when you need one object to coordinate actions for the system.

The basic idea is that only one instance of a particular class, doing a job, is created for the needs of the program. To ensure that this works, we need mechanisms that prevent the instantiation of the class more than once and also prevent cloning.

Real-world examples

In a real-life scenario, we can think of the captain of a ship or a boat. On the ship, he is the one in charge. He is responsible for important decisions, and a number of requests are directed to him because of this responsibility.

In software, the Plone CMS has, at its core, an implementation of the singleton. There are actually several singleton objects available at the root of a Plone site, called tools, each in charge of providing a specific set of features for the site. For example, the Catalog tool deals with content indexation and search features (built-in search engines for small sites where you don't need to integrate products like ElasticSearch), the Membership tool deals with things related to user profiles, and the Registry tool provides a configuration registry to store and maintain different kinds of configuration properties for the Plone site. Each tool is global to the site, created from a specific singleton class, and you can't create another instance of that singleton class in the context of the site.

Use cases

The singleton design pattern is useful when you need to create only one object or you need some sort of object capable of maintaining a global state for your program.

Other possible use cases are:

  • Controlling concurrent access to a shared resource. For example, the class managing the connection to a database.
    • A service or resource that is transversal in the sense that it can be accessed from different parts of the application or by different users and do its work. For example, the class at the core of the logging system or utility.

Implementation

Let's implement a program to fetch content from web pages, inspired by the tutorial from Michael Ford (https:/ / docs.python.org/3/ howto/urllib2.html). We have only take the simple part since the focus is to illustrate our pattern more than it is to build a special web- scraping tool.

We will use the urllib module to connect to web pages using their URLs; the core of the program would be the URLFetcher class that takes care of doing the work via a fetch() method.

We want to be able to track the list of web pages that were tracked, hence the use of the singleton pattern: we need a single object to maintain that global state.

First, our naive version, inspired by the tutorial but modified to help us track the list of URLs that were fetched, would be:

import urllib.parse import urllib.request

class URLFetcher:

def __init__(self):

self.urls = []

def fetch(self, url):

req = urllib.request.Request(url)

with urllib.request.urlopen(req) as response: if response.code == 200:

the_page = response.read() print(the_page)

urls = self.urls urls.append(url)

self.urls = urls

As an exercise, add the usual if __name__ == '__main__' block with a few lines of code to call the .fetch() method on an instance of URLFetcher.

But then, does our class implement a singleton? Here is a clue. To create a singleton, we need to make sure one can only create one instance of it. So, to see if our class implements a singleton or not, we could use a trick which consists of comparing two instances using the is operator.

You may have guessed the second exercise. Put the following code in your if __name__ == '__main__' block instead of what you previously had:

f1 = URLFetcher() f2 = URLFetcher() print(f1 is f2)

As an alternative, use the concise but still elegant form:

print(URLFetcher() is URLFetcher())

With this change, when executing the program, you should get False as the output.

Okay! This means that the first try does not yet give us a singleton. Remember, we want to manage a global state, using one and only one instance of the class for the program. The current version of the class does not yet implement a singleton.

After checking the literature and the forums on the web, you will find that there are several techniques, each with pros and cons, and some are probably outdated.

Since many use Python 3 nowadays, the recommended technique we will choose is the metaclass technique. We first implement a metaclass for the singleton, meaning the class (or type) of the classes that implement the singleton pattern, as follows:

class SingletonType(type):

_instances = {}

def __call__(cls, *args, **kwargs):

if cls not in cls._instances:

cls._instances[cls] = super(SingletonType, cls).__call__(*args, **kwargs)

return cls._instances[cls]

Now, we will rewrite our URLFetcher class to use that metaclass. We also add a dump_url_registry() method, which is useful to get the current list of URLs tracked:

class URLFetcher(metaclass=SingletonType):

def fetch(self, url):

req = urllib.request.Request(url)

with urllib.request.urlopen(req) as response: if response.code == 200:

the_page = response.read() print(the_page)

urls = self.urls urls.append(url)

self.urls = urls

def dump_url_registry(self):

return ', '.join(self.urls)

if __name__ == '__main__':

print(URLFetcher() is URLFetcher())

This time, you get True by executing the program.

Let's complete the program to do what we wanted, using a main() function that we will call:

def main():

MY_URLS = ['http://www.voidspace.org.uk', 'http://google.com', 'http://python.org', 'https://www.python.org/error', ]

print(URLFetcher() is URLFetcher())

fetcher = URLFetcher()

for url in MY_URLS:

try:

fetcher.fetch(url)

except Exception as e: print(e)

print('-------')

done_urls = fetcher.dump_url_registry() print(f'Done URLs: {done_urls}')

You will find the program's full code in the singleton.py file. Here is a summary of what we do:

  1. We start with our needed module imports (urllib.parse and

urllib.request)

  1. As shown earlier, we define the SingletonType class, with its special

__call__() method

  1. As shown earlier, we define URLFetcher, the class implementing the fetcher for the web pages, initializing it with the urls attribute; as discussed, we add its fetch() and dump_url_registry() methods
  2. Then, we add our main() function
  3. Lastly, we add Python's conventional snippet used to call the main function

A sample output when executing the python singleton.py command is as follows:

We can see that we get the expected result: both the content of the pages that the program was able to connect to and the list of URLs the operation was successful for.

We see that the URL, https:/ / www.python.org/error, does not come in the list returned by fetcher.dump_url_registry(); indeed, it is an erroneous URL and the urlllib request to it gets a 404 response code.

Summary

In this chapter, we have seen how to use two other creational design patterns: the prototype and the singleton.

A prototype is used for creating exact copies of objects. In the general case of creating a copy of an object, what happens is that you make a new reference to the same object, a method called a shallow copy. But, if you need to duplicate the object, which is the case with a prototype, you make a deep copy.

As seen in the implementation example we discussed, using a prototype in Python is natural and based on built-in features, so it is not something even mentioned.

The singleton pattern can be implemented by making the singleton class use a metaclass, its type, having previously defined the said metaclass. As required, the

metaclass's __call__() method holds the code that ensures that only one instance of the class can be created.

The next chapter is about the adapter pattern, a structural design pattern that can be used to make two incompatible software interfaces compatible.

[ 50 ] )

'Mastering Python Design Patterns' 카테고리의 다른 글

The Decorator Pattern  (0) 2023.03.22
The Adapter Pattern  (0) 2023.03.22
The Builder Pattern  (0) 2023.03.22
The Factory Pattern  (0) 2023.03.22
Index  (0) 2023.03.22

댓글