11. Antarmuka, Struktur,
dan Enumerasi
Antarmuka
Dalam
pemrograman berorientasi objek, kadangkala berguna untuk mendefinisikan apa
yang harus dilakukan sebuah kelas, tetapi tidak bagaimana kelas itu
melakukannya. Anda telah melihat contohnya: metode abstrak. Metode abstrak
mendeklarasikan tipe nilai balik dan sidik sebuah metode, tetapi tidak
menyediakan implementasinya. Kelas terderivasi harus menyediakan implementasi
dari setiap metode abstrak yang didefinisikan oleh kelas basisnya. Oleh karena
itu, metode abstrak hanya menspesifikasi antarmuka terhadap metode, bukan
implementasi metode. Meskipun kelas dan metode abstrak berguna dan aplikatif,
dimungkinkan untuk mengembangkan konsep ini selangkah lebih maju. Dalam C#,
Anda dapat memisahkan antarmuka kelas dari implementasinya menggunakan
katakunci interface.
Antarmuka secara
sintaks sama dengan kelas abstrak. Namun, dalam antarmuka, tidak metode yang
memiliki tubuh. Jadi, antarmuka sama sekali tidak menyediakan implementasi
apapun. Yang dispesifikasi hanya apa yang perlu dilakukan, bukan bagaimana
melakukannya. Begitu antarmuka didefinisikan, sembarang kelas dapat
mengimplementasikannya. Selain itu, sebuah kelas dapat mengimplementasikan
sebanyak mungkin antarmuka yang diinginkan.
Untuk
mengimplementasikan sebuah antarmuka, kelas harus menyediakan tubuh
(implementasi) untuk semua metode yang dideskripsikan oleh antarmuka. Setiap
kelas bebas dalam menentukan detil dari tiap implementasi. Jadi, dua kelas bisa
saja mengimplementasikan antarmuka yang sama dengan cara yang berbeda. Dengan
menyediakan antarmuka, C# membolehkan Anda untuk memanfaatkan “satu antarmuka,
banyak metode”, yang merupakan aspek dari polimorfisme.
Antarmuka
dideklarasikan menggunakan katakunci interface.
Berikut merupakan bentuk tersimplifikasi dari sebuah deklarasi antarmuka:
interface nama {
tipe-nilaibalik nama-metode1(daftar-param);
tipe-nilaibalik nama-metode2(daftar-param);
// ...
tipe-nilaibalik nama-metodeN(daftar-param);
}
Nama antarmuka
dispesifikasi oleh nama. Metode dideklarasikan hanya menggunakan tipe nilai balik
dan sidiknya, yang merupakan metode abstrak. Seperti dijelaskan sebelumnya, di
dalam sebuah antarmuka, tidak ada metode yang memiliki implementasi. Jadi,
setiap kelas yang menyertakan sebuah antarmuka harus mengimplementasikan semua
metode abstrak yang ada. Di dalam antarmuka, metode secara implisit public, dan tidak ada penspesifikasi
akses eksplisit yang diijinkan.
Berikut adalah
sebuah contoh antarmuka, yang menspesifikasi antarmuka terhadap sebuah kelas
yang membangkitkan sederet angka.
public interface IDeret {
int
GetBerikut(); // menghasilkan angka berikutnya di dalam deret
void Reset();
// memulai kembali
void SetAwal(int x); // menetapkan nilai awal
}
Nama dari
antarmuka ini adalah IDeret.
Meskipun prefiks I tidak diperlukan,
banyak programer membubuhi antarmuka dan I
untuk membedakannya dari kelas. IDeret
dideklarasikan public, sehingga ia
dapat diimplementasikan oleh sembarang kelas di dalam program.
Mengimplementasi
Antarmuka
Begitu sebuah
antarmuka didefinisikan, satu atau lebih kelas dapat mengimplementasikan
antarmuka itu. Untuk mengimplementasikan sebuah antarmuka, nama antarmuka
ditempatkan setelah nama kelas, sama seperti menspesifikasi kelas basis. Bentuk
umum dari sebuah kelas yang mengimplementasikan antarmuka ditunjukkan di sini:
class nama-kelas
: nama-antarmuka {
// tubuh-kelas
}
Nama antarmuka
yang sedang diimplementasikan dispesifikasi di dalam nama-antarmuka. Ketika sebuah
kelas mengimplementasikan antarmuka, ia harus mengimplementasikan keseluruhan
antarmuka. Kelas tersebut tidak bisa memilih bagian mana yang
diimplementasikan, misalnya.
Sebuah kelas
dapat mengimplementasikan lebih dari satu antarmuka. Ketika sebuah kelas
mengimplementasikan lebih dari satu antarmuka, setiap antarmuka dispesifikasi
dan dipisahkan dengan koma. Sebuah kelas dapat mewarisi suatu kelas basis dan
juga mengimplementasikan satu atau lebih antarmuka. Pada kasus ini, nama kelas
basis harus ditempatkan lebih dahulu.
Metode yang
mengimplementasikan sebuah antarmuka harus dideklarasikan public. Alasannya adalah bahwa metode itu secara implisit publik di
dalam antarmuka, sehingga implementasinya juga harus publik. Selain itu, tipe
nilai balik dan sidik dari metode pengimplementasi antarmuka harus sama dengan
tipe nilai balik dan sidik yang dispesifikasi pada definisi antarmuka.
Berikut adalah
sebuah contoh yang mengimplementasikan antarmuka IDeret yang ditunjukkan sebelumnya. Program menciptakan sebuah
kelas, DenganDua, yang membangkitkan
sederet angka, masing-masing angka dua kali lebih besar dari angka sebelumnya.
// Mengimplementasikan
IDeret.
class DenganDua : IDeret
{
int
awal;
int
nil;
public
DenganDua()
{
awal = 0;
nil = 0;
}
public int GetBerikut()
{
nil += 2;
return
nil;
}
public
void Reset()
{
nil = awal;
}
public
void SetAwal(int x)
{
awal = x;
nil = awal;
}
}
Seperti yang
Anda lihat, DenganDua
mengimplementasikan semua metode yang didefinisikan oleh IDeret. Seperti dijelaskan sebelumnya, ini diperlukan karena kelas
tidak bisa mengimplementasikan antarmuka secara parsial.
Berikut adalah
sebuah kelas yang mendemonstrasikan DenganDua:
// Demonstrasi antarmuka
IDeret.
using System;
class DemoDeret {
static
void Main() {
DenganDua ob = new DenganDua();
for(int i=0; i < 5; i++)
Console.WriteLine("Nilai berikutnya adalah " +
ob.GetBerikut());
Console.WriteLine("\nMemulai kembali");
ob.Reset();
for(int i=0; i < 5; i++)
Console.WriteLine("Nilai berikutnya adalah " +
ob.GetBerikut());
Console.WriteLine("\nMulai dari 100");
ob.SetAwal(100);
for
(int i = 0; i < 5; i++)
Console.WriteLine("Nilai berikutnya adalah " +
ob.GetBerikut());
}
}
Untuk
mengkompilasi DemoDeret, Anda harus
menyertakan tiga file, yaitu IDeret,
DenganDua, dan DemoDeret. Kompiler akan secara otomatis mengkompilasi ketiga file
untuk menciptakan file executable.
Sebagai contoh, perintah berikut akan mengkompilasi program tersebut:
>csc DemoDeret.cs IDeret.cs DenganDua.cs
Jika Anda
menggunakan VISUAL Studio IDE, Anda hanya perlu menempatkan ketiga file
tersebut di dalam C# project. Satu hal penting lain: Adalah legal untuk
menempatkan ketiga kelas di dalam file yang sama.
Keluaran program
ini ditampilkan di sini:
Nilai berikutnya adalah 2
Nilai berikutnya adalah 4
Nilai berikutnya adalah 6
Nilai berikutnya adalah 8
Nilai berikutnya adalah 10
Memulai kembali
Nilai berikutnya adalah 2
Nilai berikutnya adalah 4
Nilai berikutnya adalah 6
Nilai berikutnya adalah 8
Nilai berikutnya adalah 10
Mulai dari 100
Nilai berikutnya adalah 102
Nilai berikutnya adalah 104
Nilai berikutnya adalah 106
Nilai berikutnya adalah 108
Nilai berikutnya adalah 110
Adalah hal yang
dibolehkan dan umum dijumpai fakta bahwa kelas dapat mengimplementasikan
antarmuka untuk mendefinisikan beberapa anggota tambahan. Sebagai contoh, versi
DenganDua berikut menambahkan metode GetSebelum(),
yang menghasilkan nilai sebelumnya:
// Mengimplementasikan
IDeret dan menambahkan GetSebelum().
class DenganDua : IDeret
{
int
awal;
int
nil;
int
sebelum;
public
DenganDua()
{
awal = 0;
nil = 0;
sebelum = -2;
}
public
int GetBerikut()
{
sebelum = nil;
nil += 2;
return
nil;
}
public
void Reset()
{
nil = awal;
sebelum = awal - 2;
}
public
void SetAwal(int x)
{
awal = x;
nil = awal;
sebelum = nil - 2;
}
// Sebuah metode yang tidak dispesifikasi
oleh IDeret.
public
int GetSebelum()
{
return
sebelum;
}
}
Perhatikan bahwa
penambahan GetSebelum() memerlukan
perubahan pada antarmuka IDeret.
Namun, karena antarmuka tidak berubah, maka perubahan tersebut tidak merusak
program. Inilah salah satu keuntungan dari antarmuka.
Seperti
dijelaskan, sembarang kelas dapat mengimplementasikan antarmuka. Sebagai
contoh, berikut adalah sebuah kelas, Prima,
yang membangkitkan sederet angka prima. Perhatikan bahwa implementasi dari IDeret secara fundamental berbeda
dengan yang disediakan oleh DenganDua.
// Menggunakan IDeret untuk
mengimplementasikan deret prima.
class Prima : IDeret {
int
awal;
int
nil;
public
Prima() {
awal = 2;
nil = 2;
}
public
int GetBerikut() {
int
i, j;
bool
apaprima;
nil++;
for(i
= nil; i < 1000000; i++) {
apaprima = true;
for(j
= 2; j <= i/j; j++) {
if((i%j)==0)
{
apaprima = false;
break;
}
}
if(apaprima)
{
nil = i;
break;
}
}
return
nil;
}
public
void Reset() {
nil = awal;
}
public
void SetAwal(int x) {
awal = x;
nil = awal;
}
}
Poin kunci di
sini adalah bahwa meskipun DenganDua
dan Prima membangkitkan deret angka
yang sama sekali tidak berelasi, keduanya mengimplementasikan IDeret. Seperti dijelaskan sebelumnya,
sebuah antarmuka tidak mengatakan apapun tentang implementasi, sehingga setiap
kelas bebas dalam mengimplementasikan antarmuka tersebut sesuai dengan
kebutuhannya.
Menggunakan
Referensi Antarmuka
Anda mungkin
terkejut bila mengetahui bahwa Anda dapat mendeklarasikan sebuah variabel
referensi bertipe antarmuka. Dengan kata lain, Anda dapat sebuah variabel
referensi antarmuka. Variabel semacam itu dapat menunjuk ke sembarang objek
yang mengimplementasikan antarmukanya. Ketika Anda memanggil sebuah metode pada
suatu objek melalui referensi objek, versi metode yang diimplementasikan oleh
objek tersebutlah yang dieksekusi. Proses ini sama dengan ketika menggunakan
referensi kelas basis untuk mengakses objek kelas terderivasi, seperti
dijelaskan pada Bab 10.
Contoh berikut
mengilustrasikan kegunaan referensi antarmuka. Program menggunakan variabel
referensi antarmuka yang sama untuk memanggil beberapa metode pada objek kedua DenganDua dan Prima. Agar lebih jelas, program digabungkan ke dalam satu file.
// Demonstrasi referensi antarmuka.
using System;
// mendefinisikan antarmuka
public interface IDeret
{
int GetBerikut(); // menghasilkan angka
berikutnya di dalam deret
void Reset(); // memulai kembali
void SetAwal(int x); // menetapkan nilai awal
}
// Menggunakan IDeret untuk mengimplementasikan sebuah deret
// dimana setiap nilai dua kali lebih besar dari nilai
sebelumnya.
class DenganDua : IDeret
{
int awal;
int nil;
public DenganDua()
{
awal = 0;
nil = 0;
}
public int GetBerikut()
{
nil += 2;
return nil;
}
public void Reset()
{
nil = awal;
}
public void SetAwal(int x)
{
awal = x;
nil = awal;
}
}
// Menggunakan IDeret untuk mengimplementasikan deret prima.
class Prima : IDeret
{
int awal;
int nil;
public Prima()
{
awal = 2;
nil = 2;
}
public int GetBerikut()
{
int i, j;
bool apaprima;
nil++;
for (i = nil; i < 1000000; i++)
{
apaprima = true;
for (j = 2; j <= i / j; j++)
{
if ((i % j) == 0)
{
apaprima = false;
break;
}
}
if (apaprima)
{
nil =
i;
break;
}
}
return nil;
}
public void Reset()
{
nil = awal;
}
public void SetAwal(int x)
{
awal = x;
nil = awal;
}
}
class DemoDeret2
{
static void Main()
{
DenganDua duaOb
= new DenganDua();
Prima primaOb =
new Prima();
IDeret ob;
for (int i = 0; i < 5; i++)
{
ob = duaOb;
Console.WriteLine("Nilai DenganDua berikutnya adalah
" +
ob.GetBerikut());
ob =
primaOb;
Console.WriteLine("Nilai Prima berikutnya adalah " +
ob.GetBerikut());
}
}
}
Keluaran program
ditampilkan di sini:
Nilai DenganDua berikutnya adalah 2
Nilai Prima berikutnya adalah 3
Nilai DenganDua berikutnya adalah 4
Nilai Prima berikutnya adalah 5
Nilai DenganDua berikutnya adalah 6
Nilai Prima berikutnya adalah 7
Nilai DenganDua berikutnya adalah 8
Nilai Prima berikutnya adalah 11
Nilai DenganDua berikutnya adalah 10
Nilai Prima berikutnya adalah 13
Dalam Main(), ob dideklarasikan sebagai sebuah referensi yang menunjuk ke
antarmuka IDeret. Ini berarti bahwa
ia dapat dipakai untuk menyimpan referensi yang menunjuk ke sembarang objek
yang mengimplementasikan IDeret.
Pada kasus ini, ia digunakan untuk menunjuk ke duaOb dan primaOb, yang
merupakan objek bertipe DenganDua
dan objek bertipe Prima (keduanya
mengimplementasikan IDeret).
Properti
Antarmuka
Seperti metode,
properti, yang dispesifikasi di dalam antarmuka, tidak memiliki tubuh. Berikut
adalah bentuk umum dari spesifikasi properti:
// properti antarmuka
tipe nama {
get;
set;
}
Tentu saja,
hanya get atau set yang ada pada properti read-only atau write-only. Dapat dilihat
bahwa tidak ada pemodifikasi akses yang diijinkan pada aksesor ketika sebuah
properti dideklarasikan di dalam interface. Jadi, aksesor
set, misalnya, tidak dapat dispesifikasi sebagai private di dalam sebuah antarmuka.
Berikut adalah
hasil penulisan-ulang dari antarmuka IDeret
dan kelas DenganDua yang menggunakan
sebuah properti, Berikutnya, untuk
mendapatkan dan menetapkan elemen berikutnya di dalam deret:
//Menggunakan properti di dalam antarmuka
using System;
public interface IDeret
{
// Sebuah properti
antarmuka.
int Berikutnya
{
get; // menghasilkan angka berikutnya
di dalam deret
set; // menetapkan angka berikutnya
}
}
// Mengimplementasikan IDeret
class DenganDua : IDeret
{
int nil;
public DenganDua()
{
nil = 0;
}
// Mendapatkan atau
menetapkan nilai
public int Berikutnya
{
get
{
nil += 2;
return nil;
}
set
{
nil = value;
}
}
}
class DemoDeret3
{
static void Main()
{
DenganDua ob = new DenganDua();
// Mengakses
deret melalui properti.
for (int i = 0; i < 5; i++)
Console.WriteLine("Nilai berikutnya adalah " +
ob.Berikutnya);
Console.WriteLine("\nMulai dari 21");
ob.Berikutnya =
21;
for (int i = 0; i < 5; i++)
Console.WriteLine("Nilai berikutnya adalah " +
ob.Berikutnya);
}
}
Keluaran program
ditampilkan di sini:
Nilai berikutnya adalah 2
Nilai berikutnya adalah 4
Nilai berikutnya adalah 6
Nilai berikutnya adalah 8
Nilai berikutnya adalah 10
Mulai dari 21
Nilai berikutnya adalah 23
Nilai berikutnya adalah 25
Nilai berikutnya adalah 27
Nilai berikutnya adalah 29
Nilai berikutnya adalah 31
Indekser
Antarmuka
Sebuah antarmuka
dapat menspesifikasi indekser. Indekser satu dimensi sederhana yang
dideklarasikan di dalam sebuah antarmuka mempunyai bentuk umum:
// indekser antarmuka
tipe-elemen this[int indeks] {
get;
set;
}
Seperti
sebelumnya, hanya get atau set yang ada untuk indekser read-only
atau write-only. Selain itu, tidak ada pemodifikasi akses yang diijinkan pada
aksesor ketika sebuah indekser dideklarasikan di dalam interface.
Berikut adalah
versi lain dari IDeret yang
menambahkan sebuah indekser read-only yang menghasilkan elemen ke-i pada deret sebagai nilai balik.
//Menggunakan indekser di dalam antarmuka
using System;
public interface IDeret
{
// Sebuah properti
antarmuka.
int Berikutnya
{
get; // menghasilkan angka berikutnya
di dalam deret
set; // menetapkan angka berikutnya
}
// Sebuah indekser
antarmuka.
int this[int indeks]
{
get; // menghasilkan angka
terspesifikasi di dalam deret
}
}
// Mengimplementasikan IDeret
class DenganDua : IDeret
{
int nil;
public DenganDua()
{
nil = 0;
}
// Mendapatkan atau
menetapkan nilai
public int Berikutnya
{
get
{
nil += 2;
return nil;
}
set
{
nil = value;
}
}
// Mendapatkan
sebuah nilai menggunakan indekser.
public int this[int indeks]
{
get
{
nil = 0;
for (int i = 0; i < indeks; i++)
nil +=
2;
return nil;
}
}
}
class DemoDeret4
{
static void Main()
{
DenganDua ob = new DenganDua();
// Mengakses
deret melalui properti.
for (int i = 0; i < 5; i++)
Console.WriteLine("Nilai berikutnya adalah " +
ob.Berikutnya);
Console.WriteLine("\nMulai dari 21");
ob.Berikutnya =
21;
for (int i = 0; i < 5; i++)
Console.WriteLine("Nilai berikutnya adalah " +
ob.Berikutnya);
Console.WriteLine("\nDireset menjadi 0");
ob.Berikutnya =
0;
// Mengakses
deret melalui indekser.
for (int i = 0; i < 5; i++)
Console.WriteLine("Nilai berikutnya adalah " +
ob[i]);
}
}
Keluaran program
ditampilkan di sini:
Nilai berikutnya adalah 2
Nilai berikutnya adalah 4
Nilai berikutnya adalah 6
Nilai berikutnya adalah 8
Nilai berikutnya adalah 10
Mulai dari 21
Nilai berikutnya adalah 23
Nilai berikutnya adalah 25
Nilai berikutnya adalah 27
Nilai berikutnya adalah 29
Nilai berikutnya adalah 31
Direset menjadi 0
Nilai berikutnya adalah 0
Nilai berikutnya adalah 2
Nilai berikutnya adalah 4
Nilai berikutnya adalah 6
Nilai berikutnya adalah 8
Antarmuka
Dapat Diwariskan
Satu antarmuka
dapat mewarisi antarmuka lain. Sintaksnya sama dengan ketika mewarisi kelas.
Ketika sebuah kelas mengimplementasikan antarmuka yang mewarisi antarmuka lain,
ia harus menyediakan implementasi untuk semua anggota yang didefinisikan di
dalam rantai pewarisan antarmuka. Berikut disajikan sebuah contoh untuk
mengilustrasikan gagasan ini:
// Satu antarmuka mewarisi
antarmuka lain.
using System;
public interface IA {
void
Met1();
void
Met2();
}
// IB sekarang menyertakan
Met1() dan Met2() -- dan menambahkan Met3().
public interface IB : IA {
void
Met3();
}
// Kelas ini harus mengimplementasikan
semua IA dan IB.
class KelasKu : IB {
public
void Met1() {
Console.WriteLine("Mengimplementasikan Met1().");
}
public
void Met2() {
Console.WriteLine("Mengimplementasikan Met2().");
}
public
void Met3() {
Console.WriteLine("Mengimplementasikan Met3().");
}
}
class WarisAntarmuka
{
static
void Main()
{
KelasKu ob = new KelasKu();
ob.Met1();
ob.Met2();
ob.Met3();
}
}
Keluaran program
ditampilkan di sini:
Mengimplementasikan Met1().
Mengimplementasikan Met2().
Mengimplementasikan Met3().
Implementasi
Eksplisit
Ketika
mengimplementasikan anggota sebuah antarmuka, dimungkinkan untuk secara utuh
menyebutkan nama dengan nama antarmukanya. Hal ini menyebabkan implementasi
anggota antarmuka eksplisit atau implementasi eksplisit. Sebagai contoh,
diberikan
interface IIFKu {
int MetKu(int x);
}
maka adalah
legal untuk mengimplementasikan IIFku
seperti ditunjukkan di sini:
class KelasKu
: IIFKu {
int IIFKu.MetKu(int x) {
return x / 3;
}
}
Seperti yang
dapat Anda lihat, ketika anggota MetKu()
dari antarmuka IIFKu
diimplementasikan, nama penuhnya, termasuk nama antarmukanya, dispesifikasi.
Ada dua alasan
mengapa Anda perlu menciptakan implementasi eksplisit dari sebuah metode
antarmuka. Pertama, ketika Anda mengimplementasikan sebuah metode antarmuka
dengan penyebutan nama penuh, Anda menyediakan sebuah implementasi yang tidak
dapat diakses melalui objek kelas. Jadi, ia harus diakses melalui sebuah
referensi antarmuka. Kedua, adalah hal yang memungkinkan bagi sebuah kelas
untuk mengimplementasikan dua antarmuka, keduanya mendeklarasikan metode dengan
nama dan tipe sidik sama. Penyebutan nama dengan antarmukanya menghilangkan
ambiguitas dari situasi ini.
Program berikut
memuat sebuah antarmuka, IGenap,
yang mendefinisikan dua metode, ApaGenap()
dan ApaGanjil(), yeng menentukan
jika sebuah angka genap atau ganjil. Kelas KelasKu
kemudian mengimplementasikan IGenap.
Ketika melakukannya, kelas tersebut mengimplementasikan ApaGenap secara eksplisit.
// Secara eksplisit
mengimplementasikan sebuah anggota antarmuka.
using System;
interface IGenap
{
bool
ApaGanjil(int x);
bool
ApaGenap(int x);
}
class KelasKu : IGenap
{
// Implementasi eksplisit. Perhatikan bahwa
anggota ini
// private secara default.
bool
IGenap.ApaGanjil(int x)
{
if
((x % 2) != 0) return true;
else
return false;
}
// Implementasi normal.
public
bool ApaGenap(int x)
{
IGenap o = this; // referensi antarmuka ke objek pemanggil.
return
!o.ApaGanjil(x);
}
}
class Demo
{
static
void Main()
{
KelasKu ob = new KelasKu();
bool hasil;
hasil = ob.ApaGenap(4);
if
(hasil) Console.WriteLine("4 adalah
genap.");
// hasil = ob.ApaGanjil(4); // Error, ApaGanjil tidak bisa
diakses lewat objek.
// Tetapi, ini OK karena menciptakan
sebuah referensi IGenap yang menunjuk ke
// sebuah objek KelasKu dan kemudian memanggil ApaGanjil()
melalui referensi itu.
IGenap iRef = (IGenap)ob;
hasil = iRef.ApaGanjil(3);
if
(hasil) Console.WriteLine("3 adalah ganjil.");
}
}
Keluaran program
ditampilkan di sini:
4 adalah genap.
3 adalah ganjil.
Karena ApaGanjil() diimplementasikan secara
eksplisit, maka ia tidak dispesifikasi sebagai anggota publik pada KelasKu.
Jadi, ApaGanjil() hanya bisa diakses
melalui sebuah referensi antarmuka. Inilah mengapa ia dipanggil melalui o (yang merupakan sebuah variabel
referensi bertipe IGenap) di dalam
implementasi untuk ApaGenap().
Berikut adalah
sebuah contoh, dimana di dalamnya dua antarmuka diimplementasikan dan kedua
antarmuka tersebut mendeklarasikan sebuah metode bernama Met(). Implementasi eksplisit dipakai untuk menghilangkan
ambiguitas yang melekat pada situasi ini.
// Menggunakan implementasi
eksplisit untuk menghilangkan ambiguitas.
using System;
interface IIFKu_A {
int
Met(int x);
}
interface IIFKu_B {
int
Met(int x);
}
// KelasKu
mengimplementasikan kedua antarmuka.
class KelasKu : IIFKu_A, IIFKu_B
{
// Secara eksplisit mengimplementasikan kedua
Met().
int
IIFKu_A.Met(int x) {
return
x + x;
}
int
IIFKu_B.Met(int x) {
return
x * x;
}
// Call Meth() through an interface
reference.
public
int MetA(int x){
IIFKu_A a_ob;
a_ob = this;
return
a_ob.Met(x); // memanggil IIFKu_A
}
public int MetB(int
x){
IIFKu_B b_ob;
b_ob = this;
return
b_ob.Met(x); // memanggil IIFKu_B
}
}
class DemoIIFKu {
static
void Main() {
KelasKu ob = new KelasKu();
Console.Write("Memanggil IIFKu_A.Met(): ");
Console.WriteLine(ob.MetA(3));
Console.Write("Memanggil IIFKu_B.Met(): ");
Console.WriteLine(ob.MetB(3));
}
}
Keluaran program
ditampilkan di sini:
Memanggil IIFKu_A.Met(): 6
Memanggil IIFKu_B.Met(): 9
Perhatikan bahwa
Met() memiliki sidik sama di dalam kedua IIFKu_A
dan IIFKu_B. Jadi, ketika KelasKu mengimplementasikan kedua
antarmuka tersebut, ia secara eksplisit mengimplemen-tasikan keduanya secara
terpisah, dengan penyebutan nama penuh di dalam proses. Karena satu-satunya
cara sebuah metode yang terimplementasi secara eksplisit dapat dipanggil adalah
melalui sebuah referensi antarmuka, KelasKu
menciptakan dua referensi semacam itu, satu untuk IIFKu_A di dalam MetA() dan satu lagi untuk IIFKu_B di dalam MetB().
Struktur
Seperti Anda
ketahui, kelas adalah tipe referensi. Ini berarti bahwa objek kelas dapat
diakses melalui sebuah referensi. Ini berbeda dari tipe nilai, yang diakses
secara langsung. Namun, kadangkala akan berguna jika bisa mengakses objek
secara langsung, dengan cara seperti tipe nilai. Alasannya adalah karena
efisiensi. Pengaksesan objek kelas melalui sebuah referensi menambah beban pada
tiap pengaksesan. Untuk objek yang sangat kecil, hal ini semakin tidak efisien.
Untuk mengatasi masalah ini, C# menawarkan struktur.
Struktur
dideklarasikan menggunakan katakunci struct
dan secara sintaks sama dengan kelas. Berikut adalah bentuk umum sebuah struct:
struct nama :
antarmuka {
// deklarasi
anggota
}
Nama struktur
dispesifikasi oleh nama.
Struktur tidak
bisa mewarisi struktur lain atau kelas lain atau digunakan sebagai basis untuk struktur atau kelas lain.
(Semua struktur secara implisit mewarisi System.ValueType). Namun, sebuah struktur
dapat mengimplementasikan satu atau lebih antarmuka. Nama antarmuka ditempatkan
setelah nama struktur (jika terdapat lebih dari dua antarmuka, maka dipisahkan
dengan koma). Seperti kelas, anggota struktur mencakup metode, bidang,
indekser, properti, metode operator, dan event. Struktur dapat pula mendefinisikan
konstruktor, tetapi tidak destruktor. Namun, Anda tidak bisa mendefinisikan
sebuah konstruktor default (tanpa parameter) untuk sebuah struktur. Alasannya
adalah bahwa sebuah konstruktor default secara otomatis didefinisikan untuk
semua struktur, dan tidak bisa diubah. Konstruktor default menginisialisasi
bidang sebuah struktur dengan nilai default. Karena struktur tidak mendukung
pewarisan, anggota struktur tidak bisa dispesifikasi sebagai abstract, virtual, atau protected.
Berikut
merupakan sebuah contoh yang menggunakan sebuah struktur untuk menampung
informasi mengenai buku:
// Demonstrasi sebuah
struktur.
using System;
// Mendefinisikan sebuah
struktur.
struct Buku
{
public
string Pengarang;
public
string Judul;
public
int HakCipta;
public
Buku(string a, string t, int c)
{
Pengarang = a;
Judul = t;
HakCipta = c;
}
}
// Demonstrasi struktur
Buku.
class DemoStruktur {
static
void Main() {
Buku buku1 = new Buku("RH. Sianipar",
"Pemrograman C++",
2011); // konstruktor
eksplisit
Buku buku2 = new Buku(); // konstruktur default
Buku buku3; // tidak ada konstruktor
Console.WriteLine(buku1.Judul
+ " oleh " +
buku1.Pengarang +
", (c) " +
buku1.HakCipta);
Console.WriteLine();
if
(buku2.Judul == null)
Console.WriteLine("buku2.Judul adalah null.");
// Sekarang, memberikan info tentang buku2.
buku2.Judul = "Pemrograman Java";
buku2.Pengarang = "R.H. Sianipar";
buku2.HakCipta = 2012;
Console.Write("buku2 sekarang memuat: ");
Console.WriteLine(buku2.Judul
+ " oleh " +
buku2.Pengarang +
", (c) " +
buku2.HakCipta);
Console.WriteLine();
//
Console.WriteLine(buku3.Judul); // error, harus diinisialisai dulu
buku3.Judul = "Struktur Data Java";
Console.WriteLine(buku3.Judul);
// sekarang OK
}
}
Keluaran program
ditampilkan di sini:
Pemrograman C++ oleh RH. Sianipar, (c) 2011
buku2.Judul adalah null.
buku2 sekarang memuat: Pemrograman Java oleh R.H. Sianipar,
(c) 2012
Struktur Data Java
Seperti yang
ditunjukkan program, sebuah struktur dapat diciptakan dengan menggunakan new untuk memanggil sebuah konstruktor
atau dengan mendeklarasikan sebuah objek. Jika new digunakan, maka bidang dari struktur akan diinisialisasi oleh
konstruktor default, yang menginisialisasi semua bidang dengan nilai default,
atau oleh konstruktor yang didefinisikan sendiri. Jika new tidak digunakan, seperti kasus pada buku3, maka objek tidak diinisialisasi, dan bidangnya harus
ditetapkan sebelum menggunakan objek tersebut.
Ketika Anda
menugaskan satu struktur kepada struktur lain, sebuah salinan dari objek
diciptakan. Ini merupakan cara penting yang membedakan struct dari class.
Seperti dijelaskan sebelumnya, ketika Anda menugaskan satu referensi kelas
kepada referensi kelas yang lain, Anda hanya membuat referensi pada sisi kiri
penugasan untuk menunjuk ke objek yang ditunjuk oleh referensi pada sisi kanan
penugasan. Ketika Anda menugaskan satu variabel struct kepada variabel struct
yang lain, hal itu berarti bahwa Anda membuat salinan dari objek yang berada di
sisi kanan. Sebagai contoh, perhatikan program berikut:
// Menyalin sebuah struct.
using System;
// Mendefinisikan sebuah
struktur.
struct StruktKu
{
public
int x;
}
// Demonstrasi penugasan
struktur.
class PenugasanStruct {
static
void Main()
{
StruktKu a;
StruktKu b;
a.x = 10;
b.x = 20;
Console.WriteLine("a.x {0}, b.x {1}", a.x, b.x);
a = b;
b.x = 30;
Console.WriteLine("a.x {0}, b.x {1}", a.x, b.x);
}
}
Keluaran program
ditampilkan di sini:
a.x 10, b.x 20
a.x 20, b.x 30
Berikut adalah
contoh lain yang menunjukkan bagaimana sebuah struktur dipakai dalam praktek.
Program mensimulasikan rekaman transaksi e-commerce. Setiap transaksi mencakup
sebuah header paket yang memuat jumlah dan panjang paket, yang diikuti dengan
nomor akun dan jumlah transaksi. Header paket ini diorganisir sebagai sebuah
struktur.
// Struktur dipakai untuk
mengelompokkan sejumlah data kecil.
using System;
// Mencefinisikan struktur
paket.
struct HeaderPaket {
public
uint JumPaket; // jumlah paket
public
ushort PjgPaket; // panjang paket
}
// Menggunakan HeaderPaket
untuk menciptakan rekaman transaksi e-commerce.
class Transaksi {
static
uint jumTrans = 0;
HeaderPaket ph;
string
NomAkun;
double
jumlah;
public
Transaksi(string akun, double nil)
{
// menciptakan header paket
ph.JumPaket = jumTrans++;
ph.PjgPaket = 512;
NomAkun = akun;
jumlah = nil;
}
// Mensimulasikan transaksi.
public
void kirimTransaksi()
{
Console.WriteLine("Paket #: " + ph.JumPaket +
", Panjang: " + ph.PjgPaket +
",\n Akun #: " + NomAkun +
", Jumlah Rp: {0}\n", jumlah);
}
}
// Demonstrasi Paket.
class DemoPaket
{
static
void Main()
{
Transaksi t = new Transaksi("31243",
1000000.12);
Transaksi t2 = new Transaksi("AB4655",
345562323.25);
Transaksi t3 = new Transaksi("8475-09",
98002321.00);
t.kirimTransaksi();
t2.kirimTransaksi();
t3.kirimTransaksi();
}
}
Keluaran program
ditampilkan di sini:
Paket #: 0, Panjang: 512,
Akun #: 31243, Jumlah
Rp: 1000000.12
Paket #: 1, Panjang: 512,
Akun #: AB4655,
Jumlah Rp: 345562323.25
Paket #: 2, Panjang: 512,
Akun #: 8475-09,
Jumlah Rp: 98002321
Enumerasi
Enumerasi adalah
sebuah himpunan konstanta integer bernama. Katakunci enum mendeklarasi-kan sebuah tipe enumerasi. Bentuk umum untuk
enumerasi adalah
enum nama { daftar enumerasi };
Di sini, nama
tipe enumerasi dispesifikasi oleh nama. daftar enumerasi merupakan sebuah daftar pengenal yang
dipisahkan dengan koma.
Berikut
diberikan sebuah contoh, yang mendefinisikan enumerasi, Universitas:
enum
Universitas { Ugm, Itb, Usu, Undip,
Unram, Uns
};
Satu kunci
penting tentang enumerasi adalah bahwa setiap simbol mewakili sebuah nilai
integer. Namun, tidak ada konversi implisit yang didefinisikan antara sebuah
tipe enum dan tipe integer bulti-in,
sehingga casr eksplisit harus dilakukan. Selain itu, cast juga diperlukan
ketika mengkonversi antara dua tipe enumerasi. Karena enumerasi
merepresentasikan nilai integer, Anda dapat menggunakan sebuah enumerasi untuk
mengendalikan statemen switch atau
sebagai variabel kendali di dalam loop for,
misalnya.
Setiap simbol
enumerasi diberikan nilai, yang lebih besar satu daripada simbol yang
mendahuluinya. Secara default, nilai dari simbol enumerasi pertama adalah 0.
Oleh karena itu, di dalam enumerasi Universitas,
Ugm bernilai 0, Itb bernilai 1, Usu
bernilai 2, dan seterusnya.
Anggota suatu
enumerasi diakses melalui nama tipe dan menggunakan operator dot. Sebagai
contoh,
Console.WriteLine(Universitas.Undip
+ " memiliki nilai " +
(int)
Universitas.Undip);
menampilkan
Undip memiliki nilai 3
Seperti yang
ditunjukkan keluaran, ketika sebuah nilai terenumerasi ingin ditampilkan,
namanya digunakan. Untuk mendapatkan nilai integernya, sebuah cast menjadi int harus dipakai.
Berikut adalah
sebuah program yang mengilustrasikan enumerasi Universitas:
// Demonstrasi sebuah
enumerasi.
using System;
class DemoEnum {
enum
Universitas { Ugm, Itb, Usu, Undip,
Unram, Uns };
static
void Main() {
string[]
warna_logo = {
"Abu-abu",
"Kuning",
"Biru",
"Merah",
"Hijau",
"Coklat"
};
Universitas i; // mendeklarasikan sebuah
variabel enum
// Menggunakan i untuk menjelajahi enum.
for
(i = Universitas.Ugm; i <= Universitas.Uns; i++)
Console.WriteLine(i
+ " memiliki nilai " + (int)i);
Console.WriteLine();
// Menggunakan sebuah enumerasi untuk
mengindeks array.
for
(i = Universitas.Ugm; i <= Universitas.Uns; i++)
Console.WriteLine("Warna logo dari " + i + " adalah " +
warna_logo[(int)i]);
}
}
Keluaran program
ditampilkan di sini:
Ugm memiliki nilai 0
Itb memiliki nilai 1
Usu memiliki nilai 2
Undip memiliki nilai 3
Unram memiliki nilai 4
Uns memiliki nilai 5
Warna logo dari Ugm adalah Abu-abu
Warna logo dari Itb adalah Kuning
Warna logo dari Usu adalah Biru
Warna logo dari Undip adalah Merah
Warna logo dari Unram adalah Hijau
Warna logo dari Uns adalah Coklat
Menginisialisasi
Enumerasi
Anda dapat
menspesifikasi satu atau lebih simbol menggunakan sebuah penginisialisasi. Anda
dapat melakukannya dengan mencantumkan simbol dengan tanda sama dengan dan
sebuah ekspresi konstanta integer. Simbol-simbol yang ditempatkan setelah
penginisialisasi ditugasi nilai-nilai yang lebih besar dari nilai inisialisasi.
Sebagai contoh, kode berikut menugaskan nilai 10 kepada Usu:
enum
Universitas { Ugm, Itb, Usu = 10, Undip,
Unram, Uns
};
Sekarang, nilai
simbol-simbol menjadi
Ugm = 0;
Itb = 1;
Usu = 10;
Undip = 11;
Unram = 12;
Uns = 13;
Menggunakan
Enumerasi
Sekarang,
bayangkan Anda sedang menulis sebuah program yang mengendalikan sebuah konveyor
di pabrik. Anda akan menciptakan sebuah metode, Konveyor(), yang menerima beberapa perintah berikut sebagai
parameter: mulai, berhenti, maju, dan mundur. Anda diminta untuk menggunakan
enumerasi pada kasus ini. Berikut adalah contoh programnya:
// Simulasi konveyor.
using System;
class KendaliKonveyor {
// Mengenumerasi perintah konveyor.
public
enum Aksi { Mulai, Berhenti, Maju,
Mundur };
public
void Konveyor(Aksi perintah)
{
switch
(perintah)
{
case
Aksi.Mulai:
Console.WriteLine("Mulai
konveyor.");
break;
case
Aksi.Berhenti:
Console.WriteLine("Hentikan
konveyor.");
break;
case
Aksi.Maju:
Console.WriteLine("Majukan
konveyor.");
break;
case
Aksi.Mundur:
Console.WriteLine("Mundurkan
konveyor.");
break;
}
}
}
class DemoKonveyor
{
static
void Main()
{
KendaliKonveyor c = new KendaliKonveyor();
c.Konveyor(KendaliKonveyor.Aksi.Mulai);
c.Konveyor(KendaliKonveyor.Aksi.Maju);
c.Konveyor(KendaliKonveyor.Aksi.Mundur);
c.Konveyor(KendaliKonveyor.Aksi.Berhenti);
}
}
Keluaran program
ditampilkan di sini:
Mulai konveyor.
Majukan konveyor.
Mundurkan konveyor.
Hentikan konveyor.
No comments:
Post a Comment