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!
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:
C++1#include <iostream> 2#include <map> 3#include <string> 4 5class Employees { 6private: 7 std::map<int, std::string> employees; // private data member 8 9public: 10 void add_employee(int eid, const std::string& name) { // method to operate on private data 11 employees[eid] = name; 12 } 13 14 void update_employee(int eid, const std::string& new_name) { // method to operate on private data 15 if (employees.find(eid) != employees.end()) { 16 employees[eid] = new_name; 17 } 18 } 19 20 void display() const { 21 for (const auto& employee : employees) { 22 std::cout << "ID: " << employee.first << " Name: " << employee.second << std::endl; 23 } 24 } 25}; 26 27int main() { 28 Employees employees; 29 employees.add_employee(1, "John"); 30 employees.add_employee(2, "Mark"); 31 32 employees.update_employee(2, "Jake"); 33 employees.display(); 34 // Outputs: 35 // ID: 1 Name: John 36 // ID: 2 Name: Jake 37 return 0; 38}
In this context, Encapsulation
restricts direct access to employee data, presenting a protective layer via designated methods.
Consider creating a Vehicle
class in C++. 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:
C++1#include <iostream> 2 3class Vehicle { 4protected: 5 std::string color; 6 std::string engine_type; 7 bool engine_running; 8 9public: 10 Vehicle(const std::string& c, const std::string& e) : color(c), engine_type(e), engine_running(false) {} 11 12 virtual void start_engine() = 0; 13 virtual void stop_engine() = 0; 14 virtual void drive() = 0; 15}; 16 17class Car : public Vehicle { 18public: 19 Car(const std::string& c, const std::string& e) : Vehicle(c, e) {} 20 21 void start_engine() override { 22 engine_running = true; 23 std::cout << "Car engine started!" << std::endl; 24 } 25 26 void stop_engine() override { 27 engine_running = false; 28 std::cout << "Car engine stopped!" << std::endl; 29 } 30 31 void drive() override { 32 if (engine_running) { 33 std::cout << "Car is driving!" << std::endl; 34 } else { 35 std::cout << "Start the engine first!" << std::endl; 36 } 37 } 38}; 39 40int main() { 41 Car car("red", "gasoline"); 42 car.start_engine(); 43 car.drive(); 44 45 return 0; 46}
Here, the Vehicle
abstract class exposes relevant and necessary functions such as start_engine()
, stop_engine()
, and drive()
, and the 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.
When transitioning to GUI development, consider the creation of controls like buttons or checkboxes. Despite belonging to the same base 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:
C++1#include <iostream> 2 3class Control { // common base class 4public: 5 virtual void click() const = 0; // pure virtual function 6}; 7 8class Button : public Control { // derived class 9public: 10 void click() const override { 11 std::cout << "Button Clicked!" << std::endl; // overridden method 12 } 13}; 14 15class CheckBox : public Control { // derived class 16public: 17 void click() const override { 18 std::cout << "CheckBox Clicked!" << std::endl; // overridden method 19 } 20}; 21 22int main() { 23 Button b; 24 CheckBox c; 25 26 // Click Controls 27 b.click(); // Outputs: Button Clicked! 28 c.click(); // Outputs: CheckBox Clicked! 29 30 return 0; 31}
Despite sharing the common click
interface (that is, they both implement the same method), different controls display unique responses. This characteristic demonstrates Polymorphism
.
Let's explore the Composition
design pattern through a C++ 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.
C++1#include <iostream> 2#include <vector> 3#include <string> 4 5class WebPageElement { 6public: 7 virtual std::string render() const = 0; 8}; 9 10class Header : public WebPageElement { 11private: 12 std::string text; 13public: 14 Header(const std::string& t) : text(t) {} 15 16 std::string render() const override { 17 return "<h1>" + text + "</h1>"; 18 } 19}; 20 21class Paragraph : public WebPageElement { 22private: 23 std::string text; 24public: 25 Paragraph(const std::string& t) : text(t) {} 26 27 std::string render() const override { 28 return "<p>" + text + "</p>"; 29 } 30}; 31 32class List : public WebPageElement { 33private: 34 std::vector<std::string> items; 35public: 36 List(const std::vector<std::string>& itm) : items(itm) {} 37 38 std::string render() const override { 39 std::string items_str; 40 for (const auto& item : items) { 41 items_str += "<li>" + item + "</li>\n"; 42 } 43 return "<ul>" + items_str + "</ul>"; 44 } 45}; 46 47class WebPage { 48private: 49 std::string title; 50 std::vector<WebPageElement*> elements; 51public: 52 WebPage(const std::string& t) : title(t) {} 53 54 void add_element(WebPageElement* element) { 55 elements.push_back(element); 56 } 57 58 void display() const { 59 std::string elements_str; 60 for (const auto& element : elements) { 61 elements_str += element->render() + "\n"; 62 } 63 std::cout << "<html>\n<head>\n <title>" << title << "\n</title>\n</head>\n<body>\n " << elements_str << "\n</body>\n</html>" << std::endl; 64 } 65}; 66 67int main() { 68 WebPage page("My Web Page"); 69 page.add_element(new Header("Welcome to My Web Page")); 70 page.add_element(new Paragraph("This is a paragraph of text on the web page.")); 71 page.add_element(new List({"Item 1", "Item 2", "Item 3"})); 72 73 page.display(); 74 75 return 0; 76}
In this code, we've designed a web page structure using the Composition
design pattern. Moreover, we've also leveraged Polymorphism
, as each web page element (Header
, Paragraph
, and List
) inherits from the common WebPageElement
interface and implements the render
method differently. This allows for unified handling of different web page elements 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.
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.
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 importance 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!