# Chapter 3 - Managing Dependencies

[Practical Object-Oriented Design in Ruby](https://www.poodr.com) is a book by [Sandi Metz](https://twitter.com/sandimetz)

# Introduction

The goal of this chapter is to learn how to **manage dependencies so that each class has the fewest possible**. A class should know just enough to do its job and not one thing more. The outline of this article will be first *understanding dependencies* followed by *techniques to write loosely coupled code*.

## 1. Understanding Dependencies

### (a) What is a dependency? 

An object depends on another object if, when one object changes, the other might be forced to change in turn.

### (b) How to recognize dependency?

An object has a dependency when it knows:
- The name of another class
- The name of a message that it intends to send to someone other than self
- The arguments that a message requires
- The order of those arguments
- The object knows another who knows another who knows something

Using the same case study in [chapter 2](https://marcushwz.hashnode.dev/chapter-2-designing-classes-with-a-single-responsibility-ckt3zadbr01h3res1ae669lkj) and let's start the code with
```ruby
class Gear
	attr_reader :chainring, :cog, :rim, :tire
	def initialize(chainring, cog, rim, tire)
		@chainring = chainring
		@cog = cog
		@rim = rim
		@tire = tire
	end
	
	def gear_inches
		ratio * Wheel.new(rim, tire).diameter
	end
	
	def ratio
		chainring / cog.to_f
	end
end

class Wheel
	attr_reader :rim, :tire
	def intiialize(rim, tire)
		@rim = rim
		@tire = tire
	end
	
	def diameter
		rim + (tire * 2)
	end
end
```

From the code above, it is obvious the the `Gear` class has quite some dependencies. It knows about the `Wheel` class, the `diameter` method which is sent by the `Wheel` and its `initialize` method has 4 arguments for which their order matters.

## 2. Writing Loosely Coupled Code

This section will focus on the few techniques we could use to refactor the `Gear` class so that it knows just enough, not one thing more.

### (a) Inject Dependencies

Dependency Injection is a common technique for making your code more flexible and testable by removing strict dependencies upon other classes, modules, and other types of dependencies in your code. In this example, we will remove the strict dependency of the `Wheel` class from `Gear`.

```ruby
class Gear
	attr_reader :chainring, :cog, :wheel
	def initialize(chainring, cog, wheel)
		@chainring = chainring
		@cog = cog
		@wheel = wheel
	end
	
	def gear_inches
		ratio * wheel.diameter
	end
	
	def ratio
		chainring / cog.to_f
	end
end
```

Now, `Gear` no longer knows about the `Wheel`, it just expects itself to be initialized with an object that can respond to diameter. This object can be anything, it doesn't know or care if it's an instance of the `Wheel` class. This means that `Gear` can now collaborate with any object that implements `diameter`. 

However, we shouldn't abuse this technique. Using dependency injection to shape code very much relies on our ability to recognize that **the responsibility for knowing the name of a class and the responsibility for knowing the name of a message to send to that class may belong in different objects**. For example, just because `Gear` needs to send `diameter` somewhere does not mean that `Gear` should know about `Wheel`. The questions of *"where the responsibility for knowing about the actual `Wheel` class lies"* will be examined in a different chapter.


### (b) Isolate Dependencies

It is best if you can remove unnecessary dependencies from your class, but if you can't because of severe constraints, your goals should switch to improving the overall situation by leaving the code better than you found it. One way to do this is to **isolate** them. Dependencies are foreign invaders that represent vulnerabilities, and they should be concise, explicit, and isolated. Below are some related techniques

#### (i) Isolate Instance Creation

If you can't use dependency injection, you should isolate the creation of a new `Wheel` inside the `Gear` class. This will help to expose the dependency and also reduce its reach into your class. For example

```ruby
### METHOD 1 ###
# New Wheel will be created each time a new Gear is created
class Gear
	attr_reader :chainring, :cog, :wheel
	def initialize(chainring, cog, rim, tire)
		@chainring = chainring
		@cog = cog
        @wheel = Wheel.new(rim, tire)
	end
	
	def gear_inches
		ratio * wheel.diameter
	end
end

### METHOD 2 ###
# New Wheel is created when gear_inches invokes the new wheel method
class Gear
	attr_reader :chainring, :cog, :rim, :tire
	def initialize(chainring, cog, rim, tire)
		@chainring = chainring
		@cog = cog
        @rim = rim
        @tire =tire
	end
	
	def gear_inches
		ratio * wheel.diameter
	end

    def wheel
        @wheel ||= Wheel.new(rim, tire)
    end
end
```

#### (ii) Isolate Vulnerable External Messages

External messages are messages that are "sent to someone other than self." For example, the `gear_inches` method below sends `ratio` and `wheel` to self, but sends diameter to `wheel`

```ruby
def gear_inches
    ratio * wheel.diameter
end
```

And we can easily isolate them by

```ruby
def gear_inches
    ratio * diameter
end

def diameter
    wheel.diameter
end
```

In the original code, `gear_inches` knew that `Wheel` had a diameter. This knowledge is a dangerous dependency that couples `gear_inches` to an external object and one of its methods. After this change, `gear_inches` is more abstract. `Gear` now isolates `wheel.diameter` in a separate method and `gear_inches` can depend on a message sent to self.

Not every external method is a candidate for this kind of isolation, but, it is still worth examining your code, looking for and wrapping the most vulnerable dependencies.


### (c) Remove Argument-Order Dependencies

With the following example, the `Gear` class must be initialized with three arguments, `chainring, cog, and wheel` in the correct order. This is the dependency we are trying to fix so that the order of the arguments do not matter when we pass them to the `initialize` method.
```ruby
class Gear
	attr_reader :chainring, :cog, :wheel
	def initialize(chainring, cog, wheel)
		@chainring = chainring
		@cog = cog
        @wheel = wheel
	end
	
	def gear_inches
		ratio * wheel.diameter
	end
end
```

#### (i) Use Hashes for Initialization Arguments
The simple way to avoid depending on fixed-order arguments is by changing the `initialize` method to take a hash of options instead of a fixed list of parameters.

```ruby
def initialize(args)
  @chainring = args[:chainring]
  @cog = args[:cog]
  @wheel = args[:wheel]
end
```

This technique has several advantages like:
- It removes the dependency on argument order
- It adds verbosity in a good way
- The *key* names in the hash furnish explicit documentation about the arguments

However, don't go extreme and apply this technique in all your methods. Evaluate the situation, sometimes, if a method is very simple, it might be cheaper to merely pass the arguments and accept the dependency on order. Between these two extremes lies a common case, where a method might require a few very stable arguments and optionally permits a number of less stable ones. In this case, the most cost-effective strategy may be to use both techniques; that is, to take a few fixed-order arguments, followed by an options hash.

#### (ii) Explicitly Define Defaults

There are many techniques for adding defaults. In the section, we will show a few of them and discuss the pros and cons of each.

**TECHNIQUE 1**

This technique relies on the fact that the `[]` method of `Hash` returns `nil` for missing keys

```ruby
def initialize(args)
    @chainring = args[:chainring] || 40
    @cog = args[:cog] || 18
end
```

One downside of using this technique is that if the `args` contains a `:boolean_thing` key that defaults to true, the use of `||` in this way makes it impossible for the caller to ever explicitly set the final variable to `false` or `nil`.

```ruby
# This will always return true even if user 
# manually set boolean_thing to false or nil
@bool = args[:boolean_thing] || true
```

**TECHNIQUE 2**

If you need to distinguish between `false` and `nil`, it's better to use the `fetch` method to set defaults

```ruby
def initialize(args)
    @chainring = args.fetch(:chainring, 40)
    @cog = args.fetch(:chainring, 18)
end
```

**TECHNIQUE 3**

Completely removing the defaults from `initialize` and `isolate` them inside of a separate wrapping method. This technique is useful when the defaults are more complicated.

```ruby
def initialize(args)
    args = defaults.merge(args)
    @chainring = args[:chainring]
    @cog = args[:cog]
end

def defaults
    {chainring: 40, cog: 18}
end
```

#### (iii) Isolate Multiparameter Initialization

What happens if you have no control like you have to depend on an *external* method that requires fixed-order arguments where you do not own and thus cannot change the method itself. As dire as this situation appears, you are not doomed to accept the dependencies. You can **DRY** out the creation of that particular instance by creating a single method to wrap the external interface. The classes in your application should depend on code that you own; use a wrapping method to isolate external dependencies.

```ruby
# Some external interface
module SomeFramework
  class Gear
    attr_reader :chainring, :cog, :wheel

    def initialize(chainring, cog, wheel)
      @chainring = chainring
      @cog = cog
      @wheel = wheel
    end
  end
end

# wrap the interface to protect yourself form changes
module GearWrapper
  def self.gear(args)
    SomeFramework::Gear.new(
      args[:chainring],
      args[:cog],
      args[:wheel]
    )
  end
end
```

There are two things to note about `GearWrapper`. First, it is a Ruby module instead of a class and its responsibility is to create new instances of `SomeFramework::Gear`. Using a module here lets you define a separate and distinct object to which you can send the gear message while simultaneously conveying the idea that you don't expect to have instances of `GearWrapper`. This module is only meant to directly respond to the `gear` message but not to be included in another class. The other interesting about `GearWrapper` is that it is a `factories` where its sole purpose is to create instances of some other class.

### (d) Managing Dependency Direction
#### (i) Reversing Dependencies

What happens when we reversed the dependencies, for example, `Wheel` could instead depend on `Gear`. 

```ruby
class Gear
  attr_reader :chainring, :cog

  def initialize(chainring, cog)
    @chainring = chainring
    @cog = cog
  end

  def gear_inches(diameter)
    ratio * diameter
  end

  def ratio
    chainring / cog.to_f
  end
end

class Wheel
  attr_reader :rim, :tire, :gear

  def initialize(rim, tire, chainring, cog)
    @rim = rim
    @tire = tire
    @gear = Gear.new(chainring, cog)
  end

  def diameter
    rim + (tire * 2)
  end

  def gear_inches
    gear.gear_inches(diameter)
  end
end
```

From the example above, the reversal of dependencies does no apparent harm. Calculating `gear_inches` still requires collaboration between `Gear` and `Wheel` and the result of the calculation is unaffected by the reversal.

Indeed, in an application that never changed, your choice would not matter. However, your application *will* change and the choices you make about the direction of dependencies have far-reaching consequences that manifest themselves for the life of your application. If you get this right, your application will be pleasant to work on and easy to maintain. If you get it wrong then the dependencies will gradually take over and the application will become harder and harder to change.

#### (ii) Choosing Dependency Direction

**Always depend on things that change less often than you do**. Keep in mind that
- Some classes are more likely than others to have changes in requirements
- Concrete classes are more likely to change than abstract classes
- Changing a class that has many dependents will result in widespread consequences.

**Understanding Likelihood of Change**

Every class used in your application can be ranked along a scale of how likely they are going to change. You can then use this ranking to consider when choosing the direction of dependencies. For example, Ruby base classes are less likely to change than your own code. Mature framework classes are also less likely to change if compare with a framework that is undergoing rapid development.

**Recognizing Concretions and Abstractions**

Depending on abstraction is always safer than depending on concretion because by its very nature, the abstraction is more stable. One example we can use here is that initially `Gear` depended on `Wheel`, `Wheel.new` and `Wheel.new(rim, tire)`, it depended on extremely concrete code. After the code was altered to inject a `Wheel` into `Gear`, `Gear` begins to depend on something far more abstract, that is, anything that could respond to the `diameter` message.

**Avoiding Dependent-Laden Classes**

When a class has too many dependencies there is a very high chance that the class might **never** change because if it changed, it will cause changes to ripple through the application. Because of this, your application may be permanently handicapped by your reluctance to pay the price required to make a change to this class.

**Finding the Dependencies That Matter**

Design decisions usually occur at the place where *likelihood of change intersects with the number of dependents*. If all the classes from a well-designed application were to be evaluated using the grid below, they will cluster in Zones A, B and C.
 
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1630681982970/OrpI3BIY0.png)

When writing code, try not to have any class that will fall under Zone D. Because classes in Zone D are those that make an application painful to change.

# Conclusion

Dependency management is core to creating a future-proof application. Injecting dependencies and isolating dependencies are techniques that we could use to create objects that are loosely coupled and more adaptable to unexpected changes. While depending on abstractions decreases the likelihood of facing these changes. The key to managing dependencies is to control their direction. Last but not least, **always depend on things that change less often than you do**

