In this lab, we’ll explore a crucial design pattern in object-oriented programming: the Facade Pattern. By the end of this lab, you will be tasked with creating your own InventoryManager class, but first, we'll demonstrate the principles using a simpler example: a TicketManager system for managing event tickets.
Ensure that you are signed into your GitHub account
Join the GitHub Classroom and accept this assignment: Project 12 - Inventory System
Clone the repository to your computer using GitHub Desktop or open it online using Codespaces
Think of the Facade Pattern like using a 'smart' remote control to operate your TV, Bluray player, and sound system. Instead of manually turning on each device, adjusting the volume, and selecting the right input every time you want to watch a movie, you just press a single button on the remote control. The remote handles all these complex steps for you, simplifying your interaction with the home entertainment system.
Similarly, in programming, a facade is a class that provides a simple interface to a complex set of classes, libraries, or frameworks. It simplifies the client’s interaction with the system by hiding the complexity behind the operations and allowing the user to perform tasks with a single method call. This makes complex systems easier to use and manage, especially as they grow larger and more difficult to understand.
In programming, the Facade Pattern typically involves creating a single class called a facade. This class provides a simplified interface to a more complex subsystem underneath. Here's what the structure usually includes:
Facade Class: This is the only class that clients interact with. It offers one or more methods that clients can call to perform operations. These methods delegate the client's requests to the appropriate classes within the subsystem.
Subsystem Classes: These are the classes that actually perform the work behind the scenes. They are often complex, have dependencies on each other, and are not designed to be used directly by the clients of the facade.
Client: This is the code that uses the facade. The client might be any application or system that requires functionality provided by the subsystem. The client interacts with the subsystem by calling methods on the facade, rather than directly interacting with the subsystem classes.
Use the Facade Pattern when:
You have a complex system with multiple moving parts, and you want to simplify interaction with the system.
You need an easy-to-use interface over a large body of code.
You want to decouple a system from the clients that use it, which can help minimise the dependencies between systems.
The Facade Pattern is popular because it simplifies the interaction with complex systems, reduces dependencies, and improves flexibility in code maintenance. By limiting the impact of changes in the system, it aids in following the Open/Closed Principle, allowing systems to be open for extension but closed for modification.
The Open/Closed Principle is a fundamental concept in software engineering that encourages us to design our programs so they can be extended with new features without needing to modify existing code. Essentially, it means that you should be able to add new functionality to a system by adding new code rather than changing the code that’s already been tested and is working well. This principle helps keep your program stable and reduces the chance of introducing new bugs into an already functioning system when you want to update or improve it.
Let’s start by creating a TicketManager facade that manages ticket bookings for events. We will build on the Ticket class from the previous lab.
I recommend setting up an empty shell of a program like this initially. This is like creating a sketch or rough draft of your program.
class TicketManager:
def __init__(self):
self.tickets = []
def add_ticket(self, ticket):
print("Ticket added")
def remove_ticket(self, ticket_id):
print("Ticket removed")
def update_ticket_price(self, ticket_id, new_price):
print("Ticket updated")
def display_tickets(self):
print("Printing tickets")
def find_ticket(self, ticket_id):
print("Ticket found")
Each of the methods so far are a stub. Stubs are simple implementations of methods that do not contain the actual implementation but are useful for compiling and testing the overall structure. They can return hardcoded values or simple messages like above, allowing developers to focus on building out the structure of the application without getting bogged down by the complexities of full implementation.
Top-down design is a strategic approach in software engineering where the overall structure of a program is defined before its individual components. This design philosophy allows developers to map out the broad features and functionalities of a program and then delve into the specifics of each component. It is particularly effective in managing complex systems by breaking them down into more manageable, testable pieces.
Top-down design is closely related to the concept of incremental development, where software is developed and delivered in pieces. This method allows developers to focus on one area of functionality at a time, making it easier to spot issues and correct them without impacting the entire system. It saves developers from the overwhelming task of solving all programming challenges at once and facilitates easier testing and validation of each function as the project progresses.
We are developing a TicketManager system to handle event ticketing. Above, we outline the main functionalities we expect from our system, such as adding tickets, updating ticket prices, and removing tickets. Each functionality is initially represented by a simple stub that can later be expanded into a full implementation.
Here is a simple top-level program that uses the TicketManager facade to manage tickets:
import datetime
from ticket import Ticket
from facade import TicketManager
def main():
# Create an instance of TicketManager
manager = TicketManager()
# Create some tickets
ticket1 = Ticket("001", datetime.date(2024, 5, 20), 150.00)
ticket2 = Ticket("002", datetime.date(2024, 5, 21), 175.00)
# Add tickets to the manager
manager.add_ticket(ticket1)
manager.add_ticket(ticket2)
# Display current tickets
manager.display_tickets()
# Update the price of a ticket
manager.update_ticket_price("001", 200.00)
# Remove a ticket
manager.remove_ticket("002")
# Display current tickets again to show changes
manager.display_tickets()
if __name__ == "__main__":
main()
Bottom-Up Design focuses on creating and perfecting lower-level, detailed components of the system first. These components are then progressively integrated to form higher-level functionalities or complete systems.
In our system we started by designing the Ticket class first. We included specific methods to manage ticket attributes like ticket_id, event_date, and price. This design allows each Ticket object to operate independently with its own set of functionalities, which include setting and getting prices and dates with validations. This is an example of bottom-up design.
The combination of top-down and bottom-up approaches in software development is often very effective. Top-down allows you to understand the overall system architecture and how components will interact, while bottom-up ensures that each component is robust and fully functional on its own. This hybrid approach can lead to a well-rounded development process that ensures both the individual components and the overall system architecture are sound.
While our main approach with the TicketManager is top-down, we also utilise a bottom-up design by developing the Ticket class first. This allows us to ensure that each ticket handles its functionalities perfectly before being managed by the larger system. By designing the Ticket class independently, we are able to focus on ensuring its robustness, which is crucial for the reliability of the entire system once all parts are integrated.
Below, you will find the fully implemented TicketManager class. The facade is now capable of adding, updating, removing, and displaying tickets.
Open lab2.py
Complete the following activities:
Import the Item class from lab1.py
Define the InventoryManager class as a facade to handle the inventory operations.
It should include methods to add, remove, update, and display items in the inventory.
Create instances of the Item class and InventoryManager, then demonstrate their usage.
E.g. add items to the inventory, remove items, update items, and display the inventory.
Commit and push your code