3. Structural Pattern
Pada pattern ini kita fokus tentang bagaimana menyusun struktur dari suatu kode. Di sini kita akan mempelajari beberapa design pattern yang sangat berguna untuk mempermudah Anda untuk mengubah dan mengembangkan kode Anda sesuai dengan kebutuhan bisnis. Berikut adalah beberapa structural pattern yang biasa digunakan di Android:
a. Facade Pattern
Dengan menggunakan facade pattern, sistem yang kompleks dibuat menjadi lebih simpel sehingga membantu kita untuk membaca kode. Caranya yaitu dengan menyembunyikan detail dengan membuat fungsi baru. Modelnya sama seperti ketika Anda memesan makanan di restaurant. Anda cukup memintanya kepada kasir, kasir yang akan meneruskan ke koki, dan makanan akan kita dapatkan beberapa saat setelahnya. Anda tidak perlu tahu bagaimana kompleksnya makanan itu dibuat.
Salah satu contoh spesifikasi yang sering digunakan di Android yaitu penggunaan Repository.
Di sini Repository berperan sebagai facade. Sehingga ketika ViewModel ingin mengambil data dari datasource, cukup kode yang simpel seperti repository.getData() atau repository.getFavoriteData(). ViewModel tidak perlu tahu bagaimana cara mendapatkan data tersebut. Apakah dari data remote atau dari data local, cukup dengan memanggil fungsi itu saja. Mantap!
Contoh lain yang familiar untuk Facade Pattern adalah implementasi REST Library Retrofit yang mensimplikasi proses request dan parsing response pada REST web service.
interface CommentService { @POST(“v1/comment”) fun createComment(@Body request: Comment): Call<EmptyResult> @GET(“v1/comment”) fun getComment(@Body request: Comment): Call<MutableList<Comment>> } //usage val retrofit = Retrofit.Builder() .baseUrl(“https://www.yourappurl.com”) .addConverterFactory(GsonConverterFactory.create()) .build() val api = retrofit.create<CommentService>(CommentService::class.java) |
b. Adapter Pattern
Adapter Pattern di sini bekerja sebagai jembatan atau penghubung untuk menghubungkan dua interface yang tidak cocok supaya bisa bekerja bersama-sama. Caranya yaitu dengan membuat class untuk convert salah satu interface supaya cocok dengan yang lainnya. Contoh sederhananya di dunia nyata Anda memiliki charger dengan 3 colokan namun Anda hanya memiliki stop kontak 2 lubang. Apa yang perlu Anda lakukan? Ya benar! Anda perlu menggunakan adapter untuk menghubungkannya.
Untuk lebih mudahnya lihatlah contoh kode berikut:
class TourismAdapter(private val listData: ArrayList<TourismEntity>) : RecyclerView.Adapter<TourismAdapter.ListViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder { val inflater = LayoutInflater.from(parent.context) val itemView = inflater.inflate(R.layout.item_list_tourism, parent, false) return ListViewHolder(itemView) } override fun getItemCount() = listData.size override fun onBindViewHolder(holder: ListViewHolder, position: Int) { holder.bind(listData[position]) } } |
Anda tentu sudah sering menjumpai kode ini. Ya, ini adalah penerapan Adapter yang ada di RecyclerView. Seperti yang kita ketahui, RecyclerView adalah komponen user interface yang berfungsi sebagai media menampilkan data dalam jumlah yang banyak. Untuk menjalankan fungsinya RecyclerView tidak bisa melakukannya sendiri dan membutuhkan Object RecyclerView.ViewHolder yang menyediakan mekanisme bagaimana data-data tersebut ditampilkan. Untuk menampilkan data tersebut RecyclerView.Adapter berperan sebagai penghubung di antara keduanya.
c. Observe Pattern
Observer Pattern digunakan untuk mendapatkan update dan event terbaru dari sebuah object dengan melakukan subscribe terlebih dahulu. Sama seperti saat Anda berlangganan sebuah channel di Youtube, Anda akan mendapatkan notif ketika ada video baru muncul di channel tersebut. Nah, proses menunggu untuk mendapatkan data ini disebut “observe” (mengamati).
Di dalam Android contoh yang sering digunakan yaitu penggunaan Broadcast Receiver untuk mendapatkan event yang di-publish oleh operating system (OS) dan LiveData untuk mendapatkan data yang terbaru. Implementasi lebih lanjut prinsip ini terdapat pada Reactive Programming dikombinasi dengan pemrosesan aliran data (stream data) secara Asynchronous melalui library yang cukup populer seperti RXJava yang akan kita pelajari di dalam kelas ini.
d. Template Method Pattern
Apabila Anda memiliki dua komponen dengan tugas yang mirip, Anda dapat membuat pola (template) yang dapat dicontoh untuk menyelesaikan masalah tersebut. Caranya yaitu dengan membuat abstract class sebagai superclass dengan default implementation. Nah, jika ingin melakukan tugas yang lebih spesifik, Anda dapat membuat subclass-nya dan override beberapa bagian fungsi yang diinginkan.
Salah satu contoh yang mungkin sudah Anda pelajari adalah pembuatan NetworkBoundResource seperti berikut:
—————————————- Superclass —————————————- abstract class NetworkBoundResource<ResultType, RequestType>(private val mExecutors: AppExecutors) { protected abstract fun loadFromDB(): LiveData<ResultType> protected abstract fun shouldFetch(data: ResultType?): Boolean protected abstract fun createCall(): LiveData<ApiResponse<RequestType>> protected abstract fun saveCallResult(data: RequestType) private fun fetchFromNetwork(dbSource: LiveData<ResultType>) { //default implementation } fun asLiveData(): LiveData<Resource<ResultType>> = result } —————————————- Subclass —————————————- fun getAllTourism(): LiveData<Resource<List<TourismEntity>>> = object : NetworkBoundResource<List<TourismEntity>, List<TourismResponse>>(appExecutors) { override fun loadFromDB(): LiveData<List<TourismEntity>> { return localDataSource.getAllTourism() } override fun shouldFetch(data: List<TourismEntity>?): Boolean = data == null || data.isEmpty() override fun createCall(): LiveData<ApiResponse<List<TourismResponse>>> = remoteDataSource.getAllTourism() override fun saveCallResult(data: List<TourismResponse>) { val tourismList = DataMapper.mapResponsesToEntities(data) localDataSource.insertTourism(tourismList) } }.asLiveData() |
Pada superclass Anda membuat default implementation seperti pada fungsi fetchFromNetwork. Selain itu juga ada template fungsi apa saja yang harus dipanggil, seperti loadFromDb, shouldFetch, createCall, dan safeCallResult. Nah, implementasi detail dari masing-masing fungsi tersebut diserahkan kepada subclass.