Welcome to the third lesson of the "Applying Clean Code Principles" course. In our journey so far, we've talked about the importance of the DRY (Don't Repeat Yourself) principle to eliminate redundancy in code. We followed that with the KISS (Keep It Simple, Stupid) principle, which highlights the value of simplicity in software development. Today, our spotlight is on the Law of Demeter — a key guideline in object-oriented programming. By limiting the knowledge that an object has about other objects, this lesson will guide you in crafting more maintainable and modular code. 🤓
The Law of Demeter was introduced by Karl J. Lieberherr and suggests that an object should only communicate with its immediate collaborators, avoiding the entire system. By reducing dependency between parts, you'll find your code easier to maintain and scale. In simple terms, a method X
of the class C
should only call methods of:
- Class
C
itself - An object created by
X
- An object passed as an argument to
X
- An object held in an instance variable of
C
- A static field
With these principles, you control how parts of your application interact, leading to a more organized structure. Let's explore how this works with examples. 🚀
For the first point, a method should only access its own class's methods:
Python1class Car: 2 def start(self): 3 self.check_fuel() 4 self.ignite() 5 6 def check_fuel(self): 7 print("Checking fuel level...") 8 9 def ignite(self): 10 print("Igniting the engine...") 11
In this example, the start
method interacts solely with methods within the Car
class itself. This shows how you maintain clear boundaries, adhering to the Law of Demeter.
Next, a method can interact with the objects it creates:
Python1class Library: 2 def borrow_book(self, title): 3 book = Book(title) 4 book.issue() 5 return book 6 7class Book: 8 def __init__(self, title): 9 self.title = title 10 11 def issue(self): 12 print("Book issued:", self.title)
Here, the Library
class creates a Book
instance and calls the issue
method on it. This usage pattern complies with the Law of Demeter, where Library
interacts with the newly-created Book
. 📚
Continuing, let's look at interacting with objects passed as arguments:
Python1class Printer: 2 def print(self, document): 3 document.send_to_printer() 4 5class Document: 6 def send_to_printer(self): 7 print("Document is being printed...")
The Printer
class method print
communicates with the Document
object passed as an argument, aligning with the Law of Demeter by limiting communication to direct method parameters. 🖨️
Objects held in instance variables of a class can also be accessed:
Python1class House: 2 def __init__(self): 3 self.door = Door() 4 5 def lock_house(self): 6 self.door.close() 7 8class Door: 9 def close(self): 10 print("Door is closed.")
In this example, the House
class interacts with its door
through the lock_house
method, showcasing compliance by interacting with an object it holds in an instance variable. However, it's important to note that while accessing the door
object directly is permissible, the Law of Demeter advises against delving deeply into the door
's internals by calling multiple methods or chaining calls, as this can lead to tight coupling. 🏠
Finally, let's see a method interacting with static fields. While static fields are convenient, they should generally be used cautiously since they can lead to shared state issues in larger applications:
Python1class TemperatureConverter: 2 conversion_factor = 9.0 / 5.0 3 4 @classmethod 5 def celsius_to_fahrenheit(cls, celsius): 6 return int((celsius * cls.conversion_factor) + 32)
Here, conversion_factor
is defined as a class variable. Accessing static fields like this is compliant with the Law of Demeter. 🌡️
Here's an example that violates the Law of Demeter:
Python1class Person: 2 def __init__(self, address): 3 self.address = address 4 5 def get_address_details(self): 6 return ("Address: " + self.address.get_first_name() + " " + self.address.get_last_name() + 7 ", " + self.address.get_street() + 8 ", " + self.address.get_city() + 9 ", " + self.address.get_country() + 10 ", ZipCode: " + self.address.get_zip_code()) 11 12class Address: 13 def get_first_name(self): 14 # implementation 15 pass 16 17 def get_last_name(self): 18 # implementation 19 pass 20 21 def get_street(self): 22 # implementation 23 pass 24 25 def get_city(self): 26 # implementation 27 pass 28 29 def get_country(self): 30 # implementation 31 pass 32 33 def get_zip_code(self): 34 # implementation 35 pass
In this case, Person
is directly accessing multiple fields through Address
, leading to tight coupling. Person
relies on the internal structure of Address
, which might result in fragile code.
Let's refactor the previous code to adhere to the Law of Demeter:
Python1class Person: 2 def __init__(self, address): 3 self.address = address 4 5 def get_address_details(self): 6 return self.address.get_address_line() 7 8class Address: 9 def get_address_line(self): 10 return (self.get_first_name() + " " + self.get_last_name() + 11 ", " + self.get_street() + 12 ", " + self.get_city() + 13 ", " + self.get_country() + 14 ", ZipCode: " + self.get_zip_code()) 15 16 def get_first_name(self): 17 # implementation 18 pass 19 20 def get_last_name(self): 21 # implementation 22 pass 23 24 def get_street(self): 25 # implementation 26 pass 27 28 def get_city(self): 29 # implementation 30 pass 31 32 def get_country(self): 33 # implementation 34 pass 35 36 def get_zip_code(self): 37 # implementation 38 pass
By encapsulating all the address details within the get_address_line
method in the Address
class, the dependency is minimized, and Person
no longer accesses the internals of Address
directly.
The Law of Demeter plays a vital role in writing clean, modular code by ensuring objects only interact with their closest dependencies. By understanding and implementing these guidelines, you enhance the modularity and maintainability of your code. As you move on to the practice exercises, challenge yourself to apply these principles and evaluate your code's interactions. Keep these lessons in mind as essential steps toward mastering clean code! 🌟