Mengenal Design Pattern part 1

  1. Teori

Design pattern merupakan salah satu topik yang penting jika Anda ingin berkarir sebagai Android Developer. Lalu sebenarnya apa itu Design Pattern? Design Pattern adalah sebuah solusi umum yang telah teruji dan bisa digunakan kembali untuk menyelesaikan suatu masalah yang sering terjadi pada perancangan perangkat lunak. Jadi, penggunaan Design Pattern ditentukan oleh permasalahan apa yang akan diatasi. 

Di dalam pengembangan aplikasi Android dan pengembangan aplikasi pada umumnya, beberapa permasalahan yang bersifat berulang dapat diatasi dengan Design Patterns. Yaitu dengan meningkatkan kualitas rancangan aplikasi pada aspek-aspek penggunaan ulang (reusability), perluasan fungsi (extensibility), skalabilitas (scalability) dan pemeliharaan (maintainability). Tentunya semua ini juga akan berdampak pada gaya kita menulis kode (code style) dengan mematuhi konvensi yang disepakati dalam tim.

Terdapat tiga jenis Design Pattern yang digunakan dalam Software Development, yaitu:

  1. Creational Pattern : Berhubungan dengan penciptaan suatu object (instantiation).
  2. Structural Pattern : Berhubungan dengan komposisi suatu object (relationships).
  3. Behavioral Pattern : Berhubungan dengan komunikasi antar object (communications).
  1. Creational Pattern

Creational Pattern digunakan supaya kita dapat membuat instance suatu object tanpa perlu mengetahui bagaimana step-by-step object itu dibuat. Sehingga Anda dapat membuat sebuah object dengan mudah dan cepat. Jenis ini mengedepankan fleksibilitas dan penggunaan kembali object yang telah dibuat. Berikut adalah beberapa creational pattern yang bisa digunakan di Android:

a. Singelon Patterns

Pattern ini digunakan ketika Anda ingin memastikan suatu object hanya memiliki satu instance saja dan satu cara akses ke instance tersebut. Cara ini akan sangat bermanfaat ketika sebuah object dibutuhkan di banyak Class. Terlebih terhadap object-object yang memang akan digunakan terus menerus seperti object HttpClient atau Database. 

Mengapa? Alasannya, jika kita tidak menggunakan Singleton Pattern akibatnya adalah object baru akan selalu dibuat ketika kita memanggil sebuah object. Jika banyak object yang terbuat, maka ini bisa menyebabkan memory leak alias memori bocor. Tentunya ini akan membuat Garbage Collector bekerja lebih keras untuk merilis alokasi memori untuk dapat digunakan kembali, proses ini tentunya akan mempengaruhi performa aplikasi. 

Ingatlah bahwa proses pembuatan object adalah suatu proses yang mahal. Maka, pastikan object yang Anda buat cukup satu saja tetapi bisa diakses secara global di mana saja. Paham sampai di sini? Jika belum, silakan ulangi lagi membaca paragraf ini.

Ada dua mekanisme Singleton secara default di Kotlin, yaitu:

1). Menggunakan object seperti ini:

object CarFactory
{
   val cars = mutableListOf<Car>()
   fun makeCar(horsepowers: Int): Car
   {
       val car = Car(horsepowers)
       cars.add(car)
       return car
   }
}

2). Menggunakan companion object seperti untuk inisialisasi database di bawah ini:

companion object
{
   @Volatile
   private var INSTANCE: TourismDatabase? = null
 
   fun getInstance(context: Context): TourismDatabase =
                            INSTANCE ?: synchronized(this)
   {
       val instance = Room.databaseBuilder
       (
           context.applicationContext,
           TourismDatabase::class.java,
           “Tourism.db”
       ).build()
       INSTANCE = instance
       instance
   }
}

Di sini sistem akan mengecek apakah variabel INSTANCE sudah pernah dibuat atau belum. Jika sudah maka ia akan langsung mengembalikan nilai tersebut. Dan jika tidak, maka instance baru akan dibuat.

Anotasi @Volatile digunakan supaya nilai pada variabel INSTANCE tidak dimasukkan ke dalam cache, sehingga nilainya akan selalu up-to-date dan sama di semua thread. Itu artinya ketika salah satu thread mengubah nilai tersebut, maka semua thread akan langsung mendapatkan nilai yang dimaksud.

Selanjutnya di sini kita juga menggunakan synchronized(this). Mengapa? Hal ini karena multiple thread bisa jadi mengakses instance dalam waktu yang bersamaan, alhasil 2 (dua) instance akan terbentuk. Untuk mengatasinya, kita perlu membungkus kode untuk mendapatkan instance dengan synchronized(this). Dengan begitu, hanya satu thread yang diperbolehkan untuk mengakses instance tersebut dalam satu waktu. Sehingga kita bisa memastikan hanya satu instance saja yang terbuat. Keren kan?

b. Builder Pattern

