Dependency Diagrams
diagrams serve as a great diagnostic tool as they embody the dependency graph of components and modules, revealing tight coupling and even retain cycles that could potentially lead to rigidity and memory leaks
dependency-diagram
Visualizing the dependency graph
Writing/Reading Dependency Diagrams
There are different types of dependencies you can represent in a dependency diagram using different annotations, lines, and arrows.
Here are the main ones:
1. Solid line, empty head = “inherits from” / “is a”.
A solid line with an empty head denotes that a class inherits from another class.
For example:
class MyViewController: UIViewController { ... }
The MyViewController
class inherits from the UIViewController
class, or the MyViewController
"is a" subtype of UIViewController
.
2. Dashed line, empty head = “conforms to” or “implements”
A dashed line with an empty head denotes that a component conforms/implements a protocol/abstract interface.
For example:
protocol HTTPClient { ... }
class URLSessionHTTPClient: HTTPClient { ... }
The URLSessionHTTPClient
conforms to the HTTPClient
protocol.
3. Solid line, filled head = “depends on” / “has a” (strong dependency)
A solid line with a filled head denotes a strong dependency.
When a type instance depends on another type instance to exist, it’s considered a stronger dependency, such as Association, Aggregation, and Composition.
For example:
class RemoteFeedLoader {
private let client: HTTPClient
init(client: HTTPClient) {
self.client = client
}
}
The RemoteFeedLoader
has an HTTPClient
.
You cannot instantiate a RemoteFeedLoader
without an HTTPClient
instance. The code wouldn't even compile. So that's a strong dependency.
The RemoteFeedLoader
depends on an HTTPClient
to exist.
4. Dashed line, filled head = “depends on” (weak dependency)
A dashed line with a filled head denotes a weak dependency.
It’s important to note that a type can depend on and use another but still work without one.
For example:
class RemoteFeedLoader {
func load(with client: HTTPClient) {
client.doSomething()
}
}
The RemoteFeedLoader
has a source code dependency to the HTTPClient
because it references and uses it. But it doesn't require an HTTPClient
instance to exist.
You can create a RemoteFeedLoader
without an HTTPClient
.
That’s considered a weaker dependency, but still a dependency!
The RemoteFeedLoader
uses an HTTPClient
dependency in the load method. But it doesn't have one. It must be provided as a parameter.
Represent Language features in the diagram
Would the line and arrow be the same if this was a weak reference in case of a class-only protocol, for example?
the arrow notation for a weak reference is represented exactly as the strong one, as the weak reference is an implementation (and language) detail.
Diagrams don’t focus on language features such as “weak references,” however there is a commonly used notation that can help you communicate your intent.
If you’d like to represent a weak reference in your diagrams, you can do so by adding a label near the connection line, denoting the weak relationship between the components, as shown in the image below.
UML? Why not using it? Isn’t it enough to represent a system?
UML and the subset visual representation used here are not focused on language features and implementation details such as protocol extensions. With that said, you can still represent the dependencies between the extensions without any special tool. Just treat them as separate types. If you want to represent such language features, you do so by using annotations.
Is there a way to represent an item/object etc that doesn’t get injected and has an internal dependency? Or is modular design only used to show external dependencies?
in a dependency diagram representation, there’s no distinction between injected dependencies (explicit) or not (implicit).
The representation simply denotes if a component/module depends on another component/module. That dependency, in code, may be explicit or implicit.
For example, `FeedViewController → FeedAPI` means the concrete `FeedViewController` type depends on the concrete `FeedAPI` type. In code, the `FeedViewController` can either create/locate a `FeedAPI` internally (implicit), or receive one as a dependency (explicit).
To enable modularity, an abstraction in between concrete types (e.g., protocol or closure) is used . In this example, the dependency diagram may look like this:
`FeedViewController → | Abstraction | ← FeedAPI`
In this new dependency representation, the `FeedViewController` does not depend on the concrete `FeedAPI` anymore. It depends on the abstraction (which may denote the FeedAPI is injected explicitly in the `FeedViewController` by another component, but that’s an implementation detail!).
References: