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

Chapter 6 Modularization

Following the Law of Demeter when designing software can reduce the number of dependencies significantly. This leads to loose coupling and fosters both the information hiding principle and the open-closed principle. As with many other principles and rules, too, there may be some justified exceptions, where a developer must vary from this principle for very good reasons.

Avoid Anemic Classes

In several projects, I’ve seen classes that looked like the one in Listing 6-29.

Listing 6-29.  A Class Without Functionality that Serves Only as a Bucket for a Bunch of Data

class Customer { public:

void setId(const unsigned int id); unsigned int getId() const;

void setForename(std::string_view forename); std::string getForename() const;

void setSurname(std::string_view surname); std::string getSurname() const;

//...more setters/getters here...

private:

unsigned int id; std::string forename; std::string surname;

// ...more attributes here...

};

This domain class, representing a customer in an arbitrary software system, does not contain any logic. The logic is in a different place, even that logic which represents exclusive functionality for the Customer, that is, operating only on attributes of the

Customer.

Programmers who did this are using objects as bags for a bunch of data. This is just procedural programming with data structures, and it has nothing to do with object-­ orientation. Also all those setters/getters are totally foolish and violate the information hiding principle severely—actually we could use a simple C-structure (struct) here.

275

Chapter 6 Modularization

Such classes are called anemic classes and should be avoided at all costs. They can often be found in a software design that is an anti-pattern that has been called the

Anemic Domain Model by Martin Fowler [Fowler03]. It is the exact opposite of the basic idea of object-oriented design, which is to combine data and the functionality that works with the data together into cohesive units.

As long as you do not violate the Law of Demeter, you should insert logic into (domain) classes, if this logic is operating on attributes of that class or collaborates only with the immediate neighbors of the class.

Tell, Don’t Ask!

The principle Tell, Don’t Ask has some similarities with the previously discussed Law of Demeter. This principle is the “declaration of war” to all those public get methods, which reveals something about the internal state of an object. Tell Don’t Ask also fosters encapsulation and strengthens information hiding (see Chapter 3). But first and foremost, this principle is about strong cohesion.

Let’s examine a small example. Let’s assume that the member function Engine::start() from the Car example from the section about the Law Of Demeter is implemented as shown in Listing 6-30.

Listing 6-30.  A Possible, But Not Recommendable, Implementation of the Engine::start() Member Function

class Engine { public:

// ...

void start() {

if (! fuelPump.isRunning()) { fuelPump.powerUp();

if (fuelPump.getFuelPressure() < NORMAL_FUEL_PRESSURE) { fuelPump.setFuelPressure(NORMAL_FUEL_PRESSURE);

}

}

if (! ignition.isPoweredUp()) { ignition.powerUp();

}

if (! starter.isRotating()) {

276

Chapter 6 Modularization

starter.revolve();

}

if (engine.hasStarted()) { starter.openClutchToEngine(); starter.stop();

}

}

// ...

private:

FuelPump fuelPump; Ignition ignition; Starter starter;

static const unsigned int NORMAL_FUEL_PRESSURE { 120 };

};

As it is easy to see, the start() method of the Engine class queries many states from its parts and responds accordingly. Furthermore, the Engine checks the fuel pressure of the fuel pump and adjusts it if it is too low. This also means that the Engine must know the value for the normal fuel pressure. Due to the numerous if branches, the cyclomatic complexity (see Chapter 4) is high.

The Tell Don’t Ask principle reminds us that we should not ask an object to expose information about its internal state and decide outside of this object what to do, if this object could decide it on its own. Basically, this principle reminds us that in object-­ orientation, data, and the operations operating on these data, are to be combined to cohesive units.

If we apply this principle to the example, the Engine::start() method would only tell its parts what they should do, as shown in Listing 6-31.

Listing 6-31.  Delegating Stages of the Starting Procedure to the Responsible Parts of the Engine

class Engine { public:

// ...

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

277

Chapter 6 Modularization

starter.revolve();

}

// ...

private:

FuelPump fuelPump; Ignition ignition; Starter starter;

};

The parts can decide for themselves how they want to execute this command, because they have the knowledge about it. For example, the FuelPump can do all the things what it has to do to build up fuel pressure, as shown in Listing 6-32.

Listing 6-32.  An Excerpt from the FuelPump Class

class FuelPump { public:

// ...

void pump() {

if (! isRunning) { powerUp(); setNormalFuelPressure();

}

}

// ...

private:

void powerUp() { //...

}

void setNormalFuelPressure() {

if (pressure != NORMAL_FUEL_PRESSURE) { pressure = NORMAL_FUEL_PRESSURE;

}

}

278