Dependency Diagrams

abdul ahad
4 min readDec 24, 2023

--

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

Photo by Evaldas Grižas on Unsplash

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:

https://academy.essentialdeveloper.com

--

--

abdul ahad
abdul ahad

Written by abdul ahad

A software developer dreaming to reach the top and also passionate about sports and language learning

No responses yet