Sunday, December 7, 2008

Konsep Visibility pada PHP 5

Beberapa waktu lalu di milist PHP-UG ada yang nanya tentang konsep Visibility pada PHP 5. Banyak juga yang sudah menjawab. Dan menurut gue jawabannya juga bagus-bagus. Tapi gue juga ikutan jawab, karena sebagian besar jawabannya tidak mengacu pada konsep dasar tentang Visibility secara umum dalam pemrograman berorientasi objekt.

Tulisan gue di milist, gue masukin di blog sebagai arsip gue pribadi. Mungkin juga berguna buat yang lain kalau-kalau satu saat ingin cari tahu mengenai konsep visibility. Kalau ada kritik, koreksi atau tambahan, tentunya gue dengan senang hati menerima.

Secara umum penggunaan Visibility pada PHP 5 bisa dilihat langsung di dokumentasinya PHP 5. Setelah kita tahu bagaimana visibility berfungsi pada function, atau member variabel, gue rasa penting juga sebenarnya bertanya, kenapa sebenarnya ada visibility? Kapan pakainya? Buat kalian yang beranjak dari PHP < 5, maka pertanyaan yang sebenarnya juga cukup menarik, kenapa PHP sekarang support visibility?

Private, public, protected merupakan bagian dari prinsip "information hiding" dalam ilmu komputer. Kalau ingin tahu apa itu prinsip "information hiding" silahkan lihat di wikipedia.

Disitu hanya dijelaskan secara gamblang saja. Ada banyak sekali hal yang bersangkutan dengan "information hiding", dan pada kesempatan ini gue pengen coba untuk menjelaskan hanya pada bagian visibility.

Menjelaskan topik informatika paling enak pake analogi, jadi gue berusaha beranalogi. Gue coba2 cari di google analogi yang lebih pas, tapi gue gak ketemu. Kalau ada yang bisa memberikan analogi yang lebih pas, silahkan dibagi ^^.

Analogi gue kali ini adalah mobil. Mobil punya setir, punya starter, punya perseneling, dan minimal punya pedal gas dan rem (kopling untuk transmisi manual). Semua mobil punya "fungsi" ini. Dan semua pengemudi tahu dan "berharap" bahwa fungsi tersebut ada di dalam sebuah mobil. Keberadaan fungsi2 tersebut dalam sebuah mobil bisa kita lihat sebagai bentuk "public visibility".

Mobil bisa jalan karena ada mesinnya. Masalahnya disini adalah, bagaimana bentuk mesinnya, bagaimana cara kerjanya. atau. apakah sebenarnya mobil tersebut jalan pakai mesin? Hal itu semua sudah bukan urusan pengemudi mobil. Hal itu sudah urusan perusahaan yang bikin itu mobil. Bisa jadi mas Joko Suprapto punya methode lain buat bikin mobil bisa jalan ^^ (Ilmu gaib mungkin? ^^ ).

Fungsi mesin tidak hanya sekedar urusan perusahaan, akan tetapi mesin merupakan bagian paling penting dari inovasi perusahaan. Apakah mobil tersebut bisa ngebut, irit bensin, tidak berisik, ramah lingkungan, semuanya ditentukan dari kerja mesin tersebut. Karena semua jerih payah berpikir para insinyur automobile ada di dalam situ, perusahaan melindungi informasi tersebut, dan ingin agar fungsi tersebut tidak digunakan untuk kepentingan lain, selain untuk menjalankan mobil, ^^ dengan menggunakan fungsi2 "publik" yang sudah didefinisikan (setir, perseneling, pedal, dsb). Pokoknya pengemudi hanya tahu, untuk jalanin sebuah mobil, kudu starter dulu, masukin perseneling. dan kalo injek gas, mobilnya jalan, kalo di rem berhenti. Fungsi mesin disini bisa kita lihat sebagai bentuk "private visibility".

Perusahaan pembuat mobil tahu, bahwa dia gak akan bisa membuat mobil yang sesuai sama selera seluruh umat manusia ^^. Yang satu ingin mobilnya lebih nyaman, yang satu pengen bisa lebih cepat, yang satu ingin lebih irit, yang satu ingin lebih ramah lingkungan, dan lain sebagainya. Maka dari itu perusahaan pembuat mobil membuat beberapa komponen mobil memiliki kesempatan untuk di expand atau dimodifikasi. Entah itu pasang AC, pasang, Nitro, pasang katalisator, ganti jok mobil, ganti velg, diceperin dan lain sebagainya. Spesifikasi dan informasi komponen-komponen yang bisa di expand atau dimodifikasi tersebut diberikan oleh perusahaan mobil, ke bengkel2 mobil. Apakah itu bengkel resmi ato bukan untuk sementara tidak relevan ^^.

Seperti halnya mesin pada perusahaan mobil. sebagai pengemudi, kita tetap tidak tahu menahu tentang komponen ini. Kita tetap hanya mengetahui fungsi-fungsi publik. Kita tetap mengendarai mobil seperti biasanya. Hanya saja mobil kita mungkin sekarang lebih cepat, lebih irit, lebih nyaman, atau lebih-lebih yang lainnya. ^^. Analogi yang ini bisa kita lihat sebagai bentuk "protected visibility".

So itu analoginya visibility. Kalo dilihat dari subject threadnya ada yang nanya tentang visibility jenis "global". Jenis itu sepertinya spesifik di php doang, dimana function berdiri sendiri, tidak dalam sebuah kelas. CMIIW. Agak sulit menganalogikannya dalam kasus mobil diatas, karena paradigma object oriented itu melihat semuanya berupa object. Kalau gue memisalkan fungsi dengan visibility "global" sebagai kunci stang (yang jaman dulu tuh, yang buat dipasang dari setir ke pedal, tauk deh ada yang bisa bayangin kah) gak bisa juga. Karena kunci stang adalah object ^^ yang punya fungsi yaitu - seperti namanya - ngunci setir. ^^

Setelah panjang berteori fundamental visibility pada pemrogramman berorientasi obyek. sekarang mari kita coba menjawab pertanyaan kenapa ada visibility? Kapan pakainya, dan kenapa PHP sekarang support visibility?

PHP berkembang dari sekedar Personal Home Page pre-processor tools yang dibuat Rasmus Lerdorf, menjadi sebuah Full Blown Web Application language. Gue gak bilang enterprise ^^, karena untuk menjadikannya enterprise gak cukup pengetahuan PHP doang seperti layaknya sodara kita di java dan .net ^^.

Untuk membuat aplikasi, banyak komponen2 yang akan sering digunakan berulang-ulang di setiap project. Daripada setiap kali harus tulis ulang, mendingan dipakai lagi. Muncullah berbagai macam jenis framework di PHP yang umumnya punya tujuan yang sama, yaitu membuat pembangunan aplikasi web menjadi lebih cepat, mudah, dan bug free.

Membuat framework, sama halnya dengan membuat mobil, para developer framework menentukan melalui fungsi apa framework tersebut digunakan (public), fungsi-fungsi apa yang critical dan tidak boleh digunakan untuk kepentingan lain (private), dan fungsi-fungsi apa yang punya kemungkinan untuk di expand atau diganti (protected). Pengguna framework hanya akan dihadapkan dengan fungsi2 yang memiliki visibility public dalam membangun web aplikasinya.

Karena visibility baru di support setelah PHP 5, pembuatan framework memiliki problem dalam penggunaan visibility. Untuk mengantisipasi hal ini, Segala macam bentuk code convention digunakan dalam komunitas framework. Salah satunya adalah PEAR convention yang memberikan tanda strip bawah ( _ ) untuk setiap private member variabel dan private method.

