Menggunakan Antarmuka untuk Kode yang Digabungkan Secara Bebas

10

Latar Belakang

Saya memiliki proyek yang tergantung pada penggunaan jenis perangkat perangkat keras tertentu, sementara itu tidak terlalu penting siapa yang membuat perangkat perangkat keras selama itu melakukan apa yang saya butuhkan. Dengan itu dikatakan, bahkan dua perangkat yang seharusnya melakukan hal yang sama akan memiliki perbedaan ketika mereka tidak dibuat oleh produsen yang sama. Jadi saya berpikir untuk menggunakan antarmuka untuk memisahkan aplikasi dari merek / model perangkat tertentu yang terlibat, dan sebagai gantinya memiliki antarmuka hanya mencakup fungsionalitas tingkat tertinggi. Inilah yang saya pikirkan arsitektur saya akan terlihat seperti:

  1. Tentukan antarmuka dalam satu proyek C # IDevice.
  2. Memiliki beton di perpustakaan yang ditentukan dalam proyek C # lain, yang akan digunakan untuk mewakili perangkat.
  3. Apakah perangkat konkret mengimplementasikan IDeviceantarmuka.
  4. The IDeviceantarmuka mungkin memiliki metode seperti GetMeasurementatau SetRange.
  5. Buat aplikasi memiliki pengetahuan tentang beton, dan meneruskan beton ke kode aplikasi yang memanfaatkan ( bukan mengimplementasikan ) IDeviceperangkat.

Saya cukup yakin bahwa ini adalah cara yang tepat untuk melakukannya, karena dengan begitu saya akan dapat mengganti perangkat apa yang sedang digunakan tanpa mempengaruhi aplikasi (yang sepertinya kadang-kadang terjadi). Dengan kata lain, tidak masalah bagaimana implementasi GetMeasurementatau SetRangebenar - benar bekerja melalui beton (karena mungkin berbeda di antara produsen perangkat).

Satu-satunya keraguan dalam pikiran saya, adalah bahwa sekarang keduanya aplikasi, dan kelas konkret perangkat keduanya bergantung pada perpustakaan yang berisi IDeviceantarmuka. Tapi, apakah itu hal yang buruk?

Saya juga tidak melihat bagaimana aplikasi tidak perlu tahu tentang perangkat, kecuali perangkat dan IDeviceberada di namespace yang sama.

Pertanyaan

Apakah ini tampak seperti pendekatan yang tepat untuk mengimplementasikan antarmuka untuk memisahkan ketergantungan antara aplikasi saya dan perangkat yang digunakannya?

Mengintip
sumber
Ini benar-benar cara yang tepat untuk melakukannya. Pada dasarnya Anda memprogram driver perangkat, dan beginilah cara driver perangkat ditulis secara tradisional, dengan alasan yang bagus. Anda tidak dapat menyiasati kenyataan bahwa untuk menggunakan kapabilitas suatu perangkat, Anda harus mengandalkan kode yang mengetahui kapabilitas ini setidaknya secara abstrak.
Kilian Foth
@KilianFoth Ya saya punya perasaan, lupa menambahkan satu bagian ke pertanyaan saya. Lihat # 5.
Mengintai

Jawaban:

5

Saya pikir Anda sudah memiliki pemahaman yang cukup baik tentang cara kerja perangkat lunak yang dipisahkan :)

Satu-satunya keraguan dalam pikiran saya, adalah bahwa sekarang aplikasi, dan kelas konkret perangkat keduanya bergantung pada pustaka yang berisi antarmuka IDevice. Tapi, apakah itu hal yang buruk?

Tidak harus!

Saya juga tidak melihat bagaimana aplikasi tidak perlu tahu tentang perangkat, kecuali perangkat dan IDevice berada di namespace yang sama.

Anda dapat menangani semua masalah ini dengan Struktur Proyek .

Cara saya biasanya melakukannya:

  • Letakkan semua barang abstrak saya di Commonproyek. Sesuatu seperti MyBiz.Project.Common. Proyek lain bebas untuk merujuknya, tetapi mungkin tidak merujuk proyek lain.
  • Ketika saya membuat implementasi konkret dari abstraksi, saya meletakkannya di proyek terpisah. Sesuatu seperti MyBiz.Project.Devices.TemperatureSensors. Proyek ini akan merujuk Commonproyek.
  • Saya kemudian memiliki Clientproyek saya yang merupakan titik masuk ke aplikasi saya (sesuatu seperti MyBiz.Project.Desktop). Saat startup, aplikasi melewati proses Bootstrap di mana saya mengkonfigurasi pemetaan abstraksi / implementasi beton. Saya bisa instantiate IDevicesseperti beton saya WaterTemperatureSensordan di IRCameraTemperatureSensorsini, atau saya bisa mengkonfigurasi layanan seperti Pabrik atau wadah IoC untuk instantiate jenis beton yang tepat untuk saya nanti.

Kuncinya di sini adalah hanya Clientproyek Anda yang perlu mengetahui Commonproyek abstrak dan semua proyek implementasi konkret. Dengan membatasi abstrak-> pemetaan konkret ke kode Bootstrap Anda, Anda memungkinkan seluruh aplikasi Anda tidak menyadari jenis-jenis konkret.

Kode yang digabungkan dengan longgar :)

MetaFight
sumber
2
Saya memang mengikuti secara alami dari sini. Itu sebagian besar masalah struktur proyek / kode juga. Memiliki wadah IoC dapat membantu dengan DI tetapi itu bukan prasyarat. Anda dapat mencapai DI dengan menyuntikkan dependensi secara manual juga!
MetaFight
1
@StevieV Ya, jika objek Foo dalam aplikasi Anda memerlukan IDevice, inversi dependensi dapat sesederhana menyuntikkannya melalui konstruktor ( new Foo(new DeviceA());), daripada memiliki bidang pribadi dalam Foo instantiate DeviceA sendiri ( private IDevice idevice = new DeviceA();) - Anda masih mencapai DI, seperti Foo tidak menyadari DeviceA dalam kasus pertama
anotherdave
2
@Anotherdave input Anda dan MetaFight sangat membantu.
Mengintai
1
@StevieV Ketika Anda punya waktu, inilah intro yang bagus ke dalam Dependency Inversion sebagai konsep (dari "Paman Bob" Martin, lelaki yang menciptakannya), berbeda dari kerangka kerja Pembalikan Kontrol / Ketergantungan Injeksi, seperti Spring (atau lainnya contoh populer di dunia C♯ :))
anotherdave
1
@anotherdave Pasti berencana untuk memeriksanya, terima kasih lagi.
Mengintai
3

Ya, itu sepertinya pendekatan yang tepat. Tidak, itu bukan hal buruk bagi aplikasi dan pustaka perangkat untuk bergantung pada antarmuka, terutama jika Anda mengendalikan implementasi.

Jika Anda khawatir perangkat mungkin tidak selalu mengimplementasikan antarmuka, untuk alasan apa pun, Anda dapat memanfaatkan pola adaptor dan menyesuaikan implementasi konkret perangkat ke antarmuka.

EDIT

Dalam menyikapi keprihatinan kelima Anda, pikirkan struktur seperti ini (saya berasumsi Anda mengendalikan mendefinisikan perangkat Anda):

Anda memiliki perpustakaan inti. Di dalamnya ada antarmuka yang disebut IDevice.

Di pustaka perangkat Anda, Anda memiliki referensi ke pustaka inti Anda, dan Anda telah menentukan serangkaian perangkat yang semuanya mengimplementasikan IDevice. Anda juga memiliki pabrik yang tahu cara membuat berbagai jenis layanan.

Dalam aplikasi Anda, Anda menyertakan referensi ke perpustakaan inti dan perpustakaan perangkat. Aplikasi Anda sekarang menggunakan pabrik untuk membuat instance objek yang sesuai dengan antarmuka IDevice.

Ini adalah salah satu dari banyak cara yang mungkin untuk mengatasi masalah Anda.

CONTOH:

namespace Core
{
    public interface IDevice { }
}


namespace Devices
{
    using Core;

    class DeviceOne : IDevice { }

    class DeviceTwo : IDevice { }

    public class Factory
    {
        public IDevice CreateDeviceOne()
        {
            return new DeviceOne();
        }

        public IDevice CreateDeviceTwo()
        {
            return new DeviceTwo();
        }
    }
}

// do not implement IDevice
namespace ThirdrdPartyDevices
{

    public class ThirdPartyDeviceOne  { }

    public class ThirdPartyDeviceTwo  { }

}

namespace DeviceAdapters
{
    using Core;
    using ThirdPartyDevices;

    class ThirdPartyDeviceAdapterOne : IDevice
    {
        private ThirdPartyDeviceOne _deviceOne;

        // use the third party device to adapt to the interface
    }

    class ThirdPartyDeviceAdapterTwo : IDevice
    {
        private ThirdPartyDeviceTwo _deviceTwo;

        // use the third party device to adapt to the interface
    }

    public class AdapterFactory
    {
        public IDevice CreateThirdPartyDeviceAdapterOne()
        {
            return new ThirdPartyDeviceAdapterOne();
        }

        public IDevice CreateThirdPartyDeviceAdapterTwo()
        {
            return new ThirdPartyDeviceAdapterTwo();
        }
    }
}

namespace Application
{
    using Core;
    using Devices;
    using DeviceAdapters;

    class App
    {
        void RunInHouse()
        {
            var factory = new Factory();
            var devices = new List<IDevice>() { factory.CreateDeviceOne(), factory.CreateDeviceTwo() };
            foreach (var device in devices)
            {
                // call IDevice  methods.
            }
        }

        void RunThirdParty()
        {
            var factory = new AdapterFactory();
            var devices = new List<IDevice>() { factory.CreateThirdPartyDeviceAdapterOne(), factory.CreateThirdPartyDeviceAdapterTwo() };
            foreach (var device in devices)
            {
                // call IDevice  methods.
            }
        }
    }
}
Harga Jones
sumber
Jadi dengan implementasi ini, kemana perginya beton?
Mengintai
Saya hanya bisa berbicara untuk apa yang akan saya pilih untuk dilakukan. Jika Anda mengendalikan perangkat, Anda masih akan menempatkan perangkat konkret di perpustakaan mereka sendiri. Jika Anda tidak mengendalikan perangkat Anda akan membuat perpustakaan lain untuk adaptor.
Harga Jones
Saya kira satu-satunya adalah, bagaimana aplikasi mendapatkan akses ke beton spesifik yang saya butuhkan?
Mengintai
Salah satu solusinya adalah dengan menggunakan pola pabrik abstrak untuk membuat IDevices.
Harga Jones
1

Satu-satunya keraguan dalam pikiran saya, adalah bahwa sekarang aplikasi, dan kelas konkret perangkat keduanya bergantung pada pustaka yang berisi antarmuka IDevice. Tapi, apakah itu hal yang buruk?

Anda perlu berpikir sebaliknya. Jika Anda tidak melakukannya, aplikasi perlu tahu tentang semua perangkat / implementasi yang berbeda. Yang harus Anda ingat adalah, bahwa mengubah antarmuka itu dapat merusak aplikasi Anda jika sudah keluar dari alam, maka Anda harus merancang antarmuka tersebut dengan cermat.

Apakah ini tampak seperti pendekatan yang tepat untuk mengimplementasikan antarmuka untuk memisahkan ketergantungan antara aplikasi saya dan perangkat yang digunakannya?

Cukup ya

bagaimana beton benar-benar sampai ke aplikasi

Aplikasi ini dapat memuat perakitan beton (dll) saat runtime. Lihat: /programming/465488/can-i-load-a-net-assembly-at-runtime-and-instantiate-a-type-knowing-only-the-na

Jika Anda perlu secara dinamis membongkar / memuat "driver" tersebut, Anda perlu memuatnya ke dalam AppDomain yang terpisah .

Heslacher
sumber
Terima kasih, saya benar-benar berharap saya tahu lebih banyak tentang antarmuka ketika saya mulai coding.
Mengintai
Maaf, lupa menambahkan pada satu bagian (bagaimana beton benar-benar sampai ke aplikasi). Bisakah Anda mengatasinya? Lihat # 5.
Mengintai
Apakah Anda benar-benar melakukan aplikasi Anda dengan cara ini, memuat DLL saat dijalankan?
Mengintai
Jika misalnya kelas konkret tidak saya kenal maka ya. Asumsikan vendor perangkat memutuskan untuk menggunakan IDeviceantarmuka Anda sehingga perangkatnya dapat digunakan dengan aplikasi Anda, maka tidak akan ada cara lain IMO.
Heslacher