Pattern ini digunakan ketika kita ingin membuat sebuah object secara bertahap namun hanya fokus pada pada bentuk object yang diinginkan. Sehingga kita tidak perlu mendefinisikan semua variabel yang ada di class tersebut. Cukup yang akan diubah saja. Sebagai contohnya, ketika Anda ingin membeli handphone, biasanya Anda cukup menyebutkan fitur yang penting saja, seperti processor dan RAM. Anda tak perlu bilang handphone yang ada screen, speaker, dan baterainya kan? Walaupun saat membelinya kita akan mendapatkan komponen-komponen tersebut.

Hal ini sama dengan ketika sebuah Class memiliki banyak sekali properties. Ada yang penting dan ada yang tidak penting. Maka untuk membuat object dengan class seperti ini Anda bisa menggunakan Builder Pattern.

Untuk lebih mudahnya lihatlah kode berikut:

class Handphone private constructor(builder: Builder)
{
   private val processor: String = builder.processor
   private val battery: String = builder.battery
   private val screenSize: String = builder.screenSize
   // Builder class
   class Builder(processor: String)
   {
       var processor: String = processor // wajib ada
       var battery: String = “4000MAH”
       var screenSize: String = “6inch”
 
       fun setBattery(battery: String): Builder
       {
           this.battery = battery
           return this
       }
       fun setScreenSize(screenSize: String): Builder
       {
           this.screenSize = screenSize
           return this
       }
       fun create(): Handphone
       {
           return Handphone(this)
       }
   }
}

Sekarang untuk membuat Object Handphone, Anda tak perlu mendefinisikan satu per satu property-nya. Cukup yang kita butuhkan dan ingin kita ganti saja. Misalnya Anda menginginkan Handphone dengan spesifikasi processor “Octa-core” dan baterai “5000MAH”. Maka Anda dapat membuatnya seperti ini:

val myPhone = Handphone.Builder(“Octa-core”) // wajib ada
.setBattery(“5000MAH”)                      // optional
.create()

Dapat dilihat di sini Anda tak perlu mendefinisikan screenSize, sehingga object yang dibuat akan menggunakan nilai screenSize default, yaitu 6 inch. Tentunya jika variabel yang ada pada class tersebut ada banyak akan sangat membantu Anda bukan?

Nah, tanpa sadar sebenarnya Anda sudah sering menggunakan pattern ini, contohnya yaitu saat membuat AlertDialog seperti ini:

AlertDialog.Builder(this)
    .setTitle(“Judul dialog”)
    .setMessage(“Pesan dalam dialog”)
    .show()

c. Factory Method Pattern

Sama seperti Builder Pattern, pattern ini juga digunakan untuk menyederhanakan proses dalam membuat sebuah object yang rumit. Bedanya yaitu Anda bisa langsung membuat Object tanpa harus mendefinisikan satu per satu spesifikasi yang diinginkan. Sebagai gantinya Anda akan membuat interface dan membuat kelas turunan untuk implementasi spesifikasi yang berbeda.  Untuk lebih mudahnya lihatlah kode berikut:

interface Handphone
{
   var processor: String
   var battery: String
   var screenSize: String
}
class HandphoneNexus5 : Handphone
{
   override var processor = “Snapdragon”
   override var battery = “2300 mAh”
   override var screenSize = “4.95 inch”
}
class HandphoneNexus9 : Handphone
{
   override var processor = “Nvidia Tegra”
   override var battery = “6700 mAh”
   override var screenSize = “8.9 inch”
}
enum class Type 
{
   NEXUS5, NEXUS9
}
class HandPhoneFactory 
{
   companion object 
   {
       fun createHandphone(type: Type): Handphone
       {
           return when (type)
           {
               Type.NEXUS5 -> HandphoneNexus5()
               Type.NEXUS9 -> HandphoneNexus9()
           }
       }
   }
}
 
fun main()
{
   val myPhone = HandPhoneFactory.createHandphone(Type.NEXUS5)
}

Dengan menggunakan factory method pattern, Anda bisa langsung mendapatkan object Handphone yang diinginkan dengan lebih mudah karena tidak perlu menyebutkan spesifikasinya satu per satu. Sebagai contoh di atas, Anda tidak perlu mendefinisikan processor, battery, dan screensize untuk mendapatkan Handphone dengan tipe Nexus5. Keren bukan?

Contoh factory pattern yang mungkin pernah Anda ketahui yaitu pada saat inisialisasi ViewModel dengan menggunakan ViewModelFactory seperti berikut:

class HomeViewModelFactory (private val tourismRepository: TourismRepository) : ViewModelProvider.Factory
{
 
    override fun <T : ViewModel?> create(modelClass: Class<T>): T
    {
        if (modelClass.isAssignableFrom(HomeViewModel::class.java))
        {
            return HomeViewModel(tourismRepository) as T
        }
        throw IllegalArgumentException(“Unknown ViewModel class”)
    }
}

next silahkan cek di part 2

Leave a Comment