In previous article, we looked at patterns such as Singleton, Factory and Abstract Factory.
Today we would focus on remaining 2: Builder and Prototype.
Builder Pattern
The Builder pattern constructs complex objects step by step, allowing you to create different representations using the same construction process.
import requests
class APIRequest:
def __init__(self):
self.method = "GET"
self.url = ""
self.headers = {}
self.params = {}
self.json = None
def __str__(self):
return (
f"{self.method} {self.url}\n"
f"Headers: {self.headers}\n"
f"Params: {self.params}\n"
f"Body: {self.json}"
)
class APIRequestBuilder:
def __init__(self):
self.request = APIRequest()
def set_method(self, method: str):
self.request.method = method.upper()
return self
def set_url(self, url: str):
self.request.url = url
return self
def add_header(self, key: str, value: str):
self.request.headers[key] = value
return self
def add_query_param(self, key: str, value: str):
self.request.params[key] = value
return self
def set_json_body(self, data: dict):
self.request.json = data
return self
def build(self):
return self.request
def send(self):
print("Sending request:")
print(self.request)
print("-" * 50)
response = requests.request(
method=self.request.method,
url=self.request.url,
headers=self.request.headers,
params=self.request.params,
json=self.request.json
)
return response
class GitHubRequestDirector:
def __init__(self, builder: APIRequestBuilder):
self.builder = builder
def get_user_repos(self, username):
return (
self.builder.set_method("GET")
.set_url(f"https://api.github.com/users/{username}/repos")
.add_header("Accept", "application/vnd.github.v3+json")
.build()
)
if __name__ == "__main__":
# Example 1: POST to JSONPlaceholder
post_builder = APIRequestBuilder()
response = (
post_builder.set_method("POST")
.set_url("https://jsonplaceholder.typicode.com/posts")
.add_header("Content-Type", "application/json")
.set_json_body({
"title": "Builder Pattern",
"body": "Making API calls easier",
"userId": 42
})
.send()
)
print("Status:", response.status_code)
print("Response JSON:", response.json())
print("=" * 80)
# Example 2: GET GitHub repos using a director
github_builder = APIRequestBuilder()
director = GitHubRequestDirector(github_builder)
github_request = director.get_user_repos("octocat")
response = requests.request(
method=github_request.method,
url=github_request.url,
headers=github_request.headers
)
print("GitHub Repos for octocat:")
for repo in response.json()[:3]: # show only first 3
print("-", repo["name"])
Use Cases
- SQL query builders
- Configuration builders
- Document builders (PDF, HTML)
- Game character creation
- API request builders
Prototype Pattern
The Prototype pattern creates objects by cloning existing instances rather than creating new ones from scratch.
import copy
import requests
class Prototype:
def clone(self):
return copy.deepcopy(self)
class APIRequest(Prototype):
def __init__(self, method="GET", url="", headers=None, params=None, json=None):
self.method = method
self.url = url
self.headers = headers or {}
self.params = params or {}
self.json = json
def send(self):
print(f"Sending {self.method} request to {self.url}")
response = requests.request(
method=self.method,
url=self.url,
headers=self.headers,
params=self.params,
json=self.json
)
return response
def __str__(self):
return (
f"{self.method} {self.url}\n"
f"Headers: {self.headers}\n"
f"Params: {self.params}\n"
f"Body: {self.json}"
)
if __name__ == "__main__":
# Step 1: Create a prototype for a POST request
post_template = APIRequest(
method="POST",
url="https://jsonplaceholder.typicode.com/posts",
headers={"Content-Type": "application/json"},
json={"userId": 1}
)
# Step 2: Clone the template and customize each
post1 = post_template.clone()
post1.json["title"] = "Post 1"
post1.json["body"] = "Body for post 1"
post2 = post_template.clone()
post2.json["title"] = "Post 2"
post2.json["body"] = "Body for post 2"
# Step 3: Send requests
for post in [post1, post2]:
print("=" * 40)
print("Request:")
print(post)
print("-" * 40)
response = post.send()
print("Status:", response.status_code)
print("Response:", response.json())
Use Cases
- Document templates
- Game object spawning
- Configuration templates
- UI component templates
- Test data creation
When to Use Each Pattern
- Builder: When constructing complex objects with many optional parameters, or when the construction process must allow different representations.
- Prototype: When object creation is expensive and you have similar objects, or when you need to create objects based on templates.
Feel free to share your opinion and criticize this article.
Top comments (0)