![Prinsip Dependency Injection (Inversion of Control)](http://images.ctfassets.net/7wjlcr9d5ii8/41xpuKWeTsReW3P3HTWplp/f836e1452eb50ffe8da9d82e5e5e1e87/nurse-giving-patient-injection.jpg?w=550&h=508&fl=progressive&q=75&fm=jpg)
Sebenarnya contoh penggunaannya udah pernah gw bikin pada post tentang Single Responsibility dan Open-Close Principle, nah sekarang penjelasannya. Prinsip yang satu ini mungkin udah pada familiar. Terutama bagi yang menggunakan Spring framework pasti sudah tidak asing lagi. Prinsip ini merupakan implementasi dari Inversion of Control. Dengan Dependency Injection, sebuah objek tidak bergantung pada implementasi objek, tapi bergantung pada abstraksi. Dependency object tersebut di-inject dari luar (high level object, tempat object-object tersebut dibuat dan dikonfigurasi) lewat constructor atau setter pada object lain yang bergantung pada object tersebut (umumnya lewat constructor agar objeknya immutable), bukan dari dalam objek (low level object).
Pelanggaran Dependency Injection
Misalkan ada sebuah objek yang bertugas untuk mendapatkan data buku. Untuk lebih jelasnya seperti contoh berikut:
Interface BookGateway
public interface BookGateway{
List<Book> getAllBooks();
List<Book> getBooksByAuthorName(String name);
}
Class BookMySqlGateway
public class BookMySqlGateway implements BookGateway{
@Override
public List<Book> getAllBooks(){
return Arrays.asList(new Book("math"), new Book("english"));
}
@Override
public List<Book> getBooksByAuthorName(String name){
return Arrays.asList(new Book("math"));
}
}
Objek di atas nantinya akan dipakai oleh dua objek yang bertugas untuk memproses data buku-buku tersebut sesuai yang diinginkan. Contohnya seperti ini:
Class BookService
public class BookService{
private final BookGateway bookGateway = new BookMySqlGateway();
public List<String> getBookTitles(){
List<Book> allBookTitles = bookGateway.getAllBooks();
return allBookTitles.stream()
.map(Book::getTitle)
.collect(Collectors.toList());
}
}
Class AuthorService
public class AuthorService{
private final BookGateway bookGateway = new BookMySqlGateway();
public List<String> getBookTitlesByAuthorName(String authorName){
List<Book> allBookTitles = bookGateway.getBooksByAuthorName(authorName);
return allBookTitles.stream()
.map(Book::getTitle)
.collect(Collectors.toList());
}
}
Permasalahan pada code di atas adalah code-nya jadi ga loosely-coupled karena BookGateway dibuat dari dalam objek AuthorService dan BookService. Jika implementasi objek dari BookMySqlGateway diganti dengan objek baru, misalnya BookGatewayNoSql, maka user harus mengganti code tersebut di seluruh tempat yang menggunakan type BookMySqlGateway. Masih mending contoh di atas hanya digunakan di dua tempat, BookService & AuthorService, kalau lebih dari itu kan repot juga.
Solusi
Solusinya adalah dengan melakukan dependency injection. Objek BookGateway bisa di-inject lewat constructor maupun lewat setter. Gw sendiri prefer melakukan inject lewat constructor agar objeknya final dan immutable. Berikut code-nya menggunakan Dependency Injection:
Class BookService
public class BookService{
private final BookGateway bookGateway;
public BookService(BookGateway bookGateway){
this.bookGateway = bookGateway;
}
public List<String> getBookTitles(){
List<BookMySqlGateway.Book> allBookTitles = bookGateway.getAllBooks();
return allBookTitles.stream()
.map(BookMySqlGateway.Book::getTitle)
.collect(Collectors.toList());
}
}
Class AuthorService
public class AuthorService{
private final BookGateway bookGateway;
public AuthorService(BookGateway bookGateway){
this.bookGateway = bookGateway;
}
public List<String> getBookTitlesByAuthorName(String authorName){
List<Book> allBookTitles = bookGateway.getBooksByAuthorName(authorName);
return allBookTitles.stream()
.map(Book::getTitle)
.collect(Collectors.toList());
}
}
Dengan begitu, objek BookGateway dibuat dan dikonfigurasi di luar BookService dan AuthorService, yang kemudian akan di-inject lewat constructor. Setiap ada pergantian implementasi objek BookGateway, misalnya dari BookMySqlGateway menjadi BookNoSqlGateway, maka itu dilakukan saat pembuatan objek tersebut. AuthorService dan BookService tidak lagi bergantung pada implementasi BookGateway secara langsung, melainkan pada interface, yang membuat objek jadi lebih fleksibel.
Service Locator Pattern
Service Locator Pattern merupakan the next level of Dependency Injection yang juga mengimplementasi Inversion of Control. Service Locator Pattern adalah proses dimana terdapat sebuah container yang bertugas untuk handle objek yang ingin dibuat dan di-inject secara terpusat. Jadi proses konfigurasi objek hanya pada satu tempat saja yang disebut IoC Container. Saat menggunakan framework biasanya pembuatan objek di-handle dengan Service Locator Pattern secara otomatis oleh framework, jadi tidak perlu mengimplementasi Service Locator Pattern dan bikin IoC container sendiri. Contohnya dengan menggunakan annotasi (@Autowired pada Spring atau @Inject pada framework lain) pada constructor (sejak Spring 4.3 @Autowired pada constructor tidak mandatory jika constructor hanya 1) maupun lewat setter method. Walaupun pada Spring juga bisa menggunakan field injection, tapi IntelliJ dan Spring itu sendiri lebih menyarankan menggunakan constructor injection karena field injection membuat objek jadi mutable, mengharuskan kita menganalisa circular dependency secara manual, dan sangat framework dependent, tidak bisa diimplementasi tanpa framework. Apalagi jika mengimplementasi Clean Code Architecture, dimana business class sebaiknya terbebas dari framework. Jika menggunakan multi-layer module, seperti saat menerapkan Clean Code Architecture, biasanya Inversion of Control dilakukan dengan membuat Service Locator Pattern sendiri lewat konfigurasi class khusus sebagai IoC container. Contoh Service Locator Pattern menggunakan Spring sebagai berikut:
@Configuration
public class ServiceContainer{
@Bean
public AuthorService authorService(BookGateway bookGateway){
return new AuthorService(bookGateway);
}
@Bean
public BookGateway bookGateway(){
return new BookMySqlGateway();
}
}
Dengan demikian proses pembuatan dan konfigurasi objek jadi terpusat di satu tempat. Setiap perubahan dependency hanya dilakukan lewat Class ServiceContainer saja. Misalnya ketika ingin mengganti BookMySqlGateway menjadi BookNoSqlGateway, cukup dilakukan di dalam class ini.
Kesimpulan
Inversion of Control adalah proses pengendalian flow objek secara terbalik, jadi dependency objek tidak dikendalikan dari dalam objek, melainkan dikendalikan dari luar. Dependency Injection adalah prinsip yang mengimplementasi Inversion of Control dengan cara melakukan injeksi dependency objek pada suatu objek, baik lewat constructor ataupun lewat setter method (disarankan lewat constructor). Selain constructor injection dan setter injection, juga ada field injection yang sempat populer saat menggunakan framework. Namun belakangan ini penggunaan field injection tidak lagi disarankan bahkan oleh framework itu sendiri karena dianggap code smell. Service Locator Pattern adalah bentuk advance dari Dependency Injection yang juga mengimplementasi Inversion of Control, dimana kita mengendalikan objek dari satu tempat saja, yaitu lewat IoC container. Proses pembuatan dan konfigurasi objek tidak tersebar di banyak tempat. Dengan begitu setiap ada perubahan dependency yang berubah hanya pada containernya saja.
Prinsip SOLID lainnya: