liveBook · Manning

Inversion of control

As explained in Functional Design and Architecture: Examples in Haskell

Inversion of control is a design technique in which client code depends on an interface or abstraction rather than directly controlling or selecting a concrete implementation.

Inversion of control (IoC) makes client code depend on an interface or abstraction instead of directly selecting a concrete implementation. The interface acts as a contract for operations, inputs, outputs, types, and behavior, allowing implementations to change behind it. This reduces coupling while improving robustness, readability, and testability. section 1.3.2

IoC works with modularity: interfaces define the boundaries, implementations occupy replaceable parts, and larger functionality can be organized into packages, services, libraries, or subsystems. The technique supports divide-and-conquer design rather than prescribing one object-oriented pattern. section 1.3.2 section 1.4.2

Sensor observation

A thermometer implements the ISensor abstraction, transforms native readings into Celsius, and supplies its name and data type. An Observer receives the sensor and a Receiver, rather than requiring the observing workflow to know the thermometer's implementation. Another implementation can occupy the same interface without changing the client workflow. section 1.3.2

Listing 1.4 Interfaces and inversion of control
final class Receiver extends IConnection {                   #1
  def send(dataType: String, name: String, data: Float) =
    server.connection.send(dataType, name, data)
}
 
final class Thermometer extends ISensor {                    #2
  val correction = -12.5f                                    #3
  def transform(data: native.core.Temperature) : Float =
    toCelsius(data) + correction
 
  def getName() : String = "T-201A"                          #4
  def getDataType() : String = "temperature"
  def getData() : Float = {
    val data = native.core.thermometer.getData()
    transform(data)
  }
}
 
object Worker {                                              #5
  def observeThermometerData() {
    val t = new Thermometer()
    val r = new Receiver()
    val observer = new Observer(t, r)                        #6
    observer.readAndSendData()
  }
}

Inspect the annotated listing for the interface-based sensor, receiver, and observer composition. section 1.3.2

IoC also enables isolated tests. Injecting mocked, test-only dependencies separates unit code from external systems; impure functions similarly require interfaces, injected dependencies, and mocks when they depend on the outside world. section 15.1

How Functional Design and Architecture: Examples in Haskell covers it

How deep it goes
Explains contracts, replaceable implementations, modularity, a sensor example, and dependency injection for testing.
Examples
Uses a thermometer, sensor interface, receiver, observer, native readings, Celsius conversion, and mocked test dependencies.
What it assumes
Assumes interfaces and APIs, while treating implementation independence and dependency injection as the practical design focus.
What you can do after
Apply it via interfaces and injected dependencies to replace implementations and isolate code from external systems.
How it connects
Connects interfaces, APIs, modularity, design patterns, dependency inversion, mocking, and unit testing.

Also explained in

Compare how 3 books explain inversion of control →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Functional Design and Architecture: Examples in Haskell. The section links point to the parts of the book it draws on.