Potongan kode pada materi ini: https://repl.it/@dicodingacademy/163-02-class?lite=true
Dalam paradigma Object-Oriented Programming (OOP), class merupakan sebuah blueprint yang dapat dikembangkan untuk membuat sebuah objek. Blueprint ini merupakan sebuah template yang di dalamnya menjelaskan seperti apa perilaku dari objek itu (berupa properti ataupun method).
Paradigma OOP selalu digambarkan dengan kehidupan nyata. Visualisasi di atas mencontohkan gambaran umum OOP di mana terdapat sebuah blueprint kucing, nilai yang dimiliki kucing, dan kemampuan yang dapat dilakukan olehnya.
Dalam OOP blueprint tersebut dikenal dengan class (kelas), nilai yang dimiliki olehnya dikenal dengan properti, kemampuan yang dimilikinya dikenal sebagai behaviour/method dan realisasi dari sebuah blueprint tersebut disebut instance.
Dalam dunia pemrograman khususnya dalam paradigma OOP, kita sering membuat banyak objek dengan jenis yang sama. Dengan membuat blueprint ini kita dapat mengurangi duplikasi kode dalam membuat objek yang serupa.
ES6 Classes
Dengan hadirnya class pada ES6, pembuatan class di JavaScript menjadi lebih mudah dan juga penulisannya mirip seperti bahasa pemrograman lain berbasis class. Pembuatan class pada ES6 menggunakan keyword class itu sendiri kemudian diikuti dengan nama class-nya.
- class Car {
- // Sama seperti function constructor
- constructor(manufacture, color) {
- this.manufacture = manufacture;
- this.color = color;
- this.enginesActive = false;
- }
- // Sama seperti Car.prototype.startEngine
- startEngines() {
- console.log('Mobil dinyalakan...');
- this.enginesActive = true;
- }
- // Sama seperti car.prototype.info
- info() {
- console.log(`Manufacture: ${this.manufacture}`);
- console.log(`Color: ${this.color}`);
- console.log(`Engines: ${this.enginesActive ? "Active" : "Inactive"}`);
- }
- }
- const johnCar = new Car("Honda", "Red");
- johnCar.startEngines();
- johnCar.info();
- /* output:
- Mobil dinyalakan...
- Manufacture: Honda
- Color: Red
- Engines: Active
- */
Jika Anda terbiasa dengan bahasa pemrograman berbasis class, pasti penulisannya sangat serupa bukan? Walaupun dari segi sintaksis pembuatan class antara keduanya cukup berbeda, namun perilaku dari objek yang dibuat dengan keduanya sama. Inilah mengapa class pada ES6 hanya sebuah syntactic sugar dari konsep prototype yang sudah ada.
Mari kita bahas class pada ES6 lebih lanjut.
“Ketika kita hendak membuat sebuah constructor function ataupun class. Secara code convention (aturan penulisan), gunakan CamelCase dalam penamaannya. Contohnya Car daripada car, SportCar daripada sportCar atau Sportcar”
Constructor
Deklarasi class menggunakan ES6 memiliki sifat yang sama seperti pembuatan class menggunakan function constructor (seperti contoh sebelumnya). Namun alih-alih menggunakan function constructor dalam menginisialisasi propertinya, class ini memisahkan constructornya dan ditempatkan pada body class menggunakan method spesial yang dinamakan constructor.
- class Car {
- constructor(manufacture, color) {
- this.manufacture = manufacture;
- this.color = color;
- this.enginesActive = false;
- }
- }
constructor biasanya hanya digunakan untuk menetapkan nilai awal pada properti berdasarkan nilai yang dikirimkan pada constructor. Namun sebenarnya kita juga dapat menuliskan logika di dalam constructor jika memang kita memerlukan beberapa kondisi sebelum nilai properti diinisialisasi.
Kita juga melihat penggunaan this pada constructor. Konteks dalam class, keyword this merujuk pada instance dari class tersebut. Sehingga this dapat digunakan untuk mengelola properti yang terdapat pada instance
Instance
Setelah kita membuat class pada JavaScript, lantas bagaimana cara membuat instance dari class tersebut? Tapi sebelumnya, apa itu instance? Instance merupakan objek yang memiliki properti dan method yang telah ditentukan oleh blueprint-nya (class), atau singkatnya adalah objek yang merupakan hasil realisasi dari sebuah blueprint.
Sama seperti constructor function, untuk membuat instance dari class pada ES6 kita gunakan keyword new.
- class Car {
- constructor(manufacture, color) {
- this.manufacture = manufacture;
- this.color = color;
- this.enginesActive = false;
- }
- }
- const johnCar = new Car("Honda", "Red");
Pembuatan class menggunakan ES6 lebih ketat dibandingkan dengan constructor function, di mana dalam pembuatan instance wajib menggunakan keyword new. Jika kita tidak menuliskannya, maka akan terjadi error seperti ini:
- class Car {
- constructor(manufacture, color) {
- this.manufacture = manufacture;
- this.color = color;
- this.enginesActive = false;
- }
- }
- const johnCar = Car("Honda", "Red");
- /* error:
- TypeError: Class constructor Car cannot be invoked without 'new'
- */
Kita juga dapat membuat banyak instance dari class yang sama, dan tentunya objek yang kita buat memiliki karakteristik (properti dan method) yang sama. Walaupun sama, namun nilai dari propertinya bersifat unik atau bisa saja berbeda. Contohnya seperti ini:
- class Car {
- constructor(manufacture, color) {
- this.manufacture = manufacture;
- this.color = color;
- this.enginesActive = false;
- }
- }
- const johnCar = new Car("Honda", "Red");
- const adamCar = new Car("Tesla", "Black");
- console.log(johnCar.manufacture);
- console.log(adamCar.manufacture);
- /* output:
- Honda
- Tesla
- */
Variabel johnCar dan adamCar merupakan sebuah objek dari Car. Tentu keduanya akan memiliki properti manufacture, color, dan enginesActive. Namun pada output kita melihat bahwa nilai dari properti kedua objek tersebut berbeda, karena kita dapat memberikan nilai yang berbeda pada saat objeknya dibuat
Property Accessor
Melalui objek class kita juga dapat mengubah nilai properti seperti ini:
- class Car {
- constructor(manufacture, color) {
- this.manufacture = manufacture;
- this.color = color;
- this.enginesActive = false;
- }
- }
- const johnCar = new Car("Honda", "Red");
- console.log(`Warna mobil: ${johnCar.color}`); // output -> Warna Mobil: Red
- johnCar.color = "White"; // Mengubah nilai properti color menjadi white
- console.log(`Warna mobil: ${johnCar.color}`); // output -> Warna Mobil: White
Dengan class kita juga dapat mengimplementasi getter/setter sebuah properti menjadi sebuah method seperti ini:
- class Car {
- constructor(manufacture, color) {
- this.manufacture = manufacture;
- this._color = color;
- this.enginesActive = false;
- }
- get color() {
- return `Warna mobile ${this._color}`;
- }
- set color(value) {
- console.log(`Warna mobil diubah dari ${this._color} menjadi ${value}`);
- this._color = value;
- }
- }
- const johnCar = new Car("Honda", "Red");
- console.log(johnCar.color); // output -> Warna Mobil: Red
- johnCar.color = "White"; // Mengubah nilai properti color menjadi white
- console.log(johnCar.color); // output -> Warna Mobil: White
Perhatikan juga ketika kita menerapkan getter/setter pada properti class. Kita perlu mengubah atau membedakan penamaan properti aslinya dengan property accessor yang kita buat.
Berdasarkan code convention yang ada kita perlu mengubah properti asli class-nya dengan menambahkan underscore di depan nama propertinya (_color). Tanda underscore berfungsi sebagai tanda bahwa properti _color tidak sebaiknya diakses langsung, namun harus melalui property accessor (getter/setter).
Method
Untuk menambahkan method pada class, kita juga cukup menuliskannya pada body class, tidak perlu melalui prototype seperti menggunakan constructor function.
- class Car {
- constructor(manufacture, color) {
- this.manufacture = manufacture;
- this.color = color;
- this.enginesActive = false;
- }
- startEngines() {
- console.log("Mesin dinyalakan");
- this.enginesActive = true;
- }
- info() {
- console.log(`Manufacture: ${this.manufacture}`);
- console.log(`Color: ${this.color}`);
- console.log(`Engines: ${this.manufacture ? "Active" : "Inactive"}`);
- }
- }
- const johnCar = new Car("Honda", "Red");
- johnCar.startEngines();
- johnCar.info();
- /* output:
- Mesin dinyalakan
- Manufacture: Honda
- Color: Red
- Engines: Active
- */
Dengan menggunakan class, walaupun kita menuliskan method pada body class, namun method tersebut tetap berada pada prototype chain miliki instance yang terbuat. Kita bisa melihat bagaimana objek yang dibuat menggunakan class pada console browser.
Inheritance
Dalam gambaran dunia nyata, banyak objek yang berbeda tetapi punya kesamaan atau kemiripan tertentu. Contohnya mobil dengan motor memiliki banyak kesamaan karena objek tersebut merupakan kendaraan. Mobil merupakan kendaraan darat begitu juga dengan motor. Mungkin yang membedakan objek tersebut adalah jumlah roda dan kapasitas penumpang yang dapat ditampung.
Sama halnya pada OOP, beberapa objek yang berbeda bisa saja memiliki kesamaan dalam hal tertentu. Di situlah konsep inheritance atau pewarisan harus diterapkan. Pewarisan dapat mencegah kita melakukan perulangan kode. Untuk lebih memahaminya lihatlah contoh bagan pada sebuah kelas berikut:
Pada bagan di atas kita dapat lihat class Car, Motorcycle, Plane, dan Helicopter memiliki banyak properti yang sama seperti lisencePlate, manufacture, dan engineActive. Kemudian memiliki beberapa method yang sama seperti startEngines(), info(), dan parking().
Jika kita ubah diagram class Car di atas menjadi sebuah kode maka kode tampak seperti ini:
- class Car {
- constructor(licensePlate, manufacture, wheels) {
- this.licensePlate = licensePlate;
- this.manufacture = manufacture;
- this.wheels = wheels;
- this.engineActive = false;
- }
- startEngines() {
- console.log(`Mesin kendaraan ${this.licensePlate} dinyalakan!`);
- }
- info() {
- console.log(`Nomor Kendaraan: ${this.licensePlate}`);
- console.log(`Manufacture: ${this.manufacture}`);
- console.log(`Mesin: ${this.engineActive ? "Active": "Inactive"}`);
- }
- droveOff() {
- console.log(`Kendaraan ${this.licensePlate} melaju!`)
- }
- openDoor() {
- console.log(`Membuka pintu!`)
- }
- parking() {
- console.log(`Kendaraan ${this.licensePlate} parkir!`);
- }
- }
Tidak ada masalah dengan kode tersebut, tetapi jika kita akan membuat kelas lainnya seperti Motorcycle, Plane, dan Helicopter maka kita harus menuliskan properti dan method yang sama secara berulang.
Dengan teknik inheritance, kita bisa mengelompokkan properti dan method yang sama. Caranya dengan membuat sebuah kelas baru yang nantinya akan diturunkan sifatnya pada class lain:
Ketika class Vehicle telah dibuat, kelas lainnya dapat melakukan extends pada kelas tersebut untuk mewarisi sifatnya. Dalam pewarisan, class Vehicle dapat disebut sebagai super atau parent class. Kelas yang mewarisi sifat dari parent class disebut dengan child class.
Pada JavaScript jika kita ingin mewariskan sifat class, lakukan dengan keyword extends seperti berikut:
- class ChildClass extends ParentClass {
- }
Sebagai contoh mari kita buat class Vehicle yang nantinya akan kita gunakan sebagai parent class.
- class Vehicle {
- constructor(licensePlate, manufacture) {
- this.licensePlate = licensePlate;
- this.manufacture = manufacture;
- this.engineActive = false;
- }
- startEngines() {
- console.log(`Mesin kendaraan ${this.licensePlate} dinyalakan!`);
- }
- info() {
- console.log(`Nomor Kendaraan: ${this.licensePlate}`);
- console.log(`Manufacture: ${this.manufacture}`);
- console.log(`Mesin: ${this.engineActive ? "Active": "Inactive"}`);
- }
- parking() {
- console.log(`Kendaraan ${this.licensePlate} parkir!`);
- }
- }
Kemudian kita bisa membuat class Car sebagai child class dari Vehicle.
- class Car extends Vehicle {
- constructor(licensePlate, manufacture, wheels) {
- super(licensePlate, manufacture);
- this.wheels = wheels;
- }
- droveOff() {
- console.log(`Kendaraan ${this.licensePlate} melaju!`);
- }
- openDoor() {
- console.log(`Membuka pintu!`);
- }
- }
Dengan begitu selain properti dan method yang terdapat di dalamnya, class Car juga dapat mengakses seluruh properti dan method yang terdapat pada class Vehicle.
- class Vehicle {
- constructor(licensePlate, manufacture) {
- this.licensePlate = licensePlate;
- this.manufacture = manufacture;
- this.engineActive = false;
- }
- startEngines() {
- console.log(`Mesin kendaraan ${this.licensePlate} dinyalakan!`);
- }
- info() {
- console.log(`Nomor Kendaraan: ${this.licensePlate}`);
- console.log(`Manufacture: ${this.manufacture}`);
- console.log(`Mesin: ${this.engineActive ? "Active": "Inactive"}`);
- }
- parking() {
- console.log(`Kendaraan ${this.licensePlate} parkir!`);
- }
- }
- class Car extends Vehicle {
- constructor(licensePlate, manufacture, wheels) {
- super(licensePlate, manufacture);
- this.wheels = wheels;
- }
- droveOff() {
- console.log(`Kendaraan ${this.licensePlate} melaju!`);
- }
- openDoor() {
- console.log(`Membuka pintu!`);
- }
- }
- const car = new Car("H121S", "Honda", 4);
- car.startEngines();
- /* output:
- Mesin kendaraan H121S dinyalakan!
- */
Oiya pada constructor class Car, kita melihat penggunaan super(), apa itu maksudnya? Keyword super digunakan untuk mengakses properti dan method yang ada pada induk class ketika berada pada child class.
Jadi super(lisencePlate, manufacture) di atas berarti kita mengakses constructor dari parent class dan mengirimkan lisencePlate, dan manufacture sebagai data yang dibutuhkan olehnya agar objek (instance) Car berhasil dibuat.
Penggunaan super sangat berguna ketika kita hendak menjalankan method overriding pada method parent. Contohnya kita akan melakukan method overriding pada method info() dengan menambahkan informasi jumlah roda pada mobil, maka kita dapat melakukannya dengan seperti ini:
- class Car extends Vehicle {
- constructor(licensePlate, manufacture, wheels) {
- super(licensePlate, manufacture);
- this.wheels = wheels;
- }
- droveOff() {
- console.log(`Kendaraan ${this.licensePlate} melaju!`);
- }
- openDoor() {
- console.log(`Membuka pintu!`);
- }
- /* overriding method info dari parent class */
- info() {
- super.info();
- console.log(`Jumlah roda: ${this.wheels}`);
- }
- }
- const johnCar = new Car("H121S", "Honda", 4);
- johnCar.info();
- /* output:
- Nomor Kendaraan: H121S
- Manufacture: Honda
- Mesin: Inactive
- Jumlah roda: 4
- */
Dalam melakukan pewarisan kelas, tidak ada tingkatan yang membatasinya. Maksudnya, kita dapat mewariskan sifat kelas A pada kelas B, lalu kelas B mewarisi sifatnya kembali pada kelas C dan selanjutnya. Sama halnya dengan Nenek kita mewarisi sifatnya kepada orang tua kita kemudian orang tua kita mewarisi sifatnya kepada kita.
Sehingga jika dilihat dari bagan sebelumnya, class tersebut masih bisa dikelompokkan kembali menjadi seperti ini:
Static Method
Seluruh kendaraan pasti butuh yang namanya perawatan bukan? Jika iya, tentu kita perlu membuat method repair untuk memperbaiki kendaraan tersebut.
Dalam analogi dunia nyata, ketika kendaraan mengalami kerusakan maka kendaraan tersebut akan diperbaiki di bengkel (factory), sehingga kita perlu membuat class baru yang berperan sebagai factory, sebutlah class tersebut VehicleFactory.
Di dalam kelas VehicleFactory terdapat satu method repair() yang dapat menerima banyak kendaraan sebagai parameternya.
- class Vehicle {
- constructor(licensePlate, manufacture) {
- this.licensePlate = licensePlate;
- this.manufacture = manufacture;
- this.engineActive = false;
- }
- /*
- kode lainnya
- */
- }
- /* kode lainnya dalam pembuatan class Car,
- Motorcycle, dsb. */
- class VehicleFactory {
- repair(vehicles) {
- vehicles.forEach(vehicle => {
- console.log(`Kendaraan ${vehicle.licensePlate} sedang melakukan perawatan`)
- })
- }
- }
Untuk mengakses method dari class, sejauh ini kita perlu membuat instance dari classnya terlebih dahulu. Sehingga untuk memanggil repair(), kita perlu membuat instance dari class VehicleFactory
- const johnCar = new Car("H121S", "Honda", 4);
- const tomMotor = new Motorcycle("GF121J", "Yamaha", 2);
- const dimasCar = new Car("TA1408K", "Tesla", 4);
- /* Membuat instance untuk memanggil fungsi repair */
- const vehicleFactory = new VehicleFactory();
- vehicleFactory.repair([johnCar, tomMotor, dimasCar]);
- /* output:
- Kendaraan H121S sedang melakukan perawatan
- Kendaraan GF121J sedang melakukan perawatan
- Kendaraan TA1408K sedang melakukan perawatan
- */
Kode tersebut berjalan sesuai harapan namun tidak efektif. Mengapa? Karena kita perlu membuat instance untuk sekedar memanggil satu fungsi dari class-nya tersebut.
Membuat instance adalah membuat sebuah objek baru yang terbentuk melalui blueprint sehingga membutuhkan memori ekstra. Jika kita dapat mengakses method tersebut tanpa melalui instance mengapa tidak?
Pada kasus inilah kita membutuhkan sebuah static method. Static method merupakan method yang tidak dapat dipanggil oleh instance dari class, namun dapat dipanggil melalui class-nya sendiri.
Pada ES6 class kita dapat membuat static method dengan menambahkan keyword static sebelum deklarasi method-nya:
- class Vehicle {
- constructor(licensePlate, manufacture) {
- this.licensePlate = licensePlate;
- this.manufacture = manufacture;
- this.engineActive = false;
- }
- /*
- kode lainnya
- */
- }
- /* kode lainnya dalam pembuatan class Car,
- Motorcycle, dsb. */
- class VehicleFactory {
- static repair(vehicles) {
- vehicles.forEach(vehicle => {
- console.log(`Kendaraan ${vehicle.licensePlate} sedang melakukan perawatan`)
- })
- }
- }
Kemudian untuk memanggil methodnya kita bisa panggil melalui class Vehicle kemudian repair().
- const johnCar = new Car("H121S", "Honda", 4);
- const tomMotor = new Motorcycle("GF121J", "Yamaha", 2);
- const dimasCar = new Car("TA1408K", "Tesla", 4);
- /* Pemanggilan method repair langsung dari class-nya */
- VehicleFactory.repair([johnCar, tomMotor, dimasCar]);
- /* output:
- Kendaraan H121S sedang melakukan perawatan
- Kendaraan GF121J sedang melakukan perawatan
- Kendaraan TA1408K sedang melakukan perawatan
- */
Solution : Class
Apakah Anda sudah berhasil mengubah mengubah function constructor menjadi class? Jika belum, mari kita lakukan hal tersebut bersama-sama.
Pertama, kita buka berkas data-source.js. Kemudian ubah penerapan function constructor berikut:
- function DataSource(onSuccess, onFailed) {
- this.onSuccess = onSuccess;
- this.onFailed = onFailed;
- }
Dengan menggunakan class:
- class DataSource {
- constructor(onSuccess, onFailed) {
- this.onSuccess = onSuccess;
- this.onFailed = onFailed;
- }
- }
Kemudian untuk fungsi searchClub, kita ubah penulisannya dari:
- DataSource.prototype.searchClub = function (keyword) {
- const filteredClubs = clubs.filter(club => club.name.toUpperCase().includes(keyword.toUpperCase()));
- if (filteredClubs.length) {
- this.onSuccess(filteredClubs);
- } else {
- this.onFailed(`${keyword} is not found`);
- }
- };
Menjadi method yang dituliskan pada body class DataSource seperti ini:
- class DataSource {
- constructor(onSuccess, onFailed) {
- this.onSuccess = onSuccess;
- this.onFailed = onFailed;
- }
- searchClub(keyword) {
- const filteredClubs = clubs.filter(club => club.name.toUpperCase().includes(keyword.toUpperCase()));
- if (filteredClubs.length) {
- this.onSuccess(filteredClubs);
- } else {
- this.onFailed(`${keyword} is not found"`);
- }
- }
- }
Untuk memastikan perubahan yang kita lakukan sudah benar, silakan Anda coba buka berkas index.html pada browser. Jika aplikasi berjalan dengan lancar, maka class berhasil diterapkan dengan baik.
Namun jika aplikasi tidak berjalan lancar, dan menghasilkan eror pada console browser, jangan malu untuk bertanya pada forum diskusi ya!
Langkah dari solution ini bisa Anda temukan juga pada repository berikut: https://github.com/dicodingacademy/a163-bfwd-labs/tree/106-club-finder-class-solution
z
Posting Komentar