Pertanyaan Apa yang dimaksud programmer ketika mereka mengatakan, "Kode terhadap antarmuka, bukan objek."?


Saya telah memulai pencarian yang sangat panjang dan sulit untuk dipelajari dan menerapkan TDD ke alur kerja saya. Saya mendapat kesan bahwa TDD sangat cocok dengan prinsip-prinsip IoC.

Setelah menelusuri beberapa pertanyaan bertanda TDD di sini di SO, saya membaca ide bagus untuk memprogram antarmuka, bukan objek.

Dapatkah Anda memberikan contoh kode sederhana tentang apa ini, dan bagaimana menerapkannya dalam kasus penggunaan nyata? Contoh-contoh sederhana adalah kunci bagi saya (dan orang lain yang ingin belajar) untuk memahami konsep-konsep tersebut.

Terima kasih banyak.


75
2017-12-16 00:39


asal


Jawaban:


Mempertimbangkan:

class MyClass
{
    //Implementation
    public void Foo() {}
}

class SomethingYouWantToTest
{
    public bool MyMethod(MyClass c)
    {
        //Code you want to test
        c.Foo();
    }
}

Karena MyMethod hanya menerima a MyClass, jika Anda ingin mengganti MyClass dengan objek tiruan untuk menguji unit, Anda tidak bisa. Lebih baik menggunakan antarmuka:

interface IMyClass
{
    void Foo();
}

class MyClass : IMyClass
{
    //Implementation
    public void Foo() {}
}

class SomethingYouWantToTest
{
    public bool MyMethod(IMyClass c)
    {
        //Code you want to test
        c.Foo();
    }
}

Sekarang Anda bisa menguji MyMethod, karena hanya menggunakan antarmuka, bukan implementasi konkrit tertentu. Kemudian Anda dapat mengimplementasikan antarmuka itu untuk membuat jenis tiruan atau palsu apa pun yang Anda inginkan untuk tujuan pengujian. Bahkan ada perpustakaan seperti Rhino Mocks ' Rhino.Mocks.MockRepository.StrictMock<T>(), yang mengambil antarmuka dan membangun Anda objek tiruan dengan cepat.


77
2017-12-16 00:44



Ini semua masalah keintiman. Jika Anda membuat kode untuk suatu implementasi (objek yang disadari) Anda berada dalam hubungan yang sangat intim dengan kode "lain" itu, sebagai konsumennya. Itu berarti Anda harus tahu bagaimana membangunnya (yaitu, dependensi apa yang dimilikinya, mungkin sebagai konstruktor params, mungkin sebagai penentu), kapan membuangnya, dan Anda mungkin tidak dapat berbuat banyak tanpa itu.

Antarmuka di depan objek yang terwujud memungkinkan Anda melakukan beberapa hal -

  1. Untuk satu Anda dapat / harus memanfaatkan pabrik untuk membangun contoh-contoh objek. Kontainer IOC melakukan ini dengan sangat baik untuk Anda, atau Anda dapat membuatnya sendiri. Dengan tugas konstruksi di luar tanggung jawab Anda, kode Anda hanya dapat mengasumsikan bahwa ia mendapatkan apa yang dibutuhkannya. Di sisi lain dari dinding pabrik, Anda dapat membangun contoh nyata, atau contoh tiruan dari kelas. Dalam produksi Anda akan menggunakan nyata tentu saja, tetapi untuk pengujian, Anda mungkin ingin membuat mematikan atau contoh diejek secara dinamis untuk menguji berbagai status sistem tanpa harus menjalankan sistem.
  2. Anda tidak harus tahu di mana objek itu berada. Ini berguna dalam sistem terdistribusi di mana objek yang ingin Anda ajak bicara mungkin atau mungkin tidak lokal untuk proses Anda atau bahkan sistem. Jika Anda pernah memrogram Java RMI atau skool lama EJB, Anda tahu rutinitas "berbicara dengan antarmuka" yang menyembunyikan proxy yang melakukan jaringan jarak jauh dan menyusun tugas yang tidak perlu dipedulikan klien Anda. WCF memiliki filosofi serupa "berbicara dengan antarmuka" dan biarkan sistem menentukan cara berkomunikasi dengan objek / layanan target.

