Lesson 5
Applying Design Patterns in Python: The Art of Selecting the Right Solution
Lesson Introduction

Hello! Today, we'll venture into the realm of design patterns. Specifically, we'll tackle exercises that apply a single design pattern to problem-solving. Mastering these patterns is a surefire way to extend your coding skills.

Our goal today is to fortify your understanding of when and how to apply specific Object-Oriented Programming (OOP) design patterns. These patterns include Encapsulation, Abstraction, Polymorphism, and Composition.

We'll dissect four real-life scenarios and distinguish which pattern is applicable and why.

Let's get underway!

Real-life Example 1: Database Management System (Encapsulation)

The Encapsulation pattern proves beneficial for the development of a Database Management System (DBMS). Each DBMS table represents a class, the fields represent private data members, and the functions operating on this data serve as methods.

Encapsulation ensures that data members are accessed through methods that promote data integrity and prevent inadvertent anomalies. Here's a mini-code snippet to support this concept:

Python
1class Employees: 2 def __init__(self): 3 self.__employees = {} # private data member 4 5 def add_employee(self, eid, name): # method to operate on private data 6 self.__employees[eid] = name 7 8 def update_employee(self, eid, new_name): # method to operate on private data 9 if eid in self.__employees: 10 self.__employees[eid] = new_name 11 12employees = Employees() 13employees.add_employee(1, "John") 14employees.add_employee(2, "Mark") 15 16employees.update_employee(2, "Jake")

In this context, Encapsulation restricts direct access to employee data, presenting a protective layer via designated methods.

Real-life Example 2: Graphic User Interface (GUI) Development (Polymorphism)

When transitioning to GUI development, consider the creation of controls like buttons or checkboxes. Despite belonging to the same class, each responds differently when clicked. This situation illustrates Polymorphism, which allows us to handle different objects uniformly via a common interface.

Check out this illustrative example:

Python
1class Control: # common base class 2 def click(self): # method that can be overridden 3 pass 4 5class Button(Control): # derived class 6 def click(self): 7 print("Button Clicked!") # overridden method 8 9class CheckBox(Control): # derived class 10 def click(self): 11 print("CheckBox Clicked!") # overridden method 12 13# Create objects 14b = Button() 15c = CheckBox() 16 17# Click Controls 18b.click() # Outputs: Button Clicked! 19c.click() # Outputs: CheckBox Clicked!

Despite sharing the common click interface, different controls display unique responses. This characteristic demonstrates Polymorphism.

Real-life Example 3: Creating a Web Page Structure (Composition)

Let's explore the Composition design pattern through a Pythonic approach to create a simple web page structure. Here, we'll build a fundamental structure representing a webpage composed of various elements like headers, paragraphs, and lists. This abstraction allows us to understand how composite objects work together to form a larger system.

Python
1class WebPageElement: 2 def render(self): 3 pass 4 5class Header(WebPageElement): 6 def __init__(self, text): 7 self.text = text 8 9 def render(self): 10 return f"<h1>{self.text}</h1>" 11 12class Paragraph(WebPageElement): 13 def __init__(self, text): 14 self.text = text 15 16 def render(self): 17 return f"<p>{self.text}</p>" 18 19class List(WebPageElement): 20 def __init__(self, items): 21 self.items = items 22 23 def render(self): 24 items_str = "\n".join([f"<li>{item}</li>" for item in self.items]) 25 return f"<ul>{items_str}</ul>" 26 27class WebPage: 28 def __init__(self, title): 29 self.title = title 30 self.elements = [] 31 32 def add_element(self, element: WebPageElement): 33 self.elements.append(element) 34 35 def display(self): 36 elements_str = "\n".join([element.render() for element in self.elements]) 37 return f"<html>\n<head>\n <title>{self.title}\n</title>\n</head>\n<body>\n {elements_str}\n</body>\n</html>" 38 39# Example usage 40page = WebPage("My Web Page") 41page.add_element(Header("Welcome to My Web Page")) 42page.add_element(Paragraph("This is a paragraph of text on the web page.")) 43page.add_element(List(["Item 1", "Item 2", "Item 3"])) 44 45print(page.display()) 46""" 47Outputs: 48<html> 49<head> 50 <title>My Web Page 51</title> 52</head> 53<body> 54 <h1>Welcome to My Web Page</h1> 55<p>This is a paragraph of text on the web page.</p> 56<ul><li>Item 1</li> 57<li>Item 2</li> 58<li>Item 3</li></ul> 59</body> 60</html> 61"""

In this code, we've designed a web page structure using the Composition design pattern. Each web page element (Header, Paragraph, and List) is a WebPageElement, allowing for unified handling while maintaining their specific behaviors (rendering as HTML elements).

The WebPage class acts as a composite object that can contain an arbitrary number of WebPageElement instances, each representing different parts of a web page. By adding these elements to the WebPage and invoking the display method, we dynamically compose a complete web page structure in HTML format.

Real-life Example 4: Creating a Vehicle (Abstraction)

Consider creating a Vehicle class in Python. Here, Abstraction comes into play. You expose only the necessary functionality and abstract away the internal workings of the Vehicle.

Let's see this in code:

Python
1from abc import ABC, abstractmethod 2 3class Vehicle(ABC): 4 def __init__(self, color, engine_type): 5 self.color = color 6 self.engine_type = engine_type 7 self._engine_running = False 8 9 @abstractmethod 10 def start_engine(self): 11 pass 12 13 @abstractmethod 14 def stop_engine(self): 15 pass 16 17 @abstractmethod 18 def drive(self): 19 pass 20 21class Car(Vehicle): 22 def start_engine(self): 23 self._engine_running = True 24 print("Car engine started!") 25 26 def stop_engine(self): 27 self._engine_running = False 28 print("Car engine stopped!") 29 30 def drive(self): 31 if self._engine_running: 32 print("Car is driving!") 33 else: 34 print("Start the engine first!") 35 36# Example usage 37car = Car("red", "gasoline") 38car.start_engine() 39car.drive() 40""" 41Output: 42Car engine started! 43Car is driving! 44"""

Here, the Vehicle abstract class exposes relevant and necessary functions such as start_engine(), stop_engine(), and drive(), and Car class implements this abstract class, providing concrete implementations. However, it hides or abstracts away internal state management (_engine_running). This is a basic instance of Abstraction, which simplifies the interaction with the class and hides underlying complexity.

Design Pattern Identification

Let's recap the major OOP patterns:

  • Encapsulation: This pattern confines data and related methods into one unit, veiling direct data access.
  • Abstraction: This pattern offers a simplified interface, cloaking complexity.
  • Polymorphism: This pattern facilitates treating different objects as related objects of a common superclass.
  • Composition: This pattern builds elaborate systems by composing closely related objects.

Reflect on these principles and practice applying them to a variety of scenarios to better recognize suitable patterns.

Lesson Summary and Practice

Great job! You've poked and prodded at the practical applications of OOP design patterns. We've explored the use of Encapsulation in Database Management Systems, the pivotal role of Polymorphism in GUI development, the important of composition when designing a web page builder, and how abstraction helps to build a vehicle structure.

Next up are hands-on exercises to reinforce these concepts. Remember, practice is the master key to understanding these concepts. So keep coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.