Cat "IS NOT" a Mammal: Menggugat Pengajaran OOP by Inheritance, Melirik IOP
Artikel ini hanya untuk mengetuk pikiran saja, bukan menyalahkan pengajaran OOP selama ini :) Heck, mungkin Carl von Linné (Carolus Linnaeus 1707-1778) tidak melihat sistem taxonomy-nya bisa bermasalah 230 tahun kemudian...
Di buku2 textbook OOP, atau di kelas OOP 101, sering kita lihat inheritance chain/hierarchy seperti ini:
Gambar 1: Dog Inheritance
Inheritance hierarchy diatas disebabkan karena paradigma "IS-A". Terrier is a Dog --> Dog is a Mammal.
Dan paradigma "IS-A" disebabkan karena taxonomy di real-world... Gambar 1 diatas adalah versi ringkasnya taxonomy Systema Naturae dari Carolus Linnaeus. Versi strict-nya adalah begini:
Domain: Eukaryota
Kingdom: Animalia
Phylum: Chordata
Class: Mammalia
Order: Carnivora
Family: Canidae
Genus: Canis
Species: C. lupus
Subspecies: C.I. familiaris
Jadi "Dog" adalah Canis lupus familiaris. Nah problemnya adalah dengan majunya teknologi DNA, muncul keinginan beberapa scientist untuk me-reklasifikasi beberapa species. Re-klasifikasi species atau penambahan species baru bisa jadi menyebabkan perubahan di Rank. Rank dilihat dari suffix belakang seperti "ae" di belakang "Hominidae".
Bayangkan perubahan suatu Rank, berarti perubahan semua taxonomy yg se-level dan yg di-bawahnya. Mirip dengan software development kan? Perubahan satu kode, malah jadi chain reaction perubahan di semua code, yg akhirnya muncul pikiran untuk "start from scratch".
Untuk penjelasannya, silakan baca "Attacks on Taxonomy" http://www.americanscientist.org/issues/pub/2005/7/attacks-on-taxonomy dan lihat Penjelasan Diagram ini
Btw, solusi dari problem "Renaming Rank" diatas apa? Model taxonomy baru yaitu PhyloCode, dimana reklasifikasi suatu species atau penambahan species tidak menganggu taxonomy di sekitarnya.
OOP dengan Inheritance adalah Linnean, OOP dengan Interface adalah PhyloCode.... mau tahu? Mari lanjut...
- Apakah Anda percaya "Objects should represent behaviour?"
- Apakah Anda percaya "Service-Oriented Architecture?"
Jika demikian, mungkin saatnya Anda melihat objects berdasarkan "services" yg ditawarkan oleh object tersebut. Dilihat dari Gambar 1 diatas, mungkin kita bisa tarik beberapa services yg dibutuhkan aplikasi kita:
- CarryCargo: Horse
- Race: Dog, Horse
- GiveMilk: Cow
- Entertain: Cat, Dog
Gambar 2: Interface Animals
interface ICargoCarriers
{
Load()
GetCapacity()
}
interface IRacers
{
RunFast()
RunLong()
}
interface IMilkGivers
{
GetMilk()
}
interface IEntertainers
{
StandOnTwoLegs()
JumpOverChair()
}
Nah, sekarang kita bisa cross-hierarchy deh... jadi begini ntar implementasinya:
class Horse implements ICarryCargo, IRacers
class Dog implements IRacers, IEntertainers
class Cat implements IEntertainers
class Cow implements IMilkGivers
Dan interface-oriented design ini bisa di-mix dengan inheritance classic, koq, misal:
abstract class Dog implements IRacers, IEntertainers
{
// Default implementations
virtual RunFast() {
run between 5-10km/h
}
virtual RunLong() {
run between 1-3km/h
}
virtual StandOnTwoLegs() {
stand
}
virtual JumpOverChair() {
jump
}
}
class Terrier inherits Dog
{
// short legs, not fast
// inherit default implementation
// of RunFast & RunLong
override StandOnTwoLegs() {
// can't, too short
fall and roll on the ground
}
override JumpOverChair() {
jump and hit the chair
}
}
class Rottweiler inherits Dog
{
override RunFast() {
run between 30-50km/h
}
override RunLong() {
run between 10-15km/h
}
// Use default implementations
// of StandOnTwoLegs
// and JumpOverChair
}
Jika nanti ternyata ada type Dog yg baru, misalkan doggie-nya Paris Hilton yg nggak bisa buat balapan, cuman jalan biasa... tinggal bikin class baru yg implement interface yg diinginkan:
interface IEntertainers_v2 inherits IEntertainers
{
WalkWithOwner()
}
interface IPet
{
GetOwnerName()
GetOwnerAddress()
}
class PetDog implements IPet, IEntertainers_v2
{
string name, address;
GetOwnerName() {
return name;
}
GetOwnerAddress() {
return address;
}
StandOnTwoLegs() {
stand
}
JumpOverChair() {
jump
}
WalkWithOwner() {
follow owner from behind,
leftside or rightside
}
}
Di atas kelihatan juga gimana cara versioning Interfaces... buat yg sering main2 bikin Add-in di Visual Studio, pernah ngeliat kan waktu Visual Studio 2003 masih pake DTE interface, terus pas Visual Studio 2005, interface-nya jadi DTE2.
Nah, kalo udah Interface-Oriented begini, code-nya jangan depend to Class, tapi depend to Interface, sesuai dengan isinya buku Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1995) http://www.artima.com/lejava/articles/designprinciplesP.html
Excerpt:
Bill Venners: In the introduction of the GoF book, you mention two principles of reusable object-oriented design. The first principle is: "Program to an interface, not an implementation." What's that really mean, and why do it?
Erich Gamma: This principle is really about dependency relationships which have to be carefully managed in a large app. It's easy to add a dependency on a class. It's almost too easy; just add an import statement and modern Java development tools like Eclipse even write this statement for you. Interestingly the inverse isn't that easy and getting rid of an unwanted dependency can be real refactoring work or even worse, block you from reusing the code in another context. For this reason you have to develop with open eyes when it comes to introducing dependencies. This principle tells us that depending on an interface is often beneficial.
// Jangan nulis code kayak gini lagi
public void EntertainOwner(Dog dog)
{
dog.StandOnTwoLegs();
dog.JumpOverChair();
}
public void RaceForOwner(Dog dog)
{
if (! last_lap) {
dog.RunLong();
return;
}
dog.RunFast();
}
// Ntar nggak bisa di re-use, kebanyakan copy-paste code
// buat fungsionalitas yg sama
Dog myDog = new Dog();
Horse myHorse = new Horse();
RaceForOwner(myDog); // ini jalan
RaceForOwner(myHorse); // Class Exception error
// Tapi kayak gini
public void EntertainOwner(IEntertainers e)
{
e.StandOnTwoLegs();
e.JumpOverChair();
}
public void RaceForOwner(IRacer r)
{
if (! last_lap) {
r.RunLong();
return;
}
r.RunFast();
}
// Sekarang bisa di re-use, nggak usah copy-paste
// untuk fungsionalitas yg sama
Dog myDog = new Dog();
Horse myHorse = new Horse();
RaceForOwner(myDog); // ini jalan
RaceForOwner(myHorse); // ini juga jalan
Dengan begitu, kita bisa me-reuse method EntertainOwner dan RaceForOwner dan menjadikannya DLL terpisah yg bisa dipanggil dari kode kode baru.
Contoh lainnya adalah Employee...
SalariedEmployee dibayar per bulan, IS AN Employee.
CommissionEmployee dibayar per proyek, IS AN Employee.
HourlyEmployee dibayar per jam, IS AN Employee.
Tapi di jaman yg modern sekarang, role seperti diatas sudah kabur... contohnya Zeddy, yg sekarang punya gaji per bulan, tapi juga ngerjain proyek, dan juga provide consulting per hari. Jadi gimana donk ngitung payroll saya?
Ganti inheritance model ini dengan role-based interface:
interface IPaySalary
{
GetSalary()
}
interface IProject
{
GetProjectName()
GetProjectFee()
}
interface IPayProject
{
AddProject()
GetTotalProjectsFee()
}
interface IPayDailyConsulting
{
AddDays()
GetTotalConsultingFee()
}
class Entrepreneur implements IPaySalary, IPayProject, IPayDailyConsulting
{
decimal GetSalary() { return _salary; }
void AddProject(IProject project) {
_projects.Add(project);
}
decimal GetTotalProjectsFee() {
decimal total = 0;
foreach(IProject p in _projects)
total += p.GetProjectFee();
return total;
}
void AddDays(int i) { _days += i }
decimal GetTotalConsultingFee() {
return _days * DAILY_RATE;
}
}
Entrepreneur zeddy = new Entrepreneur();
Console.Write("Zeddy's Total Income = {0}" ,
zeddy.GetSalary() +
zeddy.GetTotalProjectsFee() +
zeddy.GetTotalConsultingFee()
);
Jadi begitulah, mudah-mudahan di pelajaran Object-Oriented Programming di manapun (kursus, kuliah, inhouse training), sisipkan materi Interface-Oriented Programming, dan hati-hati berkata "xxx IS-A yyy type". Mungkin benar dalam real-world, tapi jangan salah mengambil abstraksi...
Akhir kata, jangan terlalu Interface-Oriented juga, nanti malah terjebak bikin terlalu banyak interface, sehingga lupa coding implementasi-nya :)
Semoga membantu merubah paradigma towards better design & code.