** PEMBARUAN ** Ada permintaan untuk contoh Kontainer IOC (Pabrik). Ada banyak di luar sana untuk hampir semua platform, tetapi pada intinya mereka bekerja seperti ini:

  1. Anda menginisialisasi penampung pada rutin startup aplikasi Anda. Beberapa kerangka melakukan ini melalui file konfigurasi atau kode atau keduanya.

  2. Anda "Mendaftarkan" implementasi yang Anda inginkan agar wadah buat untuk Anda sebagai pabrik untuk antarmuka yang mereka terapkan (misalnya: daftarkan MyServiceImpl untuk antarmuka Layanan). Selama proses pendaftaran ini biasanya ada beberapa kebijakan perilaku yang dapat Anda berikan seperti jika instance baru dibuat setiap kali atau satu (ton) instance digunakan

  3. Ketika wadah menciptakan objek untuk Anda, ia menyuntikkan dependensi apa pun ke objek-objek tersebut sebagai bagian dari proses pembuatan (misalnya, jika objek Anda bergantung pada antarmuka lain, implementasi antarmuka tersebut akan disediakan dan seterusnya).

Pseudo-codishly bisa terlihat seperti ini:

IocContainer container = new IocContainer();

//Register my impl for the Service Interface, with a Singleton policy
container.RegisterType(Service, ServiceImpl, LifecyclePolicy.SINGLETON);

//Use the container as a factory
Service myService = container.Resolve<Service>();

//Blissfully unaware of the implementation, call the service method.
myService.DoGoodWork();

18
2017-12-16 00:59



Saat memprogram antarmuka, Anda akan menulis kode yang menggunakan instance antarmuka, bukan tipe konkret. Misalnya Anda mungkin menggunakan pola berikut, yang menggabungkan injeksi konstruktor. Injeksi konstruktor dan bagian lain dari inversi kontrol tidak diperlukan untuk dapat memprogram antarmuka, namun karena Anda berasal dari perspektif TDD dan IoC, saya telah menyambungkannya dengan cara ini untuk memberi Anda beberapa konteks, semoga akrab dengan.

public class PersonService
{
    private readonly IPersonRepository repository;

    public PersonService(IPersonRepository repository)
    {
        this.repository = repository;
    }

    public IList<Person> PeopleOverEighteen
    {
        get
        {
            return (from e in repository.Entities where e.Age > 18 select e).ToList();
        }
    }
}

Objek repositori dilewatkan dan merupakan tipe antarmuka. Manfaat lewat antarmuka adalah kemampuan untuk 'menukar' implementasi konkrit tanpa mengubah penggunaan.

Sebagai contoh, seseorang akan berasumsi bahwa pada saat runtime, wadah IoC akan menyuntikkan repositori yang ditransfer untuk memukul database. Selama waktu pengujian, Anda dapat meneruskan dalam bentuk tiruan atau stub repositori untuk melatih PeopleOverEighteen metode.


8
2017-12-16 00:47



Itu artinya berpikir generik. Tidak spesifik.

Misalkan Anda memiliki aplikasi yang memberi tahu pengguna mengiriminya beberapa pesan. Jika Anda bekerja menggunakan antarmuka IMESSage misalnya

interface IMessage
{
    public void Send();
}

Anda dapat menyesuaikan, per pengguna, cara mereka menerima pesan. Misalnya seseorang ingin diberitahu dengan Email dan jadi IoC Anda akan membuat kelas beton EmailMessage. Beberapa lainnya menginginkan SMS, dan Anda membuat instance SMSMessage.

