![SOLID: Prinsip Dependency Injection (Inversion of Control)](https://images.ctfassets.net/7wjlcr9d5ii8/41xpuKWeTsReW3P3HTWplp/f836e1452eb50ffe8da9d82e5e5e1e87/nurse-giving-patient-injection.jpg?w=550&h=508&fl=progressive&q=100&fm=jpg&fit=thumb&f=faces)
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).
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());
}
}
Contoh penggunaan
public static void main(String[] args) throws Exception{
BookService bookService = new BookService();
bookService.getBookTitles();
AuthorService authorService = new AuthorService();
authorService.getBookTitlesByAuthorName("ferry");
}
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 BookMySqlGateway. Masih mending contoh di atas hanya digunakan di dua tempat, BookService & AuthorService, kalau lebih dari itu kan repot juga.
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<Book> allBookTitles = bookGateway.getAllBooks();
return allBookTitles.stream()
.map(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());
}
}
Contoh penggunaan
public static void main(String[] args) throws Exception{
BookGateway bookGateway = new BookMySqlGateway();
BookService bookService = new BookService(bookGateway);
bookService.getBookTitles();
AuthorService authorService = new AuthorService(bookGateway);
authorService.getBookTitlesByAuthorName("ferry");
}
Dengan begitu, objek BookGateway dibuat dan dikonfigurasi di luar BookService dan AuthorService, yang kemudian akan di-inject lewat constructor. Ini bisa saja dikonfigurasi di dalam controller saat menggunakan aplikasi web, atau lewat container saat menggunakan framework seperti Spring, tapi pada contoh di atas untuk mempermudah pemahaman kita buat di dalam main method saja karena pada command line app main method adalah struktur method tertinggi (high level). Disini setiap ada pergantian implementasi objek BookGateway, misalnya dari BookMySqlGateway menjadi BookNoSqlGateway, maka itu bisa dilakukan sekali pada saat pembuatan objek tersebut. AuthorService dan BookService tidak lagi bergantung pada implementasi BookMySqlGateway secara langsung, melainkan pada interface yang diinjek dari luar, sehingga membuat objek jadi lebih fleksibel.
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 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 menerapkan 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 pada Spring dilakukan dengan membuat konfigurasi class khusus. Contoh Service Locator Pattern menggunakan Spring sebagai berikut:
@Configuration
public class ServiceContainer{
@Bean
public AuthorService authorService(BookGateway bookGateway){
return new AuthorService(bookGateway);
}
@Bean
public BookService bookService(BookGateway bookGateway){
return new BookService(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.
Inversion of Control adalah proses pengendalian flow objek secara terbalik, jadi dependency objek tidak dikendalikan dari dalam objek, melainkan dikendalikan dari luar. Secara umum terdapat 2 implementasi Inversion of Control, yaitu Dependency Injection dan Service Locator Pattern. 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). Dependency ditentukan saat objek tersebut dibuat. Selain constructor injection dan setter injection, juga ada field injection 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 canggih 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 jadi tidak tersebar di banyak tempat. Dengan begitu setiap ada perubahan dependency yang berubah hanya pada class konfigurasinya saja.
Prinsip SOLID lainnya: