Cara menggunakan inversi kontrol di C #

Baik inversi kontrol dan injeksi dependensi memungkinkan Anda memutuskan dependensi antara komponen dalam aplikasi Anda dan membuat aplikasi Anda lebih mudah untuk diuji dan dipelihara. Namun, inversi kontrol dan injeksi ketergantungan tidak sama - ada perbedaan halus di antara keduanya.

Pada artikel ini, kita akan memeriksa inversi pola kontrol dan memahami perbedaannya dari injeksi ketergantungan dengan contoh kode yang relevan di C #.

Untuk bekerja dengan contoh kode yang disediakan dalam artikel ini, Anda harus menginstal Visual Studio 2019 di sistem Anda. Jika Anda belum memiliki salinannya, Anda dapat mengunduh Visual Studio 2019 di sini. 

Buat proyek aplikasi konsol di Visual Studio

Pertama, mari buat proyek aplikasi konsol .NET Core di Visual Studio. Dengan asumsi Visual Studio 2019 diinstal di sistem Anda, ikuti langkah-langkah yang diuraikan di bawah ini untuk membuat proyek aplikasi konsol .NET Core baru di Visual Studio.

  1. Luncurkan Visual Studio IDE.
  2. Klik "Buat proyek baru".
  3. Di jendela "Buat proyek baru", pilih "Aplikasi Konsol (.NET Core)" dari daftar template yang ditampilkan.
  4. Klik Next. 
  5. Di jendela "Configure your new project" yang ditampilkan berikutnya, tentukan nama dan lokasi untuk proyek baru tersebut.
  6. Klik Buat. 

Ini akan membuat proyek aplikasi konsol .NET Core baru di Visual Studio 2019. Kami akan menggunakan proyek ini untuk menjelajahi inversi kontrol di bagian selanjutnya dari artikel ini.

Apa itu inversi kendali?

Inversion of control (IoC) adalah pola desain di mana aliran kontrol suatu program dibalik. Anda dapat memanfaatkan inversi pola kontrol untuk memisahkan komponen aplikasi Anda, menukar implementasi dependensi, dependensi tiruan, dan membuat aplikasi Anda modular dan dapat diuji.

Injeksi ketergantungan adalah bagian dari inversi prinsip kontrol. Dengan kata lain, injeksi ketergantungan hanyalah salah satu cara untuk mengimplementasikan inversi kendali. Anda juga dapat mengimplementasikan inversi kontrol menggunakan peristiwa, delegasi, pola template, metode pabrik, atau pencari lokasi, misalnya.

Inversi pola desain kontrol menyatakan bahwa objek tidak boleh membuat objek tempat mereka bergantung untuk melakukan beberapa aktivitas. Sebaliknya, mereka harus mendapatkan objek tersebut dari layanan luar atau kontainer. Ide ini sejalan dengan prinsip Hollywood yang mengatakan, "Jangan hubungi kami, kami akan menelepon Anda." Sebagai contoh, alih-alih aplikasi memanggil metode dalam kerangka kerja, kerangka akan memanggil implementasi yang telah disediakan oleh aplikasi. 

Contoh kontrol pembalikan di C #

Asumsikan bahwa Anda sedang membangun aplikasi pemrosesan pesanan dan Anda ingin menerapkan logging. Demi kesederhanaan, anggaplah target log adalah file teks. Pilih proyek aplikasi konsol yang baru saja Anda buat di jendela Solution Explorer dan buat dua file, bernama ProductService.cs dan FileLogger.cs.

    ProductService kelas publik

    {

        pribadi hanya baca FileLogger _fileLogger = new FileLogger ();

        public void Log (pesan string)

        {

            _fileLogger.Log (pesan);

        }

    }

    FileLogger kelas publik

    {

        public void Log (pesan string)

        {

            Console.WriteLine ("Metode Log Di Dalam FileLogger.");

            LogToFile (pesan);

        }

        private void LogToFile (pesan string)

        {

            Console.WriteLine ("Metode: LogToFile, Teks: {0}", pesan);

        }

    }

Penerapan yang ditampilkan dalam cuplikan kode sebelumnya sudah benar, tetapi ada batasan. Anda dibatasi untuk memasukkan data ke file teks saja. Anda tidak dapat dengan cara apa pun mencatat data ke sumber data lain atau target log yang berbeda.

Implementasi logging yang tidak fleksibel

Bagaimana jika Anda ingin memasukkan data ke tabel database? Implementasi yang ada tidak akan mendukung ini dan Anda akan dipaksa untuk mengubah implementasi. Anda bisa mengubah implementasi kelas FileLogger, atau Anda bisa membuat kelas baru, katakanlah, DatabaseLogger.

    public class DatabaseLogger

    {

        public void Log (pesan string)

        {

            Console.WriteLine ("Metode Inside Log dari DatabaseLogger.");

            LogToDatabase (pesan);

        }

        private void LogToDatabase (pesan string)

        {

            Console.WriteLine ("Metode: LogToDatabase, Teks: {0}", pesan);

        }

    }

Anda bahkan dapat membuat instance kelas DatabaseLogger di dalam kelas ProductService seperti yang ditunjukkan dalam cuplikan kode di bawah ini.

ProductService kelas publik

    {

        pribadi hanya baca FileLogger _fileLogger = new FileLogger ();

        pribadi hanya baca DatabaseLogger _databaseLogger =

         DatabaseLogger baru ();

        public void LogToFile (pesan string)

        {

            _fileLogger.Log (pesan);

        }

        public void LogToDatabase (pesan string)

        {

            _fileLogger.Log (pesan);

        }

    }

Namun, meskipun ini akan berhasil, bagaimana jika Anda perlu mencatat data aplikasi Anda ke EventLog? Desain Anda tidak fleksibel dan Anda akan dipaksa untuk mengubah kelas ProductService setiap kali Anda perlu masuk ke target log baru. Ini tidak hanya rumit tetapi juga akan membuat sangat sulit bagi Anda untuk mengelola kelas ProductService dari waktu ke waktu.

Tambahkan fleksibilitas dengan antarmuka 

Solusi untuk masalah ini adalah dengan menggunakan antarmuka yang akan diterapkan oleh kelas logger beton. Potongan kode berikut menunjukkan antarmuka yang disebut ILogger. Antarmuka ini akan diimplementasikan oleh dua kelas konkret FileLogger dan DatabaseLogger.

antarmuka publik ILogger

{

    void Log (pesan string);

}

Versi terbaru dari kelas FileLogger dan DatabaseLogger diberikan di bawah ini.

kelas publik FileLogger: ILogger

    {

        public void Log (pesan string)

        {

            Console.WriteLine ("Metode Log Di Dalam FileLogger.");

            LogToFile (pesan);

        }

        private void LogToFile (pesan string)

        {

            Console.WriteLine ("Metode: LogToFile, Teks: {0}", pesan);

        }

    }

kelas publik DatabaseLogger: ILogger

    {

        public void Log (pesan string)

        {

            Console.WriteLine ("Metode Inside Log dari DatabaseLogger.");

            LogToDatabase (pesan);

        }

        private void LogToDatabase (pesan string)

        {

            Console.WriteLine ("Metode: LogToDatabase, Teks: {0}", pesan);

        }

    }

Anda sekarang dapat menggunakan atau mengubah implementasi konkret dari antarmuka ILogger kapan pun diperlukan. Cuplikan kode berikut menunjukkan kelas ProductService dengan implementasi metode Log.

ProductService kelas publik

    {

        public void Log (pesan string)

        {

            ILogger logger = new FileLogger ();

            logger.Log (pesan);

        }

    }

Sejauh ini bagus. Namun, bagaimana jika Anda ingin menggunakan DatabaseLogger sebagai pengganti FileLogger dalam metode Log kelas ProductService? Anda bisa mengubah implementasi metode Log di kelas ProductService untuk memenuhi persyaratan, tetapi itu tidak membuat desain menjadi fleksibel. Sekarang mari membuat desain lebih fleksibel dengan menggunakan inversi kontrol dan injeksi ketergantungan.

Balikkan kontrol menggunakan injeksi ketergantungan

Cuplikan kode berikut menggambarkan bagaimana Anda dapat memanfaatkan injeksi ketergantungan untuk meneruskan instance kelas logger beton menggunakan injeksi konstruktor.

ProductService kelas publik

    {

        pribadi hanya baca ILogger _logger;

        ProductService publik (pencatat ILogger)

        {

            _logger = logger;

        }

        public void Log (pesan string)

        {

            _logger.Log (pesan);

        }

    }

Terakhir, mari kita lihat bagaimana kita bisa meneruskan implementasi antarmuka ILogger ke kelas ProductService. Cuplikan kode berikut menunjukkan bagaimana Anda dapat membuat instance dari kelas FileLogger dan menggunakan injeksi konstruktor untuk meneruskan dependensi.

static void Main (string [] args)

{

    ILogger logger = new FileLogger ();

    ProductService productService = ProductService baru (pencatat);

    productService.Log ("Halo Dunia!");

}

Saat melakukannya, kami telah membalikkan kontrol. Kelas ProductService tidak lagi bertanggung jawab untuk membuat instance implementasi antarmuka ILogger atau bahkan memutuskan implementasi antarmuka ILogger mana yang harus digunakan.

Kontrol pembalikan dan injeksi ketergantungan membantu Anda dengan pembuatan instance otomatis dan manajemen siklus proses objek Anda. ASP.NET Core menyertakan inversi wadah kontrol bawaan yang sederhana dengan serangkaian fitur terbatas. Anda dapat menggunakan wadah IoC bawaan ini jika kebutuhan Anda sederhana atau menggunakan wadah pihak ketiga jika Anda ingin memanfaatkan fitur tambahan.

Anda dapat membaca lebih lanjut tentang cara bekerja dengan inversi kontrol dan injeksi ketergantungan di ASP.NET Core di posting saya sebelumnya di sini.