Apa yang dimaksud dengan "program untuk antarmuka, bukan implementasi"?

Jawaban:

148

Antarmuka hanyalah kontrak atau tanda tangan dan mereka tidak tahu apa-apa tentang implementasi.

Pengodean terhadap antarmuka berarti, kode klien selalu memegang objek Antarmuka yang disediakan oleh pabrik. Setiap instance yang dikembalikan oleh pabrik akan bertipe Interface yang harus diterapkan oleh setiap kelas kandidat pabrik. Dengan cara ini program klien tidak khawatir tentang implementasi dan tanda tangan antarmuka menentukan apa semua operasi dapat dilakukan. Ini dapat digunakan untuk mengubah perilaku suatu program pada saat run-time. Ini juga membantu Anda untuk menulis program yang jauh lebih baik dari sudut pandang pemeliharaan.

Inilah contoh dasar untuk Anda.

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

[STAThread]
static void Main()
{
    //This is your client code.
    ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
    speaker.Speak();
    Console.ReadLine();
}

public interface ISpeaker
{
    void Speak();
}

public class EnglishSpeaker : ISpeaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : ISpeaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak German.");
    }

    #endregion
}

public class SpanishSpeaker : ISpeaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    #endregion
}

teks alternatif

Ini hanyalah contoh dasar dan penjelasan aktual dari prinsip tersebut berada di luar cakupan jawaban ini.

EDIT

Saya telah memperbarui contoh di atas dan menambahkan Speakerkelas dasar abstrak . Dalam pembaruan ini, saya menambahkan fitur ke semua Pembicara ke "SayHello". Semua pembicara berbicara "Hello World". Jadi itu fitur umum dengan fungsi yang serupa. Lihat diagram kelas dan Anda akan menemukan bahwa Speakerkelas abstrak mengimplementasikan ISpeakerantarmuka dan menandai Speak()sebagai abstrak yang berarti bahwa setiap implementasi Speaker bertanggung jawab untuk mengimplementasikan Speak()metode karena bervariasi dari Speakerke Speaker. Tetapi semua pembicara mengatakan "Halo" dengan suara bulat. Jadi di kelas Speaker abstrak kita mendefinisikan metode yang mengatakan "Hello World" dan setiap Speakerimplementasi akan mendapatkan SayHello()metode tersebut.

Pertimbangkan suatu kasus di mana SpanishSpeakertidak dapat Mengatakan Halo sehingga dalam kasus itu Anda dapat mengganti SayHello()metode untuk Penutur Bahasa Spanyol dan meningkatkan pengecualian yang tepat.

Harap perhatikan bahwa, kami belum melakukan perubahan pada Interface ISpeaker. Dan kode klien dan SpeakerFactory juga tetap tidak terpengaruh tidak berubah. Dan inilah yang kami capai dengan Programming-to-Interface .

Dan kita dapat mencapai perilaku ini hanya dengan menambahkan kelas dasar abstrak Speaker dan beberapa modifikasi kecil di Setiap implementasi sehingga meninggalkan program asli tidak berubah. Ini adalah fitur yang diinginkan dari aplikasi apa pun dan itu membuat aplikasi Anda mudah dipelihara.

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

class Program
{
    [STAThread]
    static void Main()
    {
        //This is your client code.
        ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
        speaker.Speak();
        Console.ReadLine();
    }
}

public interface ISpeaker
{
    void Speak();
}

public abstract class Speaker : ISpeaker
{

    #region ISpeaker Members

    public abstract void Speak();

    public virtual void SayHello()
    {
        Console.WriteLine("Hello world.");
    }

    #endregion
}

public class EnglishSpeaker : Speaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        this.SayHello();
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : Speaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        Console.WriteLine("I speak German.");
        this.SayHello();
    }

    #endregion
}

public class SpanishSpeaker : Speaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    public override void SayHello()
    {
        throw new ApplicationException("I cannot say Hello World.");
    }

    #endregion
}

teks alternatif

