Today's mission involves using multiple Object-Oriented Programming (OOP) principles to tackle complex tasks in C++. When principles like Encapsulation, Abstraction, Polymorphism, and Composition are blended, the resulting code becomes streamlined and easier to manage.
Our goal is to dissect two real-world examples, gaining insights into how these principles can seamlessly orchestrate solutions in C++.
Let's design an online library system using C++ to reinforce our understanding of Encapsulation
and Polymorphism
. Encapsulation
will help us protect the attributes of books, members, and transactions, ensuring they are accessible in a controlled manner. Polymorphism
will demonstrate its power by enabling a single interface for different forms, such as digital and print versions of books.
C++1#include <iostream> 2#include <vector> 3 4// Base class for different types of library users 5class Member { 6public: 7 Member(std::string name) : name(name) {} 8 9 void check_out_book(class Book* book); 10 11private: 12 std::string name; 13}; 14 15// Base class for different types of books 16class Book { 17public: 18 Book(std::string title) : title(title) {} 19 virtual std::string get_book_type() = 0; 20 std::string get_title() { return title; } 21 22private: 23 std::string title; 24}; 25 26// Inherits from Book, represents a digital book 27class DigitalBook : public Book { 28public: 29 DigitalBook(std::string title) : Book(title) {} 30 31 std::string get_book_type() override { 32 return "Digital"; 33 } 34}; 35 36// Inherits from Book, represents a physical book 37class PhysicalBook : public Book { 38public: 39 PhysicalBook(std::string title) : Book(title) {} 40 41 std::string get_book_type() override { 42 return "Physical"; 43 } 44}; 45 46// Implementation of the checkout method 47void Member::check_out_book(Book* book) { 48 std::cout << name << " checked out " << book->get_book_type() << " book " << book->get_title() << ".\n"; 49} 50 51// Library class that manages members and books 52class Library { 53public: 54 void add_member(Member* member) { 55 members.push_back(member); 56 } 57 58 void add_book(Book* book) { 59 books.push_back(book); 60 } 61 62private: 63 std::vector<Member*> members; 64 std::vector<Book*> books; 65}; 66 67int main() { 68 Library my_library; 69 70 Member alice("Alice"); 71 Member bob("Bob"); 72 73 my_library.add_member(&alice); 74 my_library.add_member(&bob); 75 76 DigitalBook digital_book("The C++ Handbook"); 77 PhysicalBook physical_book("Learning C++ Design Patterns"); 78 79 my_library.add_book(&digital_book); 80 my_library.add_book(&physical_book); 81 82 alice.check_out_book(&digital_book); // Prints: Alice checked out Digital book The C++ Handbook. 83 bob.check_out_book(&physical_book); // Prints: Bob checked out Physical book Learning C++ Design Patterns. 84 85 return 0; 86}
In this code snippet, Encapsulation
is clearly observed through the class structures and the controlled access to their attributes. Polymorphism
is vividly illustrated by how both DigitalBook
and PhysicalBook
classes inherit from the Book
class but provide their implementations of the get_book_type
method. This setup allows objects of DigitalBook
and PhysicalBook
to be used interchangeably when a book's type needs to be identified, demonstrating polymorphism's capability to work with objects of different classes through a common interface.
Encapsulation
ensures that details about members and books are well-contained within their respective classes.Polymorphism
showcases flexibility by treating different book types uniformly, making the system more adaptive and scalable.
Next, we'll develop a shape drawing application in C++ to draw various shapes. For this, we'll employ the principles of Abstraction
and Composition
.
Abstraction
simplifies the complexity associated with drawing different shapes.Composition
manages composite shapes.
Here's how we translate these principles into our shape drawing application:
C++1#include <iostream> 2#include <vector> 3 4// Define the basic Shape class 5class Shape { 6public: 7 virtual void draw() = 0; // Pure virtual method to be implemented in each subclass 8}; 9 10// Define the Circle class 11class Circle : public Shape { 12public: 13 void draw() override { 14 std::cout << "Drawing a circle.\n"; 15 } 16}; 17 18// Define the Square class 19class Square : public Shape { 20public: 21 void draw() override { 22 std::cout << "Drawing a square.\n"; 23 } 24}; 25 26// Define the ShapeComposite class 27class ShapeComposite : public Shape { 28public: 29 void add_shape(Shape* shape) { 30 shapes.push_back(shape); 31 } 32 33 void draw() override { 34 for (const auto& shape : shapes) { 35 shape->draw(); 36 } 37 } 38 39private: 40 std::vector<Shape*> shapes; 41}; 42 43int main() { 44 Circle circle; 45 Square square; 46 47 // Drawing individual shapes 48 circle.draw(); // Output: Drawing a circle. 49 square.draw(); // Output: Drawing a square. 50 51 // Create a ShapeComposite instance for composite shapes 52 ShapeComposite composite_shape; 53 54 // Add individual shapes to the composite 55 composite_shape.add_shape(&circle); 56 composite_shape.add_shape(&square); 57 58 // Drawing the composite shape 59 composite_shape.draw(); 60 /* 61 Output: 62 Drawing a circle. 63 Drawing a square. 64 */ 65 66 return 0; 67}
This example unveils how Abstraction streamlines the process of drawing different shapes(draw
method), and Composition handles complex shapes(vector<Shape*>
).
Well done! You combined multiple OOP principles in C++ to respond to complex tasks. By dissecting real-world examples, we understood how these principles found their applications. Now, it's time to put this knowledge to work. Practice fortifies concepts, transforming knowledge into expertise. So, let's get coding!