Framework gak selalu harus pakai punya orang. Buat mereka yang bekerja product developing, umumnya punya methode dan fungsi2 proprietary yang juga digunakan dalam pekerjaan sehari-hari. Dalam hal ini bisa jadi masing-masing memiliki code convention sendiri-sendiri.

Sekarang gue mo coba memberikan contoh praktis dalam bentuk code untuk belajar memahami kapan menggunakan dan memilih visibility. Code ini jauh dari sempurna dalam konteks design-nya. Diantara kalian pasti bisa membuat yang jauh lebih baik dari ini. Akan tetapi menurut gue ini bagus untuk membantu memahami prinsip visibility.

Gue mau tetap bergerak di lingkup analogi gue, yaitu mobil. Gue mau membuat sebuah kelas "mobil". Untuk bisa memahami lebih baik, perlu diketahui bahwa dalam bekerja menggunakan paradigma berorientasi obyek, programmer harus berpikir bahwa code yang dia tulis suatu saat akan "digunakan" oleh orang lain, gue kasih tanda kutip, karena maksudnya disini bukan dimodifikasi atau dirubah.

Untuk itu gue buat skenario sebagai berikut.

Kelas mobil yang gue tulis disini adalah bagian dari sebuah aplikasi game. Anggap saja game balap mobil. Tanggung jawab gue adalah menulis kelas mobil ini dan kelas ini satu saat nanti akan digunakan oleh kolega kerja gue yang memprogramm kelas "pengemudi". Untuk itu gue udah berdiskusi sama dia, apa fungsi yang harus ada, yang perlu dia ketahui. lalu sama2 bikin daftarnya.
  • Fungsi setir. Menerima informasi kiri atau lurus atau kanan (-1, 0 atau 1)
  • Fungsi startEngine. Mengirimkan informasi kembali apakah mesin berhasil di starter (true atau false)
  • Fungsi stopEngine. mengirimkan informasi kembali apakah mesin berhasil dimatikan (true atau false)
  • Fungsi setPerseneling. menerima informasi perseneling (0 sampai 5)
  • Fungsi gas. menerima informasi tekanan seberapa besar gas diinjak (0-5)
  • Fungsi rem. menerima informasi tekanam seberapa besar rem diinjak (0-5)
So cukup. Sisanya dia gak perduli bagaimana gue mengimplementasikannya. Dia sebenarnya juga tidak berhak tahu ^^. Tapi gue berpikir agak jauh ke depan. bisa jadi code gue nanti bagus dan ingin terus dipakai, tapi karena satu dan lain hal, beberapa komponen sudah tidak begitu relevan lagi, baik itu kurang bagus, kurang cepat, atau kurang-kurang yang lainnya. Untuk itu gue akan mencoba mendefinisikan komponen apa yang kira-kira bisa diganti atau ditambah.

Setelah berpikir, maka gue menulis daftarnya.

  • Fungsi tangkiBensin
  • Fungsi knalpot
  • Fungsi airConditioner
So setelah ini kalo ada fungsi lain yang gue butuh, gue bebas berkreatifitas. Mau berapa banyak kek fungsinya dan namanya apa. Itu sudah terserah gue. Sekarang mari kita buat kelasnya.

class Mobil{

   private $engineStart = false;
   private $warna = 'putih';
   private $brand = 'bmw';
   private $perseneling = 0;

   public function __construct(){
       echo 'objek mobil dibuat';
       $this->tangkiBensin();
       $this->airConditioner();
       $this->knalpot();
   }

   public function setWarna($warna){
       $this->warna = $warna;
   }

   public function getWarna(){
       return $this->warna;
   }

  public function setBrand($brand){
      $this->brand = $brand;
  }

  public function getBrand(){
      return $this->brand;
  }

  public function engineStart(){
      $this->engineStart = true;
  }

  public function engineStop(){
      $this->engineStart = false;
  }

  public function isEngineStart(){
      return $this->engineStart;
  }

  public function setPerseneling($perseneling){
      $this->perseneling = $perseneling;
  }