ini. __curious_geek
sumber
19
Pemrograman ke antarmuka bukan hanya tentang jenis variabel referensi. Ini juga berarti bahwa Anda tidak menggunakan asumsi tersirat tentang implementasi Anda. Misalnya jika Anda menggunakan tipe Listsebagai, Anda masih bisa berasumsi bahwa akses acak cepat dengan berulang kali menelepon get(i).
Joachim Sauer
16
Pabrik ortogonal untuk pemrograman ke antarmuka, tetapi saya pikir penjelasan ini membuatnya seolah-olah mereka adalah bagian dari itu.
T.
@Toon: setuju dengan Anda. Saya ingin memberikan contoh yang sangat mendasar dan sederhana untuk pemrograman-ke-antarmuka. Saya tidak ingin membingungkan penanya dengan mengimplementasikan antarmuka IFlyable pada beberapa kelas burung dan hewan.
ini. __curious_geek
@ini. jika saya menggunakan kelas abstrak atau pola fasad, apakah masih akan disebut "program ke antarmuka"? atau apakah saya secara eksplisit harus menggunakan antarmuka dan mengimplementasikannya di kelas?
never_had_a_name
1
Alat uml apa yang Anda gunakan untuk membuat gambar?
Adam Arold
29

Pikirkan sebuah antarmuka sebagai kontrak antara objek dan kliennya. Itu adalah antarmuka yang menentukan hal-hal yang dapat dilakukan objek, dan tanda tangan untuk mengakses hal-hal itu.

Implementasi adalah perilaku aktual. Katakan misalnya Anda memiliki semacam metode (). Anda dapat menerapkan QuickSort atau MergeSort. Itu seharusnya tidak menjadi masalah bagi pemanggilan kode kode klien selama antarmuka tidak berubah.

Perpustakaan seperti Java API dan .NET Framework banyak menggunakan antarmuka karena jutaan programmer menggunakan objek yang disediakan. Pembuat perpustakaan ini harus sangat berhati-hati agar mereka tidak mengubah antarmuka ke kelas-kelas di perpustakaan ini karena itu akan mempengaruhi semua programmer menggunakan perpustakaan. Di sisi lain mereka dapat mengubah implementasi sesuka mereka.

Jika, sebagai seorang programmer, Anda membuat kode terhadap implementasi maka segera setelah itu mengubah kode Anda berhenti bekerja. Jadi pikirkan manfaat antarmuka dengan cara ini:

  1. ia menyembunyikan hal-hal yang tidak perlu Anda ketahui sehingga objek lebih mudah digunakan.
  2. ini memberikan kontrak tentang bagaimana objek akan berperilaku sehingga Anda dapat bergantung pada itu
Vincent Ramdhanie
sumber
Ini berarti Anda harus mengetahui apa yang Anda lakukan dengan objek: dalam contoh asalkan Anda hanya mengontrak untuk jenis, tidak harus jenis yang stabil.
penguat
Begitu mirip dengan bagaimana dokumentasi perpustakaan tidak menyebutkan implementasi, mereka hanya deskripsi antarmuka kelas yang disertakan.
Joe Iddon
17

Ini berarti Anda harus mencoba menulis kode Anda sehingga menggunakan abstraksi (kelas abstrak atau antarmuka) alih-alih implementasinya secara langsung.

Biasanya implementasi disuntikkan ke dalam kode Anda melalui konstruktor atau panggilan metode. Jadi, kode Anda tahu tentang antarmuka atau kelas abstrak dan dapat memanggil apa pun yang didefinisikan dalam kontrak ini. Sebagai objek aktual (implementasi antarmuka / kelas abstrak) digunakan, panggilan beroperasi pada objek.

Ini adalah subset dari Liskov Substitution Principle(LSP), L dari SOLIDprinsip.

Contoh dalam .NET adalah untuk kode dengan IListalih - alih Listatau Dictionary, sehingga Anda bisa menggunakan kelas apa pun yang mengimplementasikan secara IListbergantian dalam kode Anda:

// myList can be _any_ object that implements IList
public int GetListCount(IList myList)
{
    // Do anything that IList supports
    return myList.Count();
}

Contoh lain dari Base Class Library (BCL) adalah ProviderBasekelas abstrak - ini menyediakan beberapa infrastruktur, dan yang penting artinya semua implementasi penyedia dapat digunakan secara bergantian jika Anda membuat kode menentangnya.

