Home >Backend Development >PHP Tutorial >PHP Master | Introduction to the Law of Demeter

PHP Master | Introduction to the Law of Demeter

Christopher Nolan
Christopher NolanOriginal
2025-02-25 23:26:10140browse

PHP Master | Introduction to the Law of Demeter

Core points

  • Dimit's law, also known as the principle of least knowledge, advocates minimizing the object's understanding of other objects, and advocates only interacting with direct neighbors to enhance modularity and maintainability.
  • Abide by the Dimitt Law can significantly enhance the design of loosely coupled software modules, making the code easier to maintain, test and modify.
  • Common violations of the Dimitter law occur when objects or methods know too much about the structure and elements of other objects, resulting in tightly coupled codes that are difficult to manage and evolve.
  • Practical examples in PHP demonstrate how violations can be cleverly embedded in common practices, such as using a service locator that exposes the internal details of other objects.
  • Refactoring the code to conform to the Dimitian law requires direct interaction with only necessary components to avoid unnecessary intermediates that complicate the architecture and increase dependencies.
  • While the Dimitter law improves code quality by reducing dependencies and promoting high cohesion, it should be applied pragmatically, taking into account the specific context and potential trade-offs in complexity and performance.

Software programming is a balanced combination of art (sometimes euphemism for improvisation) and many proven heuristics to solve certain problems and solve them in a decent way. Few people would disagree that the artistic aspect is by far the hardest to hone and refine. On the other hand, the power behind heuristics is essential to being able to develop software based on good design. With so many heuristics explaining how and why software systems should stick to specific methods, it is quite disappointing to not see them more widely implemented in the PHP world. For example, the Dimitian Law may be one of the most underrated laws in the language field. In fact, the rule’s motto of “talking only with your close friends” seems to be in a rather immature state in PHP, which has led to a decline in overall quality of several object-oriented code bases. Some popular frameworks are actively pushing it forward in an attempt to adhere to the precepts of the law more. It makes no sense to blame each other for violating the Dimitese law, because the best way to mitigate such destruction is to simply take a pragmatic attitude and understand what is actually under the law, so that it is consciously applied when writing object-oriented code. . To join the cause of justice and to study the law more deeply from a practical point of view, in the next few lines I will demonstrate with some practical examples why things as simple as observing the principles of the law are designed in loosely coupled software Module time can really improve efficiency.

It is not a good thing to know too much

is often called the principle of least knowledge, and the rules advocated by the Dimitese law are easy to understand. Simply put, suppose you have a well-designed class that implements a given method, then the method should be limited to calling other methods that belong to the following object:

    An instance of the original class of the
  1. method.
  2. Parameter object of the target method.
  3. Object created by the target method.
  4. The dependency object of the original class of the
  5. method.
  6. Global objects that the original class can access in the target method (Oh!)

Although the list is far from formal (for more formal lists, check out Wikipedia), these key points are easy to understand. In traditional design, it is considered wrong to know too much about another object (which implicitly includes knowing how to access a third object) because in some cases the object must unnecessarily go from top to bottom Iterating through the clumsy intermediates to find the actual dependencies it needs to work as expected. This is a serious design flaw for obvious reasons. The caller has a fairly extensive and detailed understanding of the internal structure of the intermediate, even if this is accessed through several getters. Furthermore, using intermediate objects to get the object required by the caller illustrates a problem in itself. After all, if the same result can be achieved by directly injecting the dependencies, why use such a complex path to get the dependencies or call one of its methods? This process has no meaning at all.

Let's assume we need to build a file storage module that uses a polymorphic encoder internally to pull data into and save it to the given target file. If we deliberately sloppyly connect the module to an injectable service locator, its implementation will look like this:

<code class="language-php"><?php namespace LibraryFile;
use LibraryDependencyInjectionServiceLocatorInterface;

class FileStorage
{
    const DEFAULT_STORAGE_FILE = "data.dat";
    private $locator;
    private $file;

    public function __construct(ServiceLocatorInterface $locator, $file = self::DEFAULT_STORAGE_FILE)  {
        $this->locator = $locator;
        $this->setFile($file);
    }

    public function setFile($file) {
        if (!is_readable($file) || !is_writable($file)) {
            throw new InvalidArgumentException(
                "The target file is invalid.");
        }
        $this->file = $file;
        return $this;
    }

    public function write($data) {
        try {
            return file_put_contents($this->file, 
                $this->locator->get("encoder")->encode($data),
                LOCK_EX);
        }
        catch (Exception $e) {
            throw new $e(
                "Error writing data to the target file: " . 
                $e->getMessage());
        }
    }

