Prefer looser coupling over tighter coupling
Dependency is the key problem in software development at all scales
Software design is the art of managing inter-dependencies between software components. It aims at minimizing artificial (technical) dependencies and introduces the necessary abstractions and compromises.
Coupling is one of the most important ideas to think about when we start to think about how to manage complexity.
All three (Beck, Iglberger, Farley) talk about the same underlying concept:
- Dependencies are links between components.
- Coupling is the strength and cost of those dependencies.
- Good design minimizes unnecessary or tight coupling so components can change, test, and evolve independently.
Coupling is defined as “the degree of interdependence between software modules; a measure of how closely connected two routines or modules are; the strength of the relationships between modules.”1
Example
Section titled “Example”In the next code snippet, there is tight coupling between UserService and ConsoleLogger, which makes the logging behavior hard2 to test, replace, or extend.
class ConsoleLogger {public: void log(const std::string& msg) { std::cout << "[LOG] " << msg << "\n"; }};
class UserService { ConsoleLogger logger; // hard dependency
void createUser(const std::string& name) { logger.log("Creating user: " + name); }};The classical way to reduce the coupling, to remove the hard dependency between UserService and ConsoleLogger, is to use Dependency Injection.
Dependency Injection with dynamic polymorphism
Section titled “Dependency Injection with dynamic polymorphism”class Logger {public: virtual void log(const std::string& msg) = 0; virtual ~Logger() = default;};
struct ConsoleLogger : Logger { void log(const std::string& msg) override { std::cout << "[LOG] " << msg << "\n"; }};
class UserService { Logger& logger; // depends on abstraction, not detail
public: UserService(Logger& logger) : logger(l) {}
void createUser(const std::string& name) { logger.log("Creating user: " + name); }};Dependency Injection with static polymorphism
Section titled “Dependency Injection with static polymorphism”#include <iostream>#include <string>#include <utility>
struct ConsoleLogger { void log(const std::string& msg) const { std::cout << "[LOG] " << msg << '\n'; }};
template <typename Logger>class UserService { Logger logger;
public: explicit UserService(Logger logger) : logger(std::move(logger)) {}
void createUser(const std::string& name) { logger.log("Creating user: " + name); }};
int main() { ConsoleLogger logger; UserService<ConsoleLogger> service{logger}; service.createUser("Alice");}#include <iostream>#include <string>#include <utility>#include <type_traits>
struct ConsoleLogger { void log(const std::string& msg) const { std::cout << "[LOG] " << msg << '\n'; }};
template <typename Logger>class UserService { static_assert(std::is_invocable_v<decltype(&Logger::log), Logger, const std::string&>, "Logger must have a log method that accepts const std::string&");
Logger logger;
public: explicit UserService(Logger logger) : logger(std::move(logger)) {}
void createUser(const std::string& name) { logger.log("Creating user: " + name); }};
int main() { ConsoleLogger logger; UserService service{logger}; // CTAD in C++17 service.createUser("Alice");}#include <iostream>#include <string>#include <concepts>#include <utility>
// Define a concept to constrain Logger typestemplate <typename T>concept LoggerConcept = requires(T logger, const std::string& msg) { { logger.log(msg) } -> std::same_as<void>;};
struct ConsoleLogger { void log(const std::string& msg) const { std::cout << "[LOG] " << msg << '\n'; }};
template <LoggerConcept Logger>class UserService { Logger logger;
public: explicit UserService(Logger logger) : logger(std::move(logger)) {}
void createUser(const std::string& name) { logger.log("Creating user: " + name); }};
int main() { ConsoleLogger logger; UserService service{logger}; service.createUser("Alice");}See also
Section titled “See also”Prefer callables over inheritance for simple behavioral patterns
Footnotes
Section titled “Footnotes”-
Wikipedia, https://en.wikipedia.org/wiki/Coupling_(computer_programming) ↩
-
Hard to do without intrusive modifications the UserService. Violating the Open-Closed Principle. ↩