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

Chapter 9 Design Patterns and Idioms

Dependency Injection (DI)

“Dependency Injection is a key element of agile architecture.”

—Ward Cunningham, paraphrased from the “Agile and Traditional Development” panel discussion at Pacific NW Software Quality Conference (PNSQC) 2004

The fact that I start the section about specific design patterns with one that is not mentioned in the famous book of the Gang of Four has weighty reasons, of course. I am convinced that dependency injection is by far the most important pattern that can help software developers significantly improve their software design. This pattern can be regarded quite rightly as a game changer.

Before we dive deeper into dependency injection, I want to reckon with another pattern that is detrimental to good software design: the singleton!

The Singleton Anti-Pattern

I’m pretty sure that you already know the design pattern named singleton. It is, on first sight, a simple and widespread pattern, not only in the C++ domain (we will see soon that its supposed simplicity can be deceptive). Some code bases are even littered with singletons. This pattern is, for instance, often used for so-called loggers (objects for logging purposes), for database connections, for central user management, or

to represent things from the physical world (e.g., hardware such as USB or printer interfaces). In addition, factories and so-called utility classes, which are a colorful hodgepodge of helper functions, are often implemented as singletons. The latter are a code smell, because they are a sign of weak cohesion (see Chapter 3).

The authors of Design Patterns have been regularly asked by journalists when they will revise their book and publish a new edition. And their regular answer was that they do not see any reason for this, because the contents of the book are still largely valid. In an interview with the online journal InformIT, however, they allowed themselves to give a more detailed answer. Here is a small excerpt from the entire interview, which reveals an interesting opinion from Gamma about singletons (Larry O’Brien was the interviewer, and Erich Gamma gives the answer):

[...]

Larry: How would you refactor “Design Patterns”?

378

Chapter 9 Design Patterns and Idioms

Erich: We did this exercise in 2005. Here are some notes from our session. We have found that the object-oriented design principles and most of the patterns haven’t changed since then. (…)

When discussing which patterns to drop, we found that we still love them all. (Not really—I’m in favor of dropping Singleton. Its use is almost always a design smell.)

—Design Patterns 15 Years Later: An Interview with Erich Gamma, Richard Helm, and Ralph Johnson, 2009 [InformIT09]

So, why did Erich Gamma say that the Singleton pattern is almost always a design smell? What’s wrong with it?

To answer this, let’s first look at what goals are achieved by means of singletons. What requirements can be fulfilled with this pattern? Here is the mission statement of the Singleton pattern from the GoF book:

“Ensure a class only has one instance and provide a global point of access to it.”

—Erich Gamma, et al., Design Patterns [Gamma95]

This statement contains two conspicuous aspects. On the one hand, the mission of this pattern is to control and manage the whole lifecycle of its one-and-only instance. In accordance with the principle of separation of concerns, the management of the lifecycle of an object should be independent and separated from its application—or domain-­ specific business logic. In a singleton, these two concerns are mixed together.

On the other hand, a global access to its one-and-only instance is provided, so that every other object in the application can use it. This talk about a “global point of access” in the context of object-orientation appears fishy and should raise red flags.

Let’s first look at a general implementation style of a singleton in C++, the so-called Meyers’ Singleton, named after Scott Meyers, the author of the Effective C++ book [Meyers05]. See Listing 9-1.

379

Chapter 9 Design Patterns and Idioms

Listing 9-1.  An Implementation of Meyers’ Singleton in Modern C++

#pragma once

class Singleton final { public:

static Singleton& getInstance() { static Singleton theInstance { }; return theInstance;

}

int doSomething() { return 42;

}

// ...more member functions doing more or less useful things here...

private:

Singleton() = default; Singleton(const Singleton&) = delete;

Singleton(Singleton&&) noexcept = delete; Singleton& operator=(const Singleton&) = delete; Singleton& operator=(Singleton&&) noexcept = delete; // ...

};

One of the main advantages of this implementation style of a singleton is that since C++11, the construction process of the one-and-only instance using a static variable inside getInstance() is thread-safe by default. Be careful, because that does not automatically mean that all the other member functions of the singleton are thread-safe too! The latter must be ensured by the developer.

In source code, the use of such a global singleton instance typically looks like Listing 9-2.

Listing 9-2.  An Excerpt from the Implementation of an Arbitrary Class That Uses the Singleton

001 #include "AnySingletonUser.h"

002 #include "Singleton.h"

380

Chapter 9 Design Patterns and Idioms

003 #include <string>

004

... // ...

024

025 void AnySingletonUser::aMemberFunction() {

... // ...

040 std::string result = Singleton::getInstance().doThis();

... // ...

050 }

051

... // ...

089

090 void AnySingletonUser::anotherMemberFunction() {

... //...

098 int result = Singleton::getInstance().doThat();

... //...

104 double value = Singleton::getInstance().doSomethingMore();

... //...

110}

111// ...

I think it should now be clear what one of the main problems with singletons is. Due to their global visibility and accessibility, they are simply used anywhere inside the implementation of other classes. That means that in the software design, all the dependencies to this singleton are hidden inside the code. You cannot see these dependencies by examining the interfaces of your classes, that is, their attributes and methods.

And the AnySingletonUser class exemplified in Listing 9-2 is only representative of perhaps hundreds of classes within a large code base, many of which also use the

Singleton at different places. In other words, a singleton in OO is like a global variable in procedural programming. You can use this global object everywhere, and you cannot see that usage in the interface of the using class, but only in its implementation.

This has a significant negative impact on the dependency situation in a project, as depicted in Figure 9-1.

381

Chapter 9 Design Patterns and Idioms

Figure 9-1.  Loved by everyone: the singleton!

Note  Perhaps you are wondering why, when looking at Figure 9-1, there is a private member variable instance inside the Singleton class EverybodysDarling, which cannot be found in this form in Meyers’s

recommended implementation. Well, UML is programming language agnostic, that is, as a multipurpose modeling language it does not know about C++, Java, or other OO languages. In fact, in Meyers’s Singleton there is a variable that holds the one-and-only instance, but there is not a graphical notation for a variable with static storage duration in UML, because this feature is proprietary in C++. Therefore, I chose to represent this variable as a private static member. This makes the representation compatible with the no longer recommended Singleton implementation described in the GoF book [Gamma95].

I think it’s easy to imagine that all these dependencies will have major drawbacks regarding reusability, maintainablility, and testability. All those client classes of the singleton are tightly coupled to it (remember the good property of loose coupling discussed in Chapter 3).

382