Oded
sumber
tetapi bagaimana klien dapat berinteraksi dengan antarmuka dan menggunakan metode yang kosong?
never_had_a_name
1
Klien tidak berinteraksi dengan antarmuka, tetapi melalui antarmuka :) Objek berinteraksi dengan objek lain melalui metode (pesan) dan antarmuka adalah semacam bahasa - ketika Anda tahu bahwa objek (orang) tertentu mengimplementasikan (berbicara) bahasa Inggris (IList) ), Anda dapat menggunakannya tanpa perlu tahu lebih banyak tentang objek itu (bahwa ia juga orang Italia), karena tidak diperlukan dalam konteks itu (jika Anda ingin meminta bantuan, Anda tidak perlu tahu dia berbicara juga bahasa Italia) jika Anda mengerti bahasa Inggris).
Gabriel Ščerbák
BTW. Prinsip substitusi IMHO Liskov adalah tentang semantik warisan dan tidak ada hubungannya dengan antarmuka, yang dapat ditemukan juga dalam bahasa tanpa warisan (Go from Google).
Gabriel Ščerbák
5

Jika Anda menulis Kelas Mobil di era Combustion-Car, maka ada kemungkinan besar Anda akan mengimplementasikan oilChange () sebagai bagian dari Kelas ini. Tetapi, ketika mobil listrik diperkenalkan, Anda akan berada dalam masalah karena tidak ada penggantian oli untuk mobil ini, dan tidak ada implementasinya.

Solusi untuk masalah ini adalah memiliki antarmuka PerformMaintenance () di kelas Mobil dan menyembunyikan detail di dalam implementasi yang sesuai. Setiap jenis mobil akan menyediakan implementasinya sendiri untuk performMaintenance (). Sebagai pemilik Mobil yang harus Anda hadapi adalah performMaintenance () dan tidak perlu khawatir beradaptasi ketika ada PERUBAHAN.

class MaintenanceSpecialist {
    public:
        virtual int performMaintenance() = 0;
};

class CombustionEnginedMaintenance : public MaintenanceSpecialist {
    int performMaintenance() { 
        printf("combustionEnginedMaintenance: We specialize in maintenance of Combustion engines \n");
        return 0;
    }
};

class ElectricMaintenance : public MaintenanceSpecialist {
    int performMaintenance() {
        printf("electricMaintenance: We specialize in maintenance of Electric Cars \n");
        return 0;
    }
};

class Car {
    public:
        MaintenanceSpecialist *mSpecialist;
        virtual int maintenance() {
            printf("Just wash the car \n");
            return 0;
        };
};

class GasolineCar : public Car {
    public: 
        GasolineCar() {
        mSpecialist = new CombustionEnginedMaintenance();
        }
        int maintenance() {
        mSpecialist->performMaintenance();
        return 0;
        }
};

class ElectricCar : public Car {
    public: 
        ElectricCar() {
             mSpecialist = new ElectricMaintenance();
        }

        int maintenance(){
            mSpecialist->performMaintenance();
            return 0;
        }
};

int _tmain(int argc, _TCHAR* argv[]) {

    Car *myCar; 

    myCar = new GasolineCar();
    myCar->maintenance(); /* I dont know what is involved in maintenance. But, I do know the maintenance has to be performed */


    myCar = new ElectricCar(); 
    myCar->maintenance(); 

    return 0;
}

