Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
roth_stephan_clean_c20_sustainable_software_development_patt-1.pdf
Скачиваний:
25
Добавлен:
27.03.2023
Размер:
7.26 Mб
Скачать

Chapter 6 Modularization

Don’t Talk to Strangers (The Law of Demeter)

Do you remember the car I talked about earlier in this chapter? I described this car as a composition of several parts, for example, body, engine, gears, and so on. And I explained that these parts can consist of parts, which for themselves can also consist of several parts, etc. This leads to a hierarchical top-down decomposition of a car. Of course, a car can have a driver who wants to drive it.

Visualized as an UML class diagram, an excerpt from the car’s decomposition can look like Figure 6-11.

Figure 6-11.  The hierarchical decomposition of a simple car

According to the single responsibility principle discussed in Chapter 5, everything is fine, because every class has a well-defined responsibility.

Now let’s assume that the driver wants to drive the car. This could be implemented in the Driver class, as shown in Listing 6-25.

269

Chapter 6 Modularization

Listing 6-25.  An Excerpt from the Implementation of the Driver Class

class Driver { public:

// ...

void drive(Car& car) const { Engine& engine = car.getEngine();

FuelPump& fuelPump = engine.getFuelPump(); fuelPump.pump();

Ignition& ignition = engine.getIgnition(); ignition.powerUp();

Starter& starter = engine.getStarter(); starter.revolve();

}

// ...

};

What is the problem here? Would you expect as a driver of a car to directly access your car’s engine, to turn on the fuel pump, turn on the ignition system, and let the starter revolve? I go even further: are you even interested in the fact that your car consists of these parts if you just want to drive it?!

I’m pretty sure your clear answer would be no!

Now let’s take a look at Figure 6-12, depicting the relevant part from the UML class diagram to see what impact this implementation has on the design.

270

Chapter 6 Modularization

Figure 6-12.  The bad dependencies of the Driver class

As can easily be seen in Figure 6-12, the Driver class has many awkward dependencies. The Driver is not only dependent from Engine. The class has also several dependency relationships to parts of the Engine. It is easy to imagine that this has some disadvantageous consequences.

What would happen, for example, if the combustion engine was replaced by an electric power train? An electric drive doesn’t have a fuel pump, an ignition system, and a starter. Thus, the consequences would be that the implementation of the class driver would have to be adapted. This violates the open-closed principle (see the earlier

271

Chapter 6 Modularization

section). Furthermore, all public getters that expose the innards of the Car and the Engine to their environment are violating the information hiding principle (see Chapter 3).

Essentially, the previous software design violates the Law of Demeter (LoD), also known as the Principle of Least Knowledge. The Law of Demeter can be regarded

as a principle that says something like “don’t talk to strangers”, or “only talk to your immediate neighbors.” This principle states that you should do shy programming, and the goal is to govern the communication structure within an object-oriented design.

The Law of Demeter postulates the following rules:

•\

A member function is allowed to call other member functions in its

 

own class scope directly.

•\

A member function is allowed to call member functions on member

 

variables that are in its class scope directly.

•\

If a member function has parameters, the member function is

 

allowed to call the member functions of these parameters directly.

•\

If a member function creates local objects, the member function is

 

allowed to call member functions on those local objects.

If one of these four aforementioned kinds of member function calls returns an object that is structurally farther than the immediate neighbors of the class, it is forbidden to call a member function on that object.

WHY THIS RULE IS NAMED LAW OF DEMETER

The name of this principle goes back to the Demeter Project about aspect-oriented software development, where these rules were formulated and strictly applied. The Demeter Project was a research project in the late 1980s with a main focus on making software easier to maintain and expand through adaptive programming. The Law of Demeter was discovered and proposed by Ian M. Holland and Karl Lieberherr who worked in that project. In Greek mythology, Demeter is the sister of Zeus and the goddess of agriculture.

So, what is now the solution in our example to get rid of the bad dependencies? Quite simply, we should ask ourselves what does a driver really want to do? The answer is easy: he wants to start the car! See Listing 6-26.

272

Chapter 6 Modularization

Listing 6-26.  The Only Thing the Refactored Class Driver Has To Do Is Start the Car

class Driver { public:

// ...

void drive(Car& car) const { car.start();

}

// ...

};

And what does the car do with this start command? Also, quite simple: it delegates this method call to its engine. See Listing 6-27.

Listing 6-27.  The Car Delegates the Start Command to its Engine

class Car { public:

// ...

void start() { engine.start();

}

// ...

private:

Engine engine;

};

Last but not least, the engine knows how it can execute the start process by calling the appropriate member functions in the correct order on its parts, which are its immediate neighbors in the software design. See Listing 6-28.

Listing 6-28.  The Engine Internally Causes Everything to Be Fired Up

class Engine { public:

// ...

void start() { fuelPump.pump(); ignition.powerUp();

273

Chapter 6 Modularization

starter.revolve();

}

// ...

private:

FuelPump fuelPump; Ignition ignition; Starter starter;

};

The positive effect of these changes on the object-oriented design can be very clearly seen in the class diagram depicted in Figure 6-13.

Figure 6-13.  Fewer dependencies after the application of the Law of Demeter

The annoying dependencies of the driver to the car’s parts are gone. Instead, the driver can start the car, regardless of the internal structure of the car. The Driver

class doesn’t know that there is an Engine, a FuelPump, etc. All those bad public getter functions, which revealed the innards of the car or the engine to all other classes, are gone. This also means that changes to the Engine and its parts have very local impacts and will not result in cascading changes straight through the whole design.

274