Saya berasumsi bahwa kamu sudah pernah mengenal bahasa pemrograman berorientasi objek lainnya sebelum kenal Ruby. Pada bahasa lain, proses instantiasi suatu kelas menjadi objek adalah dengan menggunakan operator new
seperti ini.
1 2 3 4 5 |
// C# CreateBookService service = new CreateBookService(); // PHP $service = new CreateBookService(); |
Di Ruby nyatanya tidak seperti itu, yang digunakan adalah class method bernama new
seperti ini.
1 |
service = CreateBookService.new |
Bagi yang mempunyai modal OOP di bahasa lain maka mengetahui bentuk di Ruby yang seperti itu mungkin bisa menimbulkan sedikit keheranan. Ditambah lagi constructor di Ruby yang beda dibanding dengan yang lain. Di tempat lain, constructor biasanya memiliki nama yang sama dengan nama kelasnya, misalnya di C# serupa ini.
1 2 3 4 5 6 7 8 |
public class Service { // constructor public Service() { } } |
Di Ruby constructor adalah method yang bernama initialize
Ternyata eh ternyata, proses instantiasi objek di Ruby memanfaatkan salah satu jenis dari design pattern yang terkenal itu. Pattern yang digunakan saat sebuah kelas dibuatkan instance-nya adalah template pattern
. Design pattern ini intinya adalah memiliki sebuah method yang mana di dalamnya memanggil serangkaian method lain yang harus dipenuhi. Dengan kata lain serangkaian method yang harus dipenuhi ini adalah template itu sendiri.
Supaya ada gambaran misal kita punya kode seperti ini.
1 2 3 4 5 6 7 8 9 10 11 |
class Service def call prepare perform after end def prepare; raise NotImplementedError; end def perform; raise NotImplementedError; end def after; raise NotImplementedError; end end |
Dari kode di atas berarti kelas Service
ini memiliki template di method call
yang mana di dalamnya dia memanggil berturut-turut prepare
, perform
dan after
. Artinya adalah kelas ini nantinya akan dijadikan parent dari suatu kelas yang menggunakan template itu sehingga dia harus melakukan override terhadap 3 method yang ada. Contoh,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class CreateBookService < Service def prepare puts 'preparation' end def perform puts 'do cool things here' end def after puts 'celebrate it' end end CreateBookService.new.call #=> preparation do cool things here celebrate it |
Kurang lebih seperti itu gambarannya dari template pattern. Lalu bagaimana hubungannya dengan proses instantiasi objek di Ruby ?
Proses instantiasi objek di Ruby adalah berbentuk template dengan urutan seperti ini
1. Membuat instance
2. Melakukan inisialisasi state / properti dari instance
3. Mengembalikan / me-return instance tadi
Mari kita coba buat method kita sendiri untuk melakukan instantiasi objek supaya paham bagaimana proses yang sebenarnya dilakukan oleh Ruby. Pertama adalah membuat kelas method bernama my_new
.
1 2 3 4 |
class Book def self.my_new end end |
Oke sudah, selanjutnya adalah mulai menerapkan kode untuk langkah 1-3 di atas tadi. Untuk langkah pertama, Ruby menyediakan method bernama allocate
untuk menciptakan objek dari kelas yang bersangkutan.
1 2 3 4 5 |
class Book def self.my_new instance = allocate end end |
Langkah kedua adalah menginisialisasi state yang dimiliki. Untuk saat ini kita ambil gampang dulu seperti berikut ya dengan anggapan ada 2 state yang dimiliki. Di sini kita juga mengubah my_new
menjadi menerima parameter.
1 2 3 4 5 6 7 |
class Book def self.my_new(title, author) instance = allocate instance.instance_variable_set(:@title, title) instance.instance_variabel_set(:@author, author) end end |
Dan langkah terakhir adalah mengembalikan instance yang bersangkutan.
1 2 3 4 5 6 7 8 9 10 11 |
class Book def self.my_new(title, author) instance = allocate instance.instance_variable_set(:@title, title) instance.instance_variable_set(:@author, author) instance end end Book.my_new('Ruby untuk Semua', 'Agung Setiawan') #=> #<Book:0x007efd1f38bb70 @title="Ruby untuk Semua", @author="Agung Setiawan"> |
Sampai sini kita sudah bisa membuat method sendiri untuk melakukan instantiasi objek sekaligus memberikan nilai awal. Akan tetapi, pada langkah kedua ada kode yang tidak fleksibel
1 2 |
instance.instance_variable_set(:@title, title) instance.instance_variable_set(:@author, author) |
Dengan bentuk yang seperti itu kita tidak bisa membuat sembarang instance variabel karena letaknya yang ada di dalam method new
(dalam contoh kita berarti method my_new
) yang mana kita tidak bisa sembarangan mengeditnya. Dari situ lah digunakan template pattern.
Dengan pendekatan baru ini maka proses inisiasi state dilakukan pada method yang berbeda, sebut saja namanya initialize
atau di contoh yang kita buat berarti my_initialize
.
1 2 3 4 5 6 7 8 9 10 11 12 |
class Book def self.my_new(title, author) instance = allocate instance.my_initialize(title, author) instance end def my_initialize(title, author) @title = title @author = author end end |
Dari kode di atas sudah terlihat adanya template kan?, yaitu pemanggilan my_initialize
yang mana isinya mesti kita buat sendiri.
Sampai sini masih belum clear karena coba lihat potongan kode di bawah ini.
1 2 3 4 5 |
def self.my_new(title, author) instance = allocate instance.my_initialize(title, author) instance end |
Kode di atas berarti menganggap akan selalu ada 2 buah parameter yang diberikan di my_new
untuk menginisialisasi state. Kenyataannya kita tidak tahu akan ada berapa parameter yang diberikan dan karena kita tidak punya kuasa terhadap method new
atau my_new
ini maka cara statis seperti ini tidak bisa digunakan.
Lalu bagaimana solusinya?
Gunakan splat!
1 2 3 4 5 6 7 8 9 10 11 12 |
class Book def self.my_new(*args) instance = allocate instance.my_initialize(*args) instance end def my_initialize(title, author) @title = title @author = author end end |
Dengan begini berapapun instance variabel yang kita miliki akan bisa diberikan nilai awal menggunakan my_initialize
karena sudah menggunakan splat serta kita punya kuasa sesukanya terhadap my_initialize
.