Penjelasan tambahan: Anda adalah pemilik mobil yang memiliki beberapa mobil. Anda mengukir layanan yang ingin Anda outsourcing. Dalam kasus kami, kami ingin melakukan outsourcing pekerjaan pemeliharaan semua mobil.

  1. Anda mengidentifikasi kontrak (Antarmuka) yang berlaku untuk semua mobil dan penyedia layanan Anda.
  2. Penyedia layanan mengeluarkan mekanisme untuk menyediakan layanan.
  3. Anda tidak ingin khawatir tentang mengaitkan jenis mobil dengan penyedia layanan. Anda cukup menentukan kapan Anda ingin menjadwalkan pemeliharaan dan menjalankannya. Perusahaan jasa yang tepat harus terjun dan melakukan pekerjaan pemeliharaan.

    Pendekatan alternatif.

  4. Anda mengidentifikasi pekerjaan (bisa berupa Antarmuka antarmuka baru) yang cocok untuk semua mobil Anda.
  5. Anda keluar dengan mekanisme untuk menyediakan layanan. Pada dasarnya Anda akan memberikan implementasinya.
  6. Anda memohon pekerjaan dan melakukannya sendiri. Di sini Anda akan melakukan pekerjaan pemeliharaan yang sesuai.

    Apa kelemahan dari pendekatan kedua? Anda mungkin bukan ahli dalam menemukan cara terbaik untuk melakukan perawatan. Tugas Anda adalah mengendarai mobil dan menikmatinya. Tidak berada dalam bisnis mempertahankannya.

    Apa kelemahan dari pendekatan pertama? Ada biaya overhead untuk menemukan perusahaan, dll. Kecuali jika Anda adalah perusahaan rental mobil, itu mungkin tidak sepadan dengan usaha.

Raghav Navada
sumber
4

Pernyataan ini tentang kopling. Salah satu alasan potensial untuk menggunakan pemrograman berorientasi objek adalah penggunaan kembali. Jadi misalnya Anda dapat membagi algoritme Anda di antara dua objek yang berkolaborasi A dan B. Ini mungkin berguna untuk pembuatan nanti algoritma lain, yang mungkin menggunakan kembali satu atau yang lain dari dua objek. Namun, ketika benda-benda itu berkomunikasi (mengirim pesan - metode panggilan), mereka menciptakan ketergantungan satu sama lain. Tetapi jika Anda ingin menggunakan satu tanpa yang lain, Anda perlu menentukan apa yang harus dilakukan beberapa objek lain C lakukan untuk objek A jika kita mengganti B. Deskripsi tersebut disebut antarmuka. Ini memungkinkan objek A untuk berkomunikasi tanpa perubahan dengan objek yang berbeda bergantung pada antarmuka. Pernyataan yang Anda sebutkan mengatakan bahwa jika Anda berencana untuk menggunakan kembali beberapa bagian dari suatu algoritma (atau lebih umum program), Anda harus membuat antarmuka dan mengandalkan mereka,

Gabriel Ščerbák
sumber
2

Seperti yang orang lain katakan, itu berarti bahwa kode panggilan Anda hanya harus tahu tentang orangtua yang abstrak, BUKAN kelas pelaksana aktual yang akan melakukan pekerjaan.

Apa yang membantu untuk memahami ini adalah MENGAPA Anda harus selalu memprogram ke antarmuka. Ada banyak alasan, tetapi dua yang paling mudah dijelaskan adalah

1) Pengujian.

Katakanlah saya memiliki seluruh kode basis data dalam satu kelas. Jika program saya tahu tentang kelas beton, saya hanya dapat menguji kode saya dengan benar-benar menjalankannya terhadap kelas itu. Saya menggunakan -> berarti "berbicara dengan".

WorkerClass -> DALClass Namun, mari kita tambahkan antarmuka ke dalam campuran.

WorkerClass -> IDAL -> DALClass.

Jadi DALClass mengimplementasikan antarmuka IDAL, dan kelas pekerja HANYA memanggil melalui ini.

Sekarang jika kita ingin menulis tes untuk kode, kita bisa membuat kelas sederhana yang hanya bertindak seperti database.

WorkerClass -> IDAL -> IFakeDAL.

2) Penggunaan Kembali

Mengikuti contoh di atas, katakanlah kita ingin pindah dari SQL Server (yang menggunakan DALClass konkret kami) ke MonogoDB. Ini akan membutuhkan pekerjaan besar, tetapi BUKAN jika kita sudah memprogram ke antarmuka. Dalam hal ini kami hanya menulis kelas DB baru, dan berubah (melalui pabrik)

WorkerClass -> IDAL -> DALClass

untuk

WorkerClass -> IDAL -> MongoDBClass

Mathieson
sumber
1

antarmuka menggambarkan kemampuan. saat menulis kode imperatif, bicarakan kemampuan yang Anda gunakan, bukan tipe atau kelas tertentu.

rektide
sumber