  public function gas($gas){
      echo "nge-gas dengan tekanan pedal ".$gas;
      $this->knalpot();
  }

  public function rem($rem){
     echo "nge-rem dengan tekanan pedal ".$rem;
  }

  public function setir($arah){
       switch($arah){
            case -1:
                $this->moveLeft();
                break;
            case 0:
                $this->moveStraight();
                break;
           case 1:
                $this->moveRight();
                break;
           default:
                throw new Exception('hanya bisa 
                      menerima nilai -1, 0 atau 1');
       }
  }

  protected function tangkiBensin(){
        echo "tangki bensin muat 60 liter";
  }

  protected function knalpot(){
       echo "knalpot standard";
  }

 protected function airConditioned(){
     echo "gak punya AC";
 }

 private function moveLeft(){
     //TODO: implement how to move left
 }

 private function moveRight(){
     //TODO: implement how to move right
 }

 private function moveStraight(){
     //TODO: implement how to move Straight
 }

}
Okay, mari kita lihat dengan seksama. Pertama-tama member variabel. Gue nambahin warna dan brand, hanya sekedar tambahan saja untuk mendefinisikan personalitas sebuah objekt. Yang mau gue tunjukin disini adalah penggunaan visibility private pada getter dan setter (misal setBrand dan getBrand).

Tentu saja kita bisa pasang visibility apa saja pada member variabel. Tapi sudah merupakan best practice, bahwa member variabel itu sebaiknya private atau protected, dan member variabel tersebut hanya bisa dibaca dan ditulis melalui getter dan setter methodenya (kalo gue gak salah, best practice ini pelopornya java beans CMIIW). Apa alasannya? Dengan ini kita memiliki kontrol penuh atas nilai yang diberikan kepada member variabel tersebut. Sebagai contoh, apabila kita hanya punya warna atau merk tertentu, kita bisa bikin validator di dalam set methode untuk menentukan apakah warna yang diberikan pada argument tersebut valid atau tidak.

Mungkin contoh warna dan merk tidak begitu critical. Tapi coba bayangkan kalau perseneling member variabel gue pasang public, terus temen gue pasang angka 6, dengan cara $mobilObj->perseneling = 6; padahal mobil gue cuma bisa nerima sampe 5. Gue gak mau bayangin apa yang akan terjadi ^^. Apabila memang dibutuhkan, maka gue dengan mudah bisa implementasikan if atau switch statement di methode setPerseneling untuk menghindari hal seperti ini.

Selain itu, untuk perseneling, gue gak memberikan methode getPerseneling. dalam hal ini, gue gak punya alasan konkrit. Tapi bisa saja terjadi dalam pekerjaan, bahwa kita tidak mau orang membaca kembali nilai yang sudah dia berikan ^^.

Seperti yang bisa dilihat, gue membuat fungsi tangkiBensin, knalpot dan airConditioned sebagai protected karena gue merasa bagian itu satu saat bisa di modifikasi oleh orang lain. Sisanya fungsi moveLeft, -Right, dan -Straight merupakan contoh implementasi gue tentang bagaimana gue menjalankan mobil. Mungkin bisa lebih dari ini fungsinya. Tapi ini sekedar contoh saja. Fungsi ini digunakan oleh methode setir dimana dia memvalidasi argument yang diberikan menggunakan switch statement, dan melempar exception apabila argument tersebut tidak valid.

Exception gak akan gue jelasin disini ^^, jadi makin banyak entar ^^. Gue sudah pernah jelasin tentang exception deh ^^.

Setelah kelas mobil gue kelar, gue kasih ke temen gue. Dan akhirnya dia bisa pake deh. Bagaimana dia makenya, kira2 begini:
class Pengemudi{

      //member variabel pengemudi
      private $mobilObj;

     //lots of code implementing pengemudi

    public function setMobil(Mobil $mobilObj){
        $this->mobilObj = $mobilObj;
    }

