Lesson 1
Clean Code with Multiple Classes in C++
Introduction

Welcome to the very first lesson of the "Clean Code with Multiple Classes" course! 🎉 This course aims to guide you in writing code that's easy to understand, maintain, and enhance. Within the broader scope of clean coding, effective class collaboration is crucial for building well-structured applications. In this lesson, we will delve into the intricacies of class collaboration and coupling — key factors that can make or break the maintainability of your software. Specifically, we'll address some common "code smells" that indicate problems in class interactions and explore ways to resolve them.

Overview of Class Collaboration Challenges

Let's dive into the challenges of class collaboration by focusing on four common code smells:

  • Feature Envy: Occurs when a method in one class is overly interested in methods or data in another class.
  • Inappropriate Intimacy: Describes a situation where two classes are too closely interconnected, sharing private details.
  • Message Chains: Refer to sequences of method calls across several objects, indicating a lack of clear abstraction.
  • Middle Man: Exists when a class mainly delegates its behavior to another class without adding functionality.

Understanding these code smells will enable you to improve your class designs, resulting in cleaner and more maintainable code.

Problems Arising During Class Collaboration

These code smells can significantly impact system design and maintainability. Let's consider their implications:

  • They can lead to tightly coupled classes, making them difficult to modify or extend. 🔧
  • Code readability decreases, as it becomes unclear which class is responsible for which functionality.

Addressing these issues often results in code that's not only easier to read but also more flexible and scalable. Tackling these problems can markedly enhance software architecture, making it more robust and adaptable.

Feature Envy

Feature Envy occurs when a method in one class is more interested in the fields or methods of another class than its own. This results in a dependency that's best avoided for clearer separation of concerns.

C++
1#include <vector> 2 3class Item { 4public: 5 Item(double p, int q) : price(p), quantity(q) {} 6 7 double getPrice() const { 8 return price; 9 } 10 11 int getQuantity() const { 12 return quantity; 13 } 14 15private: 16 double price; 17 int quantity; 18}; 19 20class ShoppingCart { 21public: 22 // Calculate total price by directly accessing price and quantity of each Item 23 double calculateTotalPrice() const { 24 double total = 0; 25 for (const auto& item : items) { 26 total += item.getPrice() * item.getQuantity(); // Feature Envy: ShoppingCart is overly interested in Item 27 } 28 return total; 29 } 30 31 void addItem(const Item& item) { 32 items.push_back(item); 33 } 34 35private: 36 std::vector<Item> items; 37};

To refactor, move the logic to the Item class, allowing each Item to calculate its own total, thus reducing dependency and distributing responsibility:

C++
1class Item { 2public: 3 Item(double p, int q) : price(p), quantity(q) {} 4 5 // Each Item is responsible for calculating its own total 6 double calculateTotal() const { 7 return price * quantity; 8 } 9 10private: 11 double price; 12 int quantity; 13}; 14 15class ShoppingCart { 16public: 17 // ShoppingCart now uses Item's method to calculate the total price 18 double calculateTotalPrice() const { 19 double total = 0; 20 for (const auto& item : items) { 21 total += item.calculateTotal(); 22 } 23 return total; 24 } 25 26 void addItem(const Item& item) { 27 items.push_back(item); 28 } 29 30private: 31 std::vector<Item> items; 32};
Inappropriate Intimacy

Inappropriate Intimacy occurs when a class is overly dependent on the internal details of another class. This can lead to tight coupling and a lack of encapsulation.

C++
1#include <iostream> 2#include <string> 3 4class Book { 5public: 6 Book(const std::string& t, const std::string& a) : title(t), author(a) {} 7 8 std::string getTitle() const { 9 return title; 10 } 11 12 std::string getAuthor() const { 13 return author; 14 } 15 16private: 17 std::string title; 18 std::string author; 19}; 20 21class Library { 22public: 23 // Directly accessing Book's details can lead to inappropriate intimacy 24 void printBookDetails(const Book& book) const { 25 std::cout << "Title: " << book.getTitle() << "\n"; // Accessing Book's internal data 26 std::cout << "Author: " << book.getAuthor() << "\n"; // Accessing Book's internal data 27 } 28};

To refactor, allow the Book class to handle its own representation, enabling it to encapsulate its details and encouraging separation of concerns:

C++
1class Book { 2public: 3 Book(const std::string& t, const std::string& a) : title(t), author(a) {} 4 5 // Book is now responsible for its own details representation 6 std::string getDetails() const { 7 return "Title: " + title + "\nAuthor: " + author; 8 } 9 10private: 11 std::string title; 12 std::string author; 13}; 14 15class Library { 16public: 17 // Library now relies on Book to provide its details, reducing inappropriate intimacy 18 void printBookDetails(const Book& book) const { 19 std::cout << book.getDetails() << "\n"; 20 } 21};
Message Chains

Message Chains occur when classes need to traverse multiple objects to access the methods they require. This indicates a lack of clear abstraction and can make code difficult to read and maintain.

C++
1#include <string> 2 3class ZipCode { 4public: 5 std::string getPostalCode() const { 6 return "90210"; 7 } 8}; 9 10class Address { 11public: 12 Address(ZipCode z) : zipCode(z) {} 13 14 ZipCode getZipCode() const { 15 return zipCode; 16 } 17 18private: 19 ZipCode zipCode; 20}; 21 22class User { 23public: 24 User(Address a) : address(a) {} 25 26 Address getAddress() const { 27 return address; 28 } 29 30private: 31 Address address; 32}; 33 34// Usage 35User user(Address(ZipCode())); 36std::string postalCode = user.getAddress().getZipCode().getPostalCode(); // Message chains, accessing through multiple objects

To simplify, encapsulate the access within methods, providing a clearer and more direct interface:

C++
1class Address { 2public: 3 Address(ZipCode z) : zipCode(z) {} 4 5 // Delegate access to the postal code to the Address class 6 std::string getPostalCode() const { 7 return zipCode.getPostalCode(); 8 } 9 10private: 11 ZipCode zipCode; 12}; 13 14class User { 15public: 16 User(Address a) : address(a) {} 17 18 // User now provides direct access to its postal code 19 std::string getUserPostalCode() const { 20 return address.getPostalCode(); 21 } 22 23private: 24 Address address; 25}; 26 27// Usage 28User user(Address(ZipCode())); 29std::string postalCode = user.getUserPostalCode(); // Simplified access
Middle Man

A Middle Man problem occurs when a class primarily exists to delegate its functionalities to another class without adding any functionality of its own. This can create unnecessary complexity in your class design.

C++
1class Service { 2public: 3 void performAction() { 4 // Action performed 5 } 6}; 7 8class Controller { 9private: 10 Service service; 11 12public: 13 // Controller is just passing the call to Service 14 void execute() { 15 service.performAction(); 16 } 17};

To refactor, remove the unnecessary middle man or reassign responsibility, resulting in a more streamlined and efficient design:

C++
1class Service { 2public: 3 void performAction() { 4 // Action performed 5 } 6}; 7 8// Usage 9Service service; 10service.performAction(); // Direct call, removing the unnecessary middle man
Summary and Practice Heads-Up

In this lesson, you've explored several code smells associated with suboptimal class collaboration and coupling, including Feature Envy, Inappropriate Intimacy, Message Chains, and Middle Man. By identifying and refactoring these smells, you can elevate your code's clarity and maintainability.

Get ready to put these concepts into practice with upcoming exercises, where you'll identify and refactor code smells, strengthening your skills. Keep striving for cleaner, more effective code! 🌟

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