Dalam semua kasus ini, kode untuk memberi tahu pengguna tidak akan pernah diubah. Bahkan jika Anda menambahkan kelas beton lain.


3
2017-12-16 00:55



Keuntungan besar pemrograman terhadap antarmuka saat melakukan pengujian unit adalah memungkinkan Anda mengisolasi sepotong kode dari dependensi apa pun yang ingin Anda uji secara terpisah atau simulasikan selama pengujian.

Contoh yang saya sebutkan di sini sebelum di suatu tempat adalah penggunaan antarmuka untuk mengakses nilai konfigurasi. Daripada melihat langsung di ConfigurationManager Anda dapat menyediakan satu atau lebih antarmuka yang memungkinkan Anda mengakses nilai-nilai konfigurasi. Biasanya Anda akan memberikan implementasi yang membaca dari file konfigurasi tetapi untuk pengujian Anda dapat menggunakan salah satu yang hanya mengembalikan nilai tes atau melempar pengecualian atau apa pun.

Pertimbangkan juga lapisan akses data Anda. Memiliki logika bisnis Anda yang erat digabungkan ke implementasi akses data tertentu membuat sulit untuk menguji tanpa memiliki seluruh database yang berguna dengan data yang Anda butuhkan. Jika akses data Anda tersembunyi di balik antarmuka Anda dapat menyediakan hanya data yang Anda butuhkan untuk tes.

Menggunakan antarmuka meningkatkan "luas permukaan" yang tersedia untuk pengujian memungkinkan untuk tes berbutir halus yang benar-benar menguji unit-unit individual dari kode Anda.


2
2017-12-16 00:54



Uji kode Anda seperti seseorang yang akan menggunakannya setelah membaca dokumentasi. Jangan menguji apa pun berdasarkan pengetahuan yang Anda miliki karena Anda telah menulis atau membaca kode. Anda ingin memastikan bahwa kode Anda berperilaku seperti yang diharapkan.

Dalam kasus terbaik Anda harus dapat menggunakan tes Anda sebagai contoh, doctests dengan Python adalah contoh yang baik untuk ini.

Jika Anda mengikuti panduan ini, mengubah implementasi seharusnya tidak menjadi masalah.

Juga dalam pengalaman saya adalah praktik yang baik untuk menguji setiap "lapisan" aplikasi Anda. Anda akan memiliki unit atom, yang dengan sendirinya tidak memiliki ketergantungan dan Anda akan memiliki unit yang bergantung pada unit lain sampai Anda akhirnya mendapatkan aplikasi yang dengan sendirinya adalah unit.

Anda harus menguji setiap lapisan, jangan bergantung pada fakta bahwa dengan menguji unit A Anda juga menguji unit B yang unit A bergantung pada (aturan berlaku untuk warisan juga.) Ini juga harus diperlakukan sebagai detail implementasi, bahkan meskipun Anda mungkin merasa seolah-olah mengulangi diri sendiri.

Perlu diingat bahwa sekali tes tertulis tidak mungkin berubah sementara kode yang mereka uji akan berubah hampir pasti.

Dalam prakteknya juga ada masalah IO dan dunia luar, jadi Anda ingin menggunakan antarmuka sehingga Anda dapat membuat tiruan jika perlu.

Dalam bahasa yang lebih dinamis ini bukan masalah besar, di sini Anda dapat menggunakan mengetik bebek, multiple inheritance dan mixins untuk menulis kasus uji. Jika Anda mulai tidak menyukai pewarisan secara umum, Anda mungkin melakukannya dengan benar.


2
2017-12-26 16:54



Screencast ini menjelaskan pengembangan tangkas dan TDD dalam praktik untuk c #.

Dengan mengkodekan antarmuka berarti bahwa dalam pengujian Anda, Anda dapat menggunakan objek tiruan, bukan objek nyata. Dengan menggunakan kerangka kerja tiruan yang baik, Anda dapat melakukannya di objek tiruan apa pun yang Anda suka.


1
2017-12-16 11:30