Saturday, December 24, 2016

Bab 11. Pemrograman C# Belajar Dari Contoh



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