Awalnya gw ga tertarik bahas ini karena kayaknya ini topiknya udah terlalu mainstream deh😅. Secara penggunaan pun udah sering juga gw post, terutama pada seri design pattern. Tapi ternyata di luar sana masih banyak orang yang belum teryakinkan dengan manfaat interface dalam bahasa pemrograman. Bahkan ada juga diantaranya yang udah experienced sekian tahun tapi masih ga tau fungsi interface. Mungkin karena mereka sebelumnya terbiasa ngoding pakai bahasa yang tipe datanya dinamis, pas ganti bahasa pemrograman jadi agak kaget. Biar makin banyak yang tercerahkan, gw putuskan untuk membuat tulisan ini😎.
Programming to Interface artinya code kita bergantung pada interface, bukan pada class implementasi. Interface adalah abstrak yang berisi satu set behavior (atau disebut juga “kontrak”) yang wajib diikuti oleh class implementasi. Ada beberapa alasan kuat interface itu jadi best practice.
1. Konsisten Behavior
Saat kita ngoding menggunakan interface dan memiliki beberapa varian algoritma, kita bisa memastikan behavior antar algoritma itu konsisten. Misalkan kita memiliki dua buah class untuk mengelola data siswa, yaitu via Postgres dan Cassandra. Masing-masing class memiliki behavior untuk mendapatkan nama siswa dan menghapus data siswa. Kalau tanpa interface jadinya seperti berikut:
Class StudentPostgresGateway
public class StudentPostgresGateway{
public String getStudentName(int id){
System.out.println("getStudentName in Postgres");
return "yoni";
}
public void deleteStudent(int id){
System.out.println("deleteStudent in Postgres");
}
}
Class StudentCassandraGateway
public class StudentCassandraGateway{
public Optional<String> getStudentNameById(int id){
System.out.println("getStudentNameById in Cassandra");
return Optional.of("yono");
}
public void deleteStudentById(int id){
System.out.println("deleteStudentById in Cassandra");
}
}
Tanpa interface, kita bisa membuat behavior seenaknya. Ga ada kontrak yang harus dipatuhi. Ini akan membuat rumit development karena beda engineer bisa jadi akan bikin behavior yang berbeda pula. Kalau ada engineer baru, pasti bakal kebingungan. Misalkan code tersebut dibuat menggunakan interface, jadinya seperti berikut:
Interface StudentGateway
public interface StudentGateway{
Optional<String> getStudentNameById(int id);
void deleteStudentById(int id);
}
Class StudentPostgresGateway
public class StudentPostgresGateway implements StudentGateway {
@Override
public Optional<String> getStudentNameById(int id){
System.out.println("getStudentNameById in Postgres");
return Optional.of("yoni");
}
@Override
public void deleteStudentById(int id){
System.out.println("deleteStudentById in Postgres");
}
}
Class StudentCassandraGateway
public class StudentCassandraGateway implements StudentGateway {
@Override
public Optional<String> getStudentNameById(int id){
System.out.println("getStudentNameById in Cassandra");
return Optional.of("yono");
}
@Override
public void deleteStudentById(int id){
System.out.println("deleteStudentById in Cassandra");
}
}
Sekarang kita ga bisa bikin behavior sembarangan lagi. Nama method beserta return dan parameternya harus mematuhi aturan interface🫡. Kalau ada behavior dari interface yang ga diimplementasi oleh class implementasi pasti bakal error. Dengan begini behavior antar varian pasti konsisten sehingga mempermudah development.
2. Mewakili Beberapa Tipe
Sebuah interface mewakili beberapa tipe berbeda. Berdasarkan contoh di atas, interface StudentGateway dapat mewakili implementation dari StudentPostgresGateway maupun StudentCassandraGateway. Ketika kita mendeklarasikan variable dengan tipe interface StudentGateway dan implementation dari StudenPosgtresGateway, maka setiap kali behavior dari variable tersebut dieksekusi akan mengikuti behavior dari StudentPostgresGateway.
public static void main(String[] args){
StudentGateway studentGateway = new StudentPostgresGateway();
Optional<String> studentNameById = studentGateway.getStudentNameById(1);
studentNameById.ifPresent(System.out::println);
studentGateway.deleteStudentById(1);
}
Misalkan kita ingin mengganti variannya menggunakan Cassandra, maka cukup ganti implementasinya menggunakan StudentCassandraGateway. Nantinya setiap eksekusi behavior dari variable tersebut akan mengikuti behavior dari StudentCassandraGateway. Kita ga perlu menyesuaikan lagi method, return, atau parameternya karena sesuai manfaat yang pertama, nama method, return, dan parameternya pasti sama sehingga kalau mau migrasi implementasi cukup ganti objek saat deklarasi saja.
public static void main(String[] args){
StudentGateway studentGateway = new StudentCassandraGateway();
Optional<String> studentNameById = studentGateway.getStudentNameById(1);
studentNameById.ifPresent(System.out::println);
studentGateway.deleteStudentById(1);
}
Itulah yang disebut Polymorphism yang merupakan salah satu konsep utama OOP. Proses maintenance sekarang jadi lebih mudah jika mau migrasi varian🥳. Kalau ga menggunakan interface, ga ada jaminannya method dan parameternya akan tetap sama sehingga kita harus memastikan lagi code-nya setelah ganti objek saat deklarasi.
3. Gampang Dibongkar-Pasang
Manfaat selanjutnya adalah code kita jadi decoupling, alias gampang dibongkar-pasang. Misalkan kita memiliki method dengan parameter class StudentPostgresGateway seperti berikut:
public void deleteStudentIfExist(StudentPostgresGateway gateway, int id){
Optional<String> studentNameById = gateway.getStudentNameById(id);
if(studentNameById.isPresent()){
gateway.deleteStudentById(id);
}
}
Method di atas bergantung pada class StudentPostgresGateway dan hanya menerima StudentPostgresGateway saja sebagai argument parameter. Kalau kita memanggil method tersebut menggunakan object StudentCassandraGateway sudah pasti akan error. Misalkan method di atas diganti menggunakan parameter interface StudentGateway seperti berikut:
public void deleteStudentIfExist(StudentGateway gateway, int id){
Optional<String> studentNameById = gateway.getStudentNameById(id);
if(studentNameById.isPresent()){
gateway.deleteStudentById(id);
}
}
Sekarang method di atas bergantung pada interface sehingga bisa menerima object StudentPostgresGateway maupun StudentCassandraGateway. Method-nya jadi lebih fleksibel dan varian object yang dikirim saat pemanggilan method dapat dibongkar-pasang menggunakan varian StudentGateway apapun👏.
Verdict
Itulah 3 manfaat menggunakan interface dalam pemrograman. Interface menawarkan konsistensi, kemudahan maintenance, dan fleksibilitas saat penggunaan. Mungkin ada juga yang berargumen kalau class tersebut emang ga ada variannya maka ga perlu interface. Masalahnya kita ga akan tau di masa depan apakah class tersebut akan ada variannya atau tidak. Bisa jadi pas engineernya resign baru ada requirement baru untuk membuat varian dari class tersebut. Yang repot tentu engineer yang memaintain projek tersebut😢. Untuk itu alangkah baiknya develop pakai interface aja dari awal biar aman. Secara teori, tentu saja menggunakan interface ada pengaruhnya dengan performa. Tapi tenang saja, pengaruhnya ga signifikan, terutama pada bahasa pemrograman compiled seperti Java atau Go yang udah dioptimasi sedemikian rupa oleh compiler sehingga dampaknya sangat-sangat kecil sekali bahkan bisa dibilang ga kerasa. Kalau pada bahasa interpreter gw sendiri belum pernah benchmark, tapi gw pernah baca salah satu review orang yang melakukan benchmark pake PHP katanya emang agak ngaruh. Itupun gw bacanya saat awal-awal PHP 7 rilis, bisa jadi sekarang PHP udah lebih baik. Tapi yang pasti tujuan utama dari interface ini adalah memang untuk kebutuhan maintenance development, bukan untuk meingkatkan performa.