The facade pattern is a structural design pattern that provides a simplified
interface to a complex subsystem. It allows clients to interact with the
subsystem without needing to understand its internal workings. The benefits of
this pattern are:
Simplified interface: The facade pattern provides an easy, simple and
unified interface to a set of library or services, allowing quicker usage of
the subsystem.
Decoupling: The facade pattern decouples the client code from the complex
subsystem, allowing for more flexible and maintainable code.
Single Responsibility Principle: The facade pattern allows for the
separation of concerns, allowing for a single responsibility for each class.
Encapsulation: The facade pattern encapsulates the complexity of the
subsystem, allowing for a more modular and maintainable codebase.
The facade pattern is composed of three main components:
Facade
Defines the simplified interface to the complex subsystem. It should hide any
internal details, such as data model, connection to database, service calls,
etc.
src.advanced.facade_pattern.BookstoreFacade
Facade for managing books and their stock in the bookstore.
Source code in src/advanced/facade_pattern/facade.py
classBookstoreFacade:"""Facade for managing books and their stock in the bookstore."""def__init__(self,book_manager:BookManager,inventory_stock:InventoryBookStock,isbn_validator:IsbnValidator,)->None:"""Initialize the BookstoreFacade with its subsystem collaborators. Args: book_manager: Subsystem responsible for storing and retrieving books. inventory_stock: Subsystem responsible for tracking stock levels. isbn_validator: Subsystem responsible for validating ISBN numbers. """self.book_manager=book_managerself.inventory_stock=inventory_stockself.isbn_validator=isbn_validator@classmethoddefcreate(cls)->"BookstoreFacade":"""Create a facade instance with default subsystem collaborators. Convenience factory that wires up fresh instances of every subsystem used by the facade. Prefer the regular constructor when you need to inject custom or pre-configured subsystems (e.g. in tests). Returns: A new BookstoreFacade backed by default subsystem instances. """book_manager=BookManager()inventory_stock=InventoryBookStock()isbn_validator=IsbnValidator()returncls(book_manager=book_manager,inventory_stock=inventory_stock,isbn_validator=isbn_validator,)defnew_book(self,isbn:str,title:str,author:str,quantity:int,)->None:"""Add a book to the bookstore and set its stock level. Args: isbn: The International Standard Book Number of the book. title: The title of the book. author: The author of the book. quantity: The stock level to set for the book. Raises: ValueError: If isbn is not a valid ISBN-10 or ISBN-13. """ifnotself.isbn_validator.is_valid_isbn(isbn):raiseValueError(f"Invalid ISBN: {isbn}")book=Book(isbn,title,author)self.book_manager.add_book(book)self.inventory_stock.set_stock(isbn,quantity)deflist_books_with_stock(self)->list[str]:"""List all books in the bookstore with stock. Returns: A list of books that have stock available. """books=self.book_manager.list_books()books_with_stock=[]forbookinbooks:ifself.inventory_stock.get_stock(book.isbn)>0:books_with_stock.append(book.title)returnbooks_with_stockdefpurchase_book(self,isbn:str,quantity:int)->bool:"""Purchase a specific quantity of a book. Args: isbn: The International Standard Book Number of the book. quantity: The quantity to purchase. Returns: True if the purchase was successful (enough stock), False otherwise. """ifself.book_manager.find_book_by_isbn(isbn):returnself.inventory_stock.purchase_book(isbn,quantity)returnFalse
Subsystem
The subsystem is composed of multiple classes that work together to provide the
functionality of the subsystem. The subsystem classes should not be aware of the
facade, and should not depend on it. The subsystem classes should be designed to
work together, and should not be designed to work independently.
src.advanced.facade_pattern.BookManager
Manages a collection of books in the bookstore.
For this example, it uses an in-memory list to store books. In a real-world,
it could be any database.
Source code in src/advanced/facade_pattern/facade.py
classBookManager:"""Manages a collection of books in the bookstore. For this example, it uses an in-memory list to store books. In a real-world, it could be any database. """def__init__(self)->None:"""Initialize the BookManager with an empty list of books."""self.books:list[Book]=[]defadd_book(self,book:Book)->None:"""Add a book to the collection. Args: book: The Book instance to be added. """self.books.append(book)deflist_books(self)->list[Book]:"""List all books in the collection. Returns: A list of Book instances. """returnself.booksdeffind_book_by_isbn(self,isbn:str)->Book|None:"""Find a book by its ISBN. Args: isbn: The International Standard Book Number of the book to find. Returns: The Book instance if found, otherwise None. """forbookinself.books:ifbook.isbn==isbn:returnbookreturnNonedefremove_book(self,isbn:str)->bool:"""Remove a book from the collection by its ISBN. Args: isbn: The International Standard Book Number of the book to remove. Returns: True if the book was removed, False otherwise. """book=self.find_book_by_isbn(isbn)ifbook:self.books.remove(book)returnTruereturnFalse
src.advanced.facade_pattern.InventoryBookStock
Manages the stock levels of books in the bookstore.
In this example, it uses an in-memory dictionary to track stock levels. In a
real-world, it could be a external inventory management system.
Source code in src/advanced/facade_pattern/facade.py
classInventoryBookStock:"""Manages the stock levels of books in the bookstore. In this example, it uses an in-memory dictionary to track stock levels. In a real-world, it could be a external inventory management system. """def__init__(self)->None:"""Initialize the InventoryBookStock with an empty stock dictionary."""self.stock:dict[str,int]={}defset_stock(self,isbn:str,quantity:int)->None:"""Set the stock level for a specific book. Args: isbn: The International Standard Book Number of the book. quantity: The stock level to set for the book. """self.stock[isbn]=quantitydefget_stock(self,isbn:str)->int:"""Get the stock level for a specific book. Args: isbn: The International Standard Book Number of the book. Returns: The stock level of the book. Returns 0 if the book is not found. """returnself.stock.get(isbn,0)defpurchase_book(self,isbn:str,quantity:int)->bool:"""Purchase a specific quantity of a book. Args: isbn: The International Standard Book Number of the book. quantity: The quantity to purchase. Returns: True if the purchase was successful (enough stock), False otherwise. """current_stock=self.get_stock(isbn)ifcurrent_stock>=quantity:self.stock[isbn]=current_stock-quantityreturnTruereturnFalse
src.advanced.facade_pattern.IsbnValidator
Validates ISBN numbers for books.
This class provides methods to validate the format of both ISBN-10 and
ISBN-13. In a real-world scenario this could be delegated to an external
service.
Source code in src/advanced/facade_pattern/facade.py
classIsbnValidator:"""Validates ISBN numbers for books. This class provides methods to validate the format of both ISBN-10 and ISBN-13. In a real-world scenario this could be delegated to an external service. """@staticmethoddefis_valid_isbn(isbn:str)->bool:"""Validate the given ISBN number (ISBN-10 or ISBN-13). Hyphens and spaces are ignored. The check digit is verified against the standard checksum for the detected format. Args: isbn: The International Standard Book Number to validate. Returns: True if the ISBN is a valid ISBN-10 or ISBN-13, False otherwise. """ifnotisbn:returnFalsenormalized=isbn.replace("-","").replace(" ","")iflen(normalized)==ISBN10_LENGTH:returnIsbnValidator._is_valid_isbn10(normalized)iflen(normalized)==ISBN13_LENGTH:returnIsbnValidator._is_valid_isbn13(normalized)returnFalse@staticmethoddef_is_valid_isbn10(isbn:str)->bool:"""Validate a normalized 10-character ISBN-10 string. Args: isbn: The normalized ISBN-10 string (no hyphens or spaces). Returns: True if the checksum is valid, False otherwise. """ifnotisbn[:-1].isdigit()orisbn[-1]notin"0123456789X":returnFalsetotal=sum((i+1)*(10ifx=="X"elseint(x))fori,xinenumerate(isbn))returntotal%11==0@staticmethoddef_is_valid_isbn13(isbn:str)->bool:"""Validate a normalized 13-character ISBN-13 string. Args: isbn: The normalized ISBN-13 string (no hyphens or spaces). Returns: True if the checksum is valid, False otherwise. """ifnotisbn.isdigit():returnFalsetotal=sum(int(x)*(1ifi%2==0else3)fori,xinenumerate(isbn))returntotal%10==0
Client
The client is the code that uses the facade to interact with the subsystem. The
client should not be aware of the internal workings of the subsystem, and should
only interact with the facade. The client should be designed to work with the
facade, which is curated to expose only the necessary functionality, avoiding
using the complexity to init and manage the subsystem classes.
`python
store = BookstoreFacade.create()
store.new_book("9788413148465", "Project Hail Mary", "Andy Weir", 5)
store.purchase_book("9788413148465", 1)
Some good notes to keep in mind when using the facade pattern are:
A big facade is a code smell. You can then split it into multiple smaller
facades, each with a specific responsibility.
Avoid exposing the subsystem classes to the client code. The facade should be
the only point of contact for the client code. Create new data models to
return from the facade, instead of returning the subsystem classes.
to ensure that client code is using the facade, you can use a linter to
enforce that the subsystem classes are not used directly, such as
import-linter.
Some real-world examples are: complex libraries or frameworks, backend for
microservices, services that require multiple steps to perform a task, and
systems that require a simplified interface for end-users.