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

Chapter 9 Design Patterns and Idioms

Figure 9-12.  The Customer uses a LoggerFactory to obtain concrete loggers

Comparing this diagram with Figure 9-5 shows a significant difference: while the CustomerRepository class has no dependency on the assembler, the customer “knows” the factory class when using the Factory pattern. Presumably, this dependency is not

a serious problem, but it makes clear once again that loose coupling is brought to the maximum extent with dependency injection.

Facade

The Facade pattern is a structural pattern that is often used on an architectural level and has the following intent:

“Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.”

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

Structuring a large software system according to the separation of concerns and single responsibility principles (see Chapter 6), and information hiding (see Chapter 3) usually has the result that some kind of bigger components or modules are originated.

425

Chapter 9 Design Patterns and Idioms

Generally, these components or modules can sometimes be referred to as “subsystems.” Even in a layered architecture, individual layers can be considered subsystems.

In order to promote encapsulation, the internal structure of a component or subsystem should be hidden from its clients (see information hiding in Chapter 3). The communication between subsystems, and thus the amount of dependencies between them, should be minimized. It would be fatal, if clients of a subsystem must know details about the internal structure and the interaction of its parts.

A Facade regulates access to a complex subsystem by providing a well-defined and simple interface for clients. Any access to the subsystem must solely be done over the Facade.

The UML diagram in Figure 9-13 shows a subsystem named Billing for preparing invoices. Its internal structure consists of several interconnected parts. Clients

of the subsystem cannot access these parts directly. They have to use the Facade BillingService, which is represented by a UML port (stereotype «facade») on the border of the subsystem.

Figure 9-13.  The Billing subsystem provides a facade called BillingService as an access point for clients

In C++, and also in other languages, a Facade is nothing special. It is often just a simple class that is receiving calls at its public interface and forwarding them to the internal structure of the subsystem. Sometimes it is only a simple forwarding of a call to

426

Chapter 9 Design Patterns and Idioms

one of the internal structural elements of the subsystem, but occasionally a Facade also carries out data conversions, in which case it’s also an adapter (see the section about adapters).

In our example, the Facade class BillingService implements two interfaces, represented by the UML ball-notation. According to the interface segregation principle (ISP; see Chapter 6), the configuration of the Billing subsystem (the Configuration interface) is separated from the generation of bills (the InvoiceCreation interface). Thus, the Facade must override operations that are declared in both interfaces.

The Money Class

If high accuracy is of any importance, you should avoid floating-point values. Floating-­ point variables of type float, double, or long double fail in simple additions, as this small example demonstrates. See Listing 9-39.

Listing 9-39.  When Adding Ten Floating-Point Numbers This way, the Result Is Possibly Not Accurate Enough

#include <assert.h> #include <iostream>

int main() {

double sum = 0.0; double addend = 0.3;

for (int i = 0; i < 10; i++) { sum = sum + addend;

};

assert(sum == 3.0); return 0;

}

427

Chapter 9 Design Patterns and Idioms

If you compile and run this small program, this is what you’ll see as its console output:

Assertion failed: sum == 3.0, file ..\main.cpp, line 13

I think that the cause for this deviation is generally known. Floating-point numbers are stored in a binary format internally. Due to this, it is impossible to store a value of 0.3 (and others) precisely in a variable of type float, double, or long double, because it has no exact representation of finite length in binary. In decimal, we have a similar problem. We can’t represent the value 1/3 (one-third) using only decimal notation. 0.33333333 isn’t completely accurate.

There are several solutions for this problem. For currencies it can be a suitable approach to store the money value in an integer with the required precision, for example, $12.45 will be stored as 1245. If requirements are not very high, an integer can be a feasible solution. Note that the C++ standard does not specify the size of integral types in bytes; thus you must be careful with very big amounts since an integer overflow can occur. If in doubt, a 64-bit integer should be used, as it can hold very large amounts of money.

DETERMINING THE RANGE OF AN ARITHMETIC TYPE

The actual implementation-specific ranges for arithmetic types (either integral or floating-­ point) can be found as class templates in the <limits> header. For example, this is how you would find the maximum range for int:

#include <limits>

constexpr auto INT_LOWER_BOUND = std::numeric_limits<int>::min(); constexpr auto INT_UPPER_BOUND = std::numeric_limits<int>::max();

Another popular approach is to provide a special class for this purpose, the so-called

Money class:

“Provide a class to represent exact amounts of money. A Money class handles different currencies and exchanges between them.”

—Martin Fowler, Patterns of Enterprise Application Architecture

[Fowler02]

428

Chapter 9 Design Patterns and Idioms

The Money Class pattern is basically a class encapsulating a financial amount and its currency, but dealing with money is just one example of this category of classes. There are many other properties, or dimensions, that must be accurately represented, for example, precise measurements in physics (time, voltage, current, distance, mass, frequency, amount of substances, and so on).

1991: PATRIOT MISSILE MISTIMING

MIM-104 Patriot is a surface-to-air missile (SAM) system that was designed and manufactured by the Raytheon Company of the United States. Its typical application was to counter high-­ altitude tactical ballistic missiles, cruise missiles, and advanced aircraft. During the first Persian Gulf War (1990 – 1991), also called operation “Desert Storm,” Patriot was used to shoot down incoming Iraqi SCUD or Al Hussein short-range ballistic missiles.

On February 25, 1991, a battery in Dhahran, a city located in the Eastern Province of Saudi Arabia, failed to intercept a SCUD. The missile struck an Army barracks and caused 28 deaths and 98 injuries.

An investigation report [GAOIMTEC92] revealed that the cause of this failure was an inaccurate calculation of the time since power-up of the system due to computer arithmetic errors.

So that Patriot’s missiles can detect and hit the target after launch, they must be spatially approximated to the target, the “range gate.” To predict where the target will appear next (the deflection angle), some calculations with the system’s time and the target’s flying speed had to be performed. The elapsed time since system’s start was measured in tenths of a second and expressed as an integer. The target’s speed was measured in miles per second and expressed as a decimal value. To calculate the “range gate,” the value of the system’s timer has to be multiplied by 1/10 to get the time in seconds. This calculation was done using registers that were only 24 bits long.

The problem was that the value of 1/10 in decimal cannot be accurately represented in a 24-bit register. The value was chopped at 24 bits after the radix point. The consequence was that the conversion of time from an integer to a real number results in a small loss of precision causing a less accurate time calculation. This accuracy error would probably not have been a problem if the system would only been in operation for a few hours, according to its concept of operation as a mobile system. But in this case, the system has been running for more than

429

Chapter 9 Design Patterns and Idioms

100 hours. The number representing the system’s up-time was quite large. This meant that the small conversion error of 1/10 into its decimal 24-bit representation resulted in a large deviation error of nearly half of a second! An Iraqi SCUD missile travels approximately 800 meters in that time span—far enough to be outside the “range gate” of an approaching Patriot missile.

Although the accurate dealing with amounts of money is a very common case in many business IT systems, you will struggle in vain to find a Money class in most

mainstream C++ base class libraries. But don’t reinvent the wheel! There are multitudes of different C++ Money class implementations out there, just search and you will

get thousands of hits. As is often the case, one implementation doesn’t satisfy all requirements. The key is to understand your problem domain. While choosing (or designing) a Money class, you may consider several constraints and requirements. Here are a few questions that you may have to clarify first:

•\

What is the full range of values to be handled (minimum, maximum)?

•\

Which rounding rules apply? There are national laws or practices for

 

roundings in some countries.

•\

Are there legal requirements for accuracy?

•\

Which standards must be considered (e.g., ISO 4217 International

 

Standard for Currency Codes)?

•\

How will the values be displayed to the user?

•\

How often will conversion take place?

From my perspective, it is absolutely essential to have 100% unit test coverage (see Chapter 2 about unit tests) for a Money class to check whether the class is working as expected under all circumstances. Of course, the Money class has a small drawback compared to the pure number representation with an integer: you lose a smidgen

of performance. This might be an issue in some systems. But I’m convinced that in most cases the advantages will predominate (always keep in mind that premature optimization is bad).

430