    public function nyetirUgalUgalan(){
         if(!$this->mobilObj->isEngineStart()){
              $this->mobilObj->engineStart();
         }
         $this->mobilObj->setPerseneling(1);
         $this->mobilObj->gas(5);
         try{
             $this->mobilObj->setir(-1);
             $this->mobilObj->setir(1);
             $this->mobilObj->setir(-1);
             $this->mobilObj->setir(1);
         }catch(Exception $e){
             echo $e->getMessage();
             print_r($e->getTrace());
         }
         $this->mobilObj->rem(5);
         $this->mobilObj->setPerseneling(0);
         $this->mobilObj->engineStop();
    }

}
Ok gue harap semua tahu apa yang code diatas lakukan. Sedikit informasi, bahwa gue melakukan Type Hinting pada methode setMobil. Untuk jelasnya bisa baca dokumentasinya PHP. Type Hinting berguna sekali, membuat code jadi lebih robust dan stabil. Di tempat lain (biasanya istilahnya controller atau main methode kalo di java, di php katakanlah index.php) ada code yang membuat objekt sang pengemudi, membuat objekt mobil dan memberikannya kepada si pengemudi. Kira-kira begini:
$mobilObj = new Mobil();
$mobilObj->setWarna('orange');
$mobilObj->setBrand('Metro Mini');
$pengemudi = new Pengemudi();
$pengemudi->setMobil($mobilObj);
$pengemudi->nyetirUgalUgalan();
Di kemudian hari, kelas gue sudah mulai banyak dipakai di dalam system. Akan tetapi system tersebut butuh satu mobil lagi. Bedanya sekarang, mobil tersebut harus pasang AC, punya knalpot remus ^^ dan tangki bensin nya bisa isi 100 liter. Lebih canggihnya lagi, mobil tersebut gak cuma bisa belok ke kiri, kanan dan lurus, tapi sekarang bisa keatas dan kebawah (bisa terbang ^^. Ga usah protes. Suka-suka gue. Aplikasi gue kok :P). Mobil tersebut akan digunakan oleh pengemudi baru yang jauh lebih berpengalaman. Istilahnya sekarang "pilot". Mobil-mobil yang sudah dimiliki oleh pengemudi lainnya tidak boleh diganggu-gugat. Mereka sudah senang dengan keadaannya tersebut (prinsip never touch a running system). So apa yang gue lakukan? Gue akan bikin satu class mobil baru yang mewarisi fungsi-fungsi class mobil gue yang lama. Yang suka pake bahasa inggris, pasti kenal yang istilahnya inheritance.^^

So mari, coba kita bikin.
class MobilBaru extends Mobil{

  public function setir($arah){
       switch($arah){
            case -1:
                $this->terbang('kiri');
                break;
            case 0:
                $this->terbang('kanan');
                break;
           case 1:
                $this->terbang('atas');
                break;
           case -2:
                 $this->terbang('bawah');
                 break;
           case 2:
                 $this->terbang('lurus');
                 break;
           default:
                throw new Exception('hanya bisa menerima nilai -2,
-1, 0, 1 atau 2');
       }
  }

  protected function tangkiBensin(){
        echo "tangki bensin muat 100 liter";
  }

  protected function knalpot(){
       echo "knalpot remus";
  }

 protected function airConditioned(){
     echo "pake AC Central Daikin ^^";
 }

 private function terbang($arah){
     //TODO: implement how to fly
 }

}
So mari kita cerna pelan-pelan. Code gue sekarang agak lebih sedikit. Kenapa? Karena sebagian besar fungsi, sudah di implementasikan di parent class. Bahkan gue ga nulis constructor. Yang gue lakukan adalah memodifikasi methode sesuai tugas gue. Perhatikan bahwa gue meng-override methode setir, tangkiBensin dan knalpot. Perlu diperhatikan disini, apabila gue melakukan override pada methode setir, maka fungsi setir disini tidak dapat membaca private method yang ada di kelas mobil. Ini bukan sesuatu yang buruk, tapi justru baik. Karena private methode tersebut diperuntukkan khusus pada kelas Mobil.

Sekarang kita membuat kelas MobilBaru dimana behaviornya berbeda sekali (bisa terbang ^^) meskipun masih masuk definisi mobil. Maka dari itu, bagaimana setir di implementasikan supaya mobil tersebut bisa bersikap sesuai yang diharapkan (terbang ^^), ditulis method baru, dan diberikan visibility private.

Disini sebenarnya, sekali lagi bebas saja mau berapa methodenya, namanya apa, dsbnya. Sebagai contoh gue bikin satu methode namanya terbang. Sekarang bagaimana bentuk kelas pengemudinya? yaa kira2 begini lah:
class Pilot{