    public function read() {
        try {
            return $this->locator->get("encoder")->decode(
                @file_get_contents($this->file));
        }
        catch(Exception $e) {
            throw new $e(
                "Error reading data from the target file: " .
                $e->getMessage());
        }
    }
}</code>

Omit some irrelevant implementation details, focusing on the constructor of the FileStorage class and its write() and read() methods. This class injects an instance of a service locator that has not been defined and is later used to fetch dependencies (the aforementioned encoder) in order to fetch and store data in the target file. This is usually a violation of the Dimitter law, considering that the class traverses the locator first and then reaches the encoder. Caller FileStorage knows too much about the internal structure of the locator, including how to access the encoder, which is definitely not a capability I would like to praise. It is an artifact inherently rooted in the nature of a service locator (which is why some people think of it as anti-pattern) or any other type of static or dynamic registry, which I pointed out before. To get a more comprehensive understanding of this problem, let's check the implementation of the locator:

(The codes of locator and encoder are omitted here because they are consistent with the previous output. In order to avoid duplication, I will not repeat them here.)

With the encoder, let's start with all the sample classes together:

(The usage sample code is omitted here because it is consistent with the previous output. In order to avoid duplication, I will not repeat it here.)

Violations of this law are a rather covert problem in this case and are difficult to track from the surface, except for the use of mutator of the locator, which suggests that at some point the encoder will be somehow taken by FileStorage instance access and use. In any case, the fact that we know that the violation is there hidden outside the outside world not only reveals too much information about the locator structure, but also unnecessarily couples the FileStorage class to the locator itself. Just follow the rules of this rule and get rid of the locator, we can remove the coupling while providing FileStorage with the actual collaborators it needs to do its business. There are no more clumsy, exposed intermediates along the way! Fortunately, all this nonsense can be easily converted into working code with a little effort. Simply view the enhanced, Dimitri law-compliant version of FileStorage class here:

(The refactored FileStorage code is omitted here because it is consistent with the previous output. In order to avoid duplication, I will not repeat it here.)

This is indeed easy to refactor. Now, the class directly uses any implementer of the EncoderInterface interface, avoiding traversing the internal structure of unnecessary intermediates. The example is undoubtedly trivial, but it does illustrate a point of validity and demonstrate why following the precepts of Dimitri's law is one of the best things you can do to improve the design of the class. However, a special case of this rule is deeply explored in Robert Martin's book "The Way of Code: Agile Software Development Manual" and deserves special analysis. Please take a moment to think carefully: What happens if FileStorage is defined as getting its collaborator through a Data Transfer Object (DTO)?

(The code example using DTO is omitted here, because it is consistent with the previous output, and in order to avoid duplication, I will not repeat it here.)

This is definitely an interesting way to implement file storage classes, as it now uses injectable DTOs to transfer and use encoder internally. The question to be answered is whether this method really violates the law. From a purist perspective, it does violate, because DTO is undoubtedly an intermediate that exposes its entire structure to the caller. However, DTO is just a normal data structure, unlike earlier service locators, it does not behave at all. And the purpose of the data structure is...yes, to disclose its data. This means that as long as the intermediate does not implement the behavior (which is exactly the opposite of the behavior of a regular class, which hides its data because it exposes the behavior), the Dimitter law remains intact. The following code snippet shows how to use FileStorage using the problematic DTO:

(The code example using DTO is omitted here, because it is consistent with the previous output, and in order to avoid duplication, I will not repeat it here.)

This approach is much more troublesome than passing an encoder directly into a file storage class, but the example shows that some tricky implementations that may appear at first glance to be blatant violations of the law, are usually quite harmless, as long as they Just use a data structure without any additional behavior.

Conclusion

As various complex, sometimes esoteric heuristics are popular in OOP, it seems meaningless to add another principle that obviously has no obvious positive impact on the design of layer components. However, the Dimitter Law is by no means a principle that is hardly applied in the real world. Despite its gorgeous name, the Dimitt Law is a powerful paradigm with the main goal of facilitating the implementation of highly decoupled application components by eliminating any unnecessary intermediates. Just follow its precepts and of course don't be blindly dogmatic and you'll see an improvement in code quality. ensure.

(The FAQs part is omitted here because it is consistent with the previous output. In order to avoid duplication, I will not repeat it here.)

The above is the detailed content of PHP Master | Introduction to the Law of Demeter. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn