# Dependency Inversion Principle (DIP)

Ide utama dari prinsip ini adalah mengurangi masalah saling ketergantungan dari module-module pada software atau aplikasi. Karena ketergantungan antar module, akan menyulitkan dalam memaintain module-module tersebut pada fase selanjutnya. Sebagai contoh yaitu ketika ada perubahan pada low level module, maka high level module yang terkait dengan module tersebut juga harus mengalami perubahan. Akibatnya jika module-module yang terkait pada module yang berubah tersebut banyak jumlahnya, dipastikan menambah beban *timeline development* untuk merivisi module-module tersebut lebih panjang. Hal tersebut akan sangat-sangat berpengaruh besar pada biaya development dan bisnis. Selain itu juga akan memperbesar kemungkinan kerusakan pada module-module yang tidak seharusnya ikut diubah.

> Tulisan ini adalah bagian dari [SOLID Principles](https://verri.andriawan.web.id/series/solid-principles) Series, sehingga untuk bisa membaca secara menyeluruh dan berkesinambungan, alangkah lebih baik bisa membaca secara urut dari link series [berikut](https://verri.andriawan.web.id/series/solid-principles).

Berdasarkan Wikipedia, dua point utama dari prinsip ini:

* High level modules should not import anything from low-level modules. Both should depend on abstractions (module-module High-level)
    
* Abstraction should not depend on details. Details (concrete implementations) should depend on abstractions.
    

Beberapa mungkin bingung, seperti apa itu high-level module, apa itu low-level module dan juga yang dimaksud abstraction. Yang pertama perlu diingat bahwa module itu bisa berupa class, bisa kumpulan class untuk menyelesaikan kasus penggunaan tertentu (usecase). Berikut sedikit penjelasan mengenai istilah-istilah tersebut.

* High-Level module merupakan module teratas/terdepan yang memerlukan module/class yang lebih detail untuk menyelesaikan suatu masalah atau logic. Cirinya, module ini akan menginisialisasi module lain di dalamnya.
    
* Low-Level module merupakan module yang lebih detail untuk menyelesaikan suatu masalah atau logic. Cirinya, module ini tidak menginisialisasi module tertentu di dalamnya.
    
* Abstraction, proses men-generalkan sebuah logic yang kompleks atau menyembunyikan detail-detail yang tidak diperlukan untuk diketahui antar module. Biasanya menggunakan interface atau abstract class.
    

Untuk lebih memperjelas, mari kita lihat dari contoh code berikut.

**Account Class File**

```typescript
class EmailAccount {
	public emailBucket: string[] = []
	public notifyType: string
	
	constructor() {
		this.sendType = 'email'
	}
	
	addEmail(email: string) {
		this.emailBucket.push(email)
	}
	
	validate(email: string) {
		return email
	}
}
```

**Notification Class File**

```typescript
class Notifier {
  private account: EmailAccount
  
  constructor(email: string) {
     this.account = new EmailAccount
     this.account.addEmail(email)
  }
  
  send() {
	  const data = this.send.emailBucket
	  const type = this.send.notifyType
	  
	  this.sendTo(type, data)
  }
	 
	 sendTo(type: string, data: string) {
		 console.log({type, data})
	 }
}
```

Code di atas memperlihatkan bahwa class `Notifier` adalah `high-level module` karena class atau module ini membutuhkan class yang lebih detail (`EmailAccount`) untuk menyelesaikan tugasnya, terlihat pada constructor class ini. Sedangkan class `EmailAccount` disebut juga sebagai `low-level module` karena class atau module ini lebih explicit yaitu management account email dan utamanya module ini tidak menginisialisasi atau tidak memerlukan module lain di dalamnya.

Seperti terlihat pada class `Notifier` pada section constructor, class ini meng-initialisasi `EmailAccount` yang diset ke property `account`. Lalu pada bagian method `send` terjadi pengambilan property `emailBucket` dan `notifyType` yang merupaka property milik `EmailAccount`. Bagian ini lah yang menjadi masalah. Class atau module ini menyalahi aturan dari Dependency Inversion Principle (DIP). Karena seandainya terjadi perubahan pada property pada module EmailAccount, misalnya property `emailBucket` berubah menjadi `emails` maka programmer harus merevisi juga `high-level module` yaitu class `Notifier` . Seharusnya agar tidak melanggar aturan DIP, sebaiknya jika terjadi perubahan pada `EmailAccount` module , maka pada `Notifier` tidak perlu ada perubahan. Untuk programmer perlu melakukan abstraction yang biasanya menggunakan `interface` atau `abstract class` . Sebagai contoh code di sebelumnya diubah menjadi seperti berikut.

**Abstraction**

```typescript
type Prop = {
	type: string
	data: string[]
	
}

// MG is short of Management
interface IAccountMG {
	getData(): Prop
}
```

Pertama kita perlu melakukan `abstraction`. Seperti pada contoh snippet code di atas melakukan abstraction dengan memanfaatkan `type` dan `interface` . Di typescript sendiri, kita bisa menggunakan banyak cara untuk `abstraction`, bisa menggunakan `type`, `interface` atau juga menggunakan `abstract class`. Tapi untuk contoh kasus di code ini cukup menggunakan `type` dan `interface` saja.

Seperti yang terlihat pada interface `IAccountMG` terdapat member method `getData` dengan return value berupa `type Prop` . Tujuan dari `type Prop` adalah kontrak agar module apapun yang meng-implement `interface IAccountMG` maka return dari `getData` harus selalu ber-type `Prop` dengan begitu return type akan selalu konsisten.

**Account class**

```typescript
class EmailAccount implements IAccountMG {
	public emailBucket: string[] = []
	public notifyType: string
	
	constructor() {
		this.sendType = 'email'
	}
	
	addEmail(email: string) {
		email = this.validate(email)
		this.emailBucket.push(email)
	}
	
	validate(email: string) {
		return email
	}
	
	getData() {
		return {
			data: this.emailBucket,
			type: this.notifyType
		}
	}

}
```

Selanjutnya class `EmailAccount` meng-implement interface `IAccountMG`. Dengan meng-implement interface `IAccountMG` maka class `EmailAccount` akan dipaksa untuk meng-implementasi method `getData` juga. Karena method ini yang akan di call pada `high-level module` maka dari itu method `getData` wajib ada dan *callable* (bisa dieksekusi)*.*

**Notifier Class**

```typescript
class Notifier {
  private account: IAccountMG
  
  constructor(email: string) {
    this.account = new EmailAccount
    this.account.addEmail(email)
  }
  
  send() {
	const payload: Prop = this.account.getData()
    this.sendTo(payload)
  }
	 
  sendTo(payload: Prop) {
    console.log(payload)
  }
}
```

Lalu pada `contructor` class `Notifier` sama seperti sebelumnya yaitu menginisialisasi class EmailAccount. Tapi yang berbeda kali ini adalah type property `account` pada `Notifier` bukan lagi `EmailAccount` melainkan interface `IAccountMG`. Dengan begini module `Notifier` tidak perlu tahu yang dilakukan `getData` pada module `account`. Yang perlu diketahui oleh module `Notifier` hanya bahwa `getData` bersifat dapat dieksekusi dan memberikan return type Prop. Itulah tujuan dari `abstraction` yaitu menyederhanakan module dengan menyembunyikan detail pada `Low-Level Module` yang tidak perlu diketahui oleh `High-Level module`.

Lalu kenapa menggunakan penamaan method `getData` dari pada `getEmail` misalnya. Kembali ke point ke dua pada gagasan dari DIP, yaitu abstraction tidak boleh berdasarkan detail dari implementator. Implementator-lah yang harus mengikuti abstraction. Maksudnya penamaan dan fungsi member method harus lebih general, seperti misal pada kasus code di sini, jika misalkan akan menambah module lain yang bukan berupa kumpulan email maka `getData` sudah cukup untuk merepresentasikan module implementatornya.

**references:**

* [https://www.tutorialsteacher.com/csharp/dependency-inversion-principle](https://www.tutorialsteacher.com/csharp/dependency-inversion-principle)
    
* [https://salesforcecodex.com/architecture/understanding-dependency-inversion-principle-with-c/](https://salesforcecodex.com/architecture/understanding-dependency-inversion-principle-with-c/)
    
* [https://code-maze.com/dependency-inversion-principle/](https://code-maze.com/dependency-inversion-principle/)