      //member variabel pilot
      private $mobilObj;

     //lots of code implementing pilot

    public function setMobil(MobilBaru $mobilObj){
        $this->mobilObj = $mobilObj;
    }

    public function terbangUgalUgalan(){
         if(!$this->mobilObj->isEngineStart()){
              $this->mobilObj->engineStart();
         }
         $this->mobilObj->setPerseneling(1);
         $this->mobilObj->gas(5);
         try{
             $this->mobilObj->setir(1);
             $this->mobilObj->setir(-2);
             $this->mobilObj->setir(1);
             $this->mobilObj->setir(-2);
         }catch(Exception $e){
             echo $e->getMessage();
             print_r($e->getTrace());
         }
         $this->mobilObj->rem(5);
         $this->mobilObj->setPerseneling(0);
         $this->mobilObj->engineStop();
    }

}
So sekarang agak ribet. Mohon dicerna pelan-pelan. Perhatikan pada setMobil, type hint gue sekarang MobilBaru, artinya, object yang dioper pada argument cuma boleh berupa instanz dari kelas MobilBaru. Ini berguna sekali. Coba bayangkan kalau type hint nya tetap Mobil. Dia bakal sama sekali gak protes, ketika tuh pilot kita kasih objekt MobilBaru karena MobilBaru adalah sebuah mobil ^^ (inherit class Mobil). Berikutnya, waktu kita kasih dia object Mobil, pada bagian setMobil(), dia masih gak protes, karena Mobil adalah sebuah mobil (astaga...). Tapi giliran dia mo terbangUgalUgalan(). Mobilnya bakal protes. kenapa? karena dia gak mengenal argument -2 ^^. Kalo masih bingung. mari kita lihat code yang menggunakan kelas ini :
//Mari kita anggap methode setMobil pada kelas Pilot 
//bentuknya begini:
//   public function setMobil(Mobil $mobilObj){
//        $this->mobilObj = $mobilObj;
//    }

//Perhatikan type hint nya Mobil.

//so sekarang main method atau controller atau index.php nya

$mobilObj = new MobilBaru();
$mobilObj->setWarna('orange');
$mobilObj->setBrand('Metro Mini');
$pengemudi = new Pilot();
$pengemudi->setMobil($mobilObj);
$pengemudi-> terbangUgalUgalan(); // << No error

$mobilObj = new Mobil();
$mobilObj->setWarna('orange');
$mobilObj->setBrand('Metro Mini');
$pengemudi = new Pilot();
$pengemudi->setMobil($mobilObj);
$pengemudi-> terbangUgalUgalan(); // << error: 
                                  // hanya bisa menerima
                                  // nilai -1, 0 atau 1
So gue sudah selesai. panjang juga ya? ^^ Sekali lagi, gue harap semoga bermanfaat.

2 comments:

dekoz said...

Salam kenal sebelumnya
Makasih mas tutorialnya, jadi lumayan mengerti sekarang :)
Penjelasannya bagus

Zlumber Jay said...

mengenai penggunaan print_r dalam :
print_r($e->getTrace());
Saya belum memahami tujuannya.


}catch(Exception $e){
echo $e->getMessage();
print_r($e->getTrace());
}
#thank's sebelumnya