Mengapa menerapkan antarmuka secara eksplisit?


122

Jadi, apa sebenarnya kasus penggunaan yang baik untuk mengimplementasikan antarmuka secara eksplisit?

Apakah hanya agar orang yang menggunakan kelas tidak perlu melihat semua metode / properti tersebut di intellisense?

Jawaban:


146

Jika Anda mengimplementasikan dua antarmuka, dengan metode yang sama dan implementasi yang berbeda, Anda harus mengimplementasikan secara eksplisit.

public interface IDoItFast
{
    void Go();
}
public interface IDoItSlow
{
    void Go();
}
public class JustDoIt : IDoItFast, IDoItSlow
{
    void IDoItFast.Go()
    {
    }

    void IDoItSlow.Go()
    {
    }
}

Ya, sebenarnya ini adalah salah satu kasus yang EIMI selesaikan. Dan poin lainnya tercakup dalam jawaban "Michael B".
crypted

10
Contoh yang bagus. Suka nama antarmuka / kelas! :-)
Brian Rogers

11
Saya tidak menyukainya, dua metode dengan tanda tangan yang sama di kelas yang melakukan hal yang sangat berbeda? Ini adalah hal yang sangat berbahaya dan kemungkinan besar akan menyebabkan malapetaka dalam perkembangan besar apa pun. Jika Anda memiliki kode seperti ini, saya akan mengatakan analisis dan desain Anda wahzoo.
Mick

4
@Mike Antarmuka mungkin milik beberapa API atau dua API yang berbeda. Mungkin cinta agak dibesar-besarkan di sini, tetapi setidaknya saya senang bahwa penerapan eksplisit tersedia.
TobiMcNamobi

@BrianRogers Dan nama metode juga ;-)
Sнаđошƒаӽ

66

Ini berguna untuk menyembunyikan anggota yang tidak disukai. Misalnya, jika Anda menerapkan keduanya IComparable<T>dan IComparablebiasanya lebih baik menyembunyikan IComparablekelebihan beban agar tidak memberi kesan kepada orang-orang bahwa Anda dapat membandingkan objek dari jenis yang berbeda. Demikian pula, beberapa antarmuka tidak sesuai dengan CLS, misalnya IConvertible, jadi jika Anda tidak mengimplementasikan antarmuka secara eksplisit, pengguna akhir bahasa yang memerlukan kepatuhan CLS tidak dapat menggunakan objek Anda. (Yang akan sangat berbahaya jika pelaksana BCL tidak menyembunyikan anggota IConvertible dari primitif :))

Catatan menarik lainnya adalah bahwa biasanya menggunakan konstruksi seperti itu berarti struct yang secara eksplisit mengimplementasikan antarmuka hanya dapat memanggilnya dengan meninju ke tipe antarmuka. Anda bisa menyiasatinya dengan menggunakan batasan umum:

void SomeMethod<T>(T obj) where T:IConvertible

Tidak akan mengemas int saat Anda memberikannya.


1
Anda memiliki kesalahan ketik dalam kendala Anda. Untuk klerifikasi, kode di atas berfungsi. Ini harus dalam deklarasi awal dari tanda tangan metode di Antarmuka. Posting asli tidak menyebutkan ini. Selain itu, format yang tepat adalah "void SomeMehtod <T> (T obj) di mana T: IConvertible. Perhatikan, ada titik dua tambahan antara") "dan" di mana "yang seharusnya tidak ada. Tetap, +1 untuk penggunaan cerdas dari obat generik untuk menghindari tinju yang mahal
Zack Jannsen

1
Hi Michael B. Jadi mengapa dalam implementasi string di NET. Ada implementasi publik dari IComparable: public int CompareTo (Object value) {if (value == null) {return 1; } if (! (value is String)) {throw new ArgumentException (Environment.GetResourceString ("Arg_MustBeString")); } mengembalikan String.Compare (ini, (String) nilai, StringComparison.CurrentCulture); } Terima kasih!
zzfima

stringsudah ada sebelum obat generik dan praktik ini populer. Ketika .net 2 muncul, mereka tidak ingin merusak antarmuka publik stringsehingga mereka membiarkannya seperti itu dengan pengamanan di tempatnya.
Michael B

37

Beberapa alasan tambahan untuk mengimplementasikan antarmuka secara eksplisit:

kompatibilitas mundur : Jika ICloneableantarmuka berubah, anggota kelas yang menerapkan metode tidak perlu mengubah tanda tangan metode mereka.

kode pembersih : akan ada kesalahan kompiler jika Clonemetode tersebut dihapus dari ICloneable, namun jika Anda menerapkan metode secara implisit, Anda dapat berakhir dengan metode publik 'orphaned' yang tidak terpakai

pengetikan yang kuat : Untuk mengilustrasikan cerita supercat dengan sebuah contoh, ini akan menjadi kode sampel pilihan saya, menerapkan ICloneablesecara eksplisit memungkinkan Clone()untuk diketik dengan kuat ketika Anda memanggilnya secara langsung sebagai anggota MyObjectcontoh:

public class MyObject : ICloneable
{
  public MyObject Clone()
  {
    // my cloning logic;  
  }

  object ICloneable.Clone()
  {
    return this.Clone();
  }
}

Untuk yang itu, saya lebih suka interface ICloneable<out T> { T Clone(); T self {get;} }. Perhatikan bahwa tidak ada ICloneable<T>batasan pada T. Sementara sebuah objek umumnya hanya dapat dikloning dengan aman jika basisnya bisa, seseorang mungkin ingin mendapatkan dari kelas dasar yang dapat dengan aman mengkloning objek dari kelas yang tidak bisa. Untuk memungkinkan itu, saya merekomendasikan agar kelas yang diwariskan mengekspos metode klon publik. Sebaliknya memiliki kelas yang diwariskan dengan protectedmetode kloning dan kelas tertutup yang berasal dari mereka dan mengekspos kloning publik.
supercat

Tentu itu akan lebih bagus, kecuali tidak ada versi kovarian dari ICloneable di BCL, jadi Anda harus membuatnya, bukan?
Wiebe Tijsma

Ketiga contoh tersebut bergantung pada situasi yang tidak mungkin dan merusak praktik terbaik antarmuka.
MickyD

13

Teknik lain yang berguna adalah membuat implementasi publik suatu fungsi dari suatu metode mengembalikan nilai yang lebih spesifik daripada yang ditentukan dalam sebuah antarmuka.

Misalnya, sebuah objek dapat diimplementasikan ICloneable, tetapi masih memiliki Clonemetode yang terlihat secara publik mengembalikan tipenya sendiri.

Demikian juga, IAutomobileFactorymungkin memiliki Manufacturemetode yang mengembalikan Automobile, tetapi a FordExplorerFactory, yang mengimplementasikan IAutomobileFactory, mungkin memiliki Manufacturemetode mengembalikan a FordExplorer(yang berasal dari Automobile). Kode yang mengetahui bahwa ia memiliki FordExplorerFactorydapat menggunakan FordExplorerproperti -specific pada suatu objek yang dikembalikan oleh a FordExplorerFactorytanpa harus melakukan typecast, sedangkan kode yang hanya mengetahui bahwa ia memiliki beberapa jenis IAutomobileFactoryakan menangani pengembaliannya sebagai file Automobile.


2
+1 ... ini akan menjadi penggunaan yang saya sukai dari implementasi antarmuka eksplisit, meskipun sampel kode kecil mungkin akan sedikit lebih jelas daripada cerita ini :)
Wiebe Tijsma

7

Ini juga berguna ketika Anda memiliki dua antarmuka dengan nama anggota dan tanda tangan yang sama, tetapi ingin mengubah perilakunya tergantung bagaimana penggunaannya. (Saya tidak merekomendasikan menulis kode seperti ini):

interface Cat
{
    string Name {get;}
}

interface Dog
{
    string Name{get;}
}

public class Animal : Cat, Dog
{
    string Cat.Name
    {
        get
        {
            return "Cat";
        }
    }

    string Dog.Name
    {
        get
        {
            return "Dog";
        }
    }
}
static void Main(string[] args)
{
    Animal animal = new Animal();
    Cat cat = animal; //Note the use of the same instance of Animal. All we are doing is picking which interface implementation we want to use.
    Dog dog = animal;
    Console.WriteLine(cat.Name); //Prints Cat
    Console.WriteLine(dog.Name); //Prints Dog
}

60
Contoh terkait OO teraneh yang pernah saya lihat:public class Animal : Cat, Dog
mbx

38
@mbx: Jika Animal juga menerapkan Parrot, itu akan menjadi hewan Polly-morphing.
RenniePet

3
Saya ingat karakter kartun yang adalah Kucing di satu ujung dan Anjing di ujung lainnya ;-)
George Birbilis

2
ada serial TV di tahun 80-an .. "Manimal" .. di mana seorang pria bisa berubah menjadi .. oh tidak masalah
bkwdesign

Morkies terlihat seperti kucing, dan juga ninja-kucing.
samis

6

Hal ini dapat menjaga kebersihan antarmuka publik untuk mengimplementasikan antarmuka secara eksplisit, misalnya Filekelas Anda mungkin mengimplementasikan IDisposablesecara eksplisit dan menyediakan metode publik Close()yang mungkin lebih masuk akal bagi konsumen daripada Dispose().

F # saja menawarkan implementasi antarmuka eksplisit sehingga Anda selalu harus mentransmisikan ke antarmuka tertentu untuk mengakses fungsinya, yang membuat penggunaan antarmuka menjadi sangat eksplisit (tanpa maksud kata-kata).


Saya pikir sebagian besar versi VB juga hanya mendukung definisi antarmuka eksplisit.
Gabe

1
@Gabe - lebih halus daripada itu untuk VB - penamaan dan aksesibilitas anggota yang mengimplementasikan antarmuka terpisah dari yang menunjukkan bahwa mereka adalah bagian dari implementasi. Jadi di VB, dan melihat jawaban @ Iain (jawaban teratas saat ini), Anda dapat mengimplementasikan IDoItFast dan IDoItSlow dengan anggota publik "GoFast" dan "GoSlow", masing-masing.
Damien_The_Unbeliever

2
Saya tidak suka contoh khusus Anda (IMHO, satu-satunya hal yang harus disembunyikan Dispose adalah hal-hal yang tidak akan pernah membutuhkan pembersihan); contoh yang lebih baik adalah implementasi dari koleksi yang tidak dapat diubah IList<T>.Add.
supercat

5

Jika Anda memiliki antarmuka internal dan Anda tidak ingin menerapkan anggota di kelas Anda secara publik, Anda akan menerapkannya secara eksplisit. Implementasi implisit harus diketahui publik.


Oke, itu menjelaskan mengapa proyek tidak dapat dikompilasi dengan implementasi implisit.
pauloya

4

Alasan lain untuk implementasi eksplisit adalah untuk pemeliharaan .

Ketika kelas menjadi "sibuk" - ya itu terjadi, kita tidak semua memiliki kemewahan untuk memfaktorkan ulang kode anggota tim lain - kemudian memiliki implementasi eksplisit memperjelas bahwa ada metode di sana untuk memenuhi kontrak antarmuka.

Jadi itu meningkatkan "keterbacaan" kode.


IMHO lebih penting untuk memutuskan apakah kelas harus atau tidak seharusnya mengekspos metode tersebut kepada kliennya. Itu mendorong apakah akan eksplisit atau implisit. Untuk mendokumentasikan bahwa beberapa metode dimiliki bersama, dalam hal ini karena mereka memenuhi kontrak - untuk itulah #region, dengan string judul yang sesuai. Dan komentar tentang metodenya.
ToolmakerSteve

1

Contoh berbeda diberikan oleh System.Collections.Immutable, di mana penulis memilih untuk menggunakan teknik untuk mempertahankan API yang sudah dikenal untuk jenis koleksi sambil membuang bagian antarmuka yang tidak memiliki arti untuk jenis baru mereka.

Secara konkret, ImmutableList<T>mengimplementasikan IList<T>dan dengan demikian ICollection<T>( agar memungkinkan ImmutableList<T>untuk digunakan lebih mudah dengan kode warisan), namun void ICollection<T>.Add(T item)tidak masuk akal untuk ImmutableList<T>: karena menambahkan elemen ke daftar yang tidak dapat diubah tidak boleh mengubah daftar yang ada, ImmutableList<T>juga berasal dari IImmutableList<T>yang IImmutableList<T> Add(T item)dapat digunakan untuk daftar yang tidak dapat diubah.

Jadi dalam kasus Add, implementasi pada ImmutableList<T>akhirnya terlihat sebagai berikut:

public ImmutableList<T> Add(T item)
{
    // Create a new list with the added item
}

IImmutableList<T> IImmutableList<T>.Add(T value) => this.Add(value);

void ICollection<T>.Add(T item) => throw new NotSupportedException();

int IList.Add(object value) => throw new NotSupportedException();

0

Dalam kasus antarmuka yang ditentukan secara eksplisit, semua metode secara otomatis menjadi pribadi, Anda tidak dapat memberikan pengubah akses publik kepada mereka. Seharusnya:

interface Iphone{

   void Money();

}

interface Ipen{

   void Price();
}


class Demo : Iphone, Ipen{

  void Iphone.Money(){    //it is private you can't give public               

      Console.WriteLine("You have no money");
  }

  void Ipen.Price(){    //it is private you can't give public

      Console.WriteLine("You have to paid 3$");
  }

}


// So you have to cast to call the method


    class Program
    {
        static void Main(string[] args)
        {
            Demo d = new Demo();

            Iphone i1 = (Iphone)d;

            i1.Money();

            ((Ipen)i1).Price();

            Console.ReadKey();
        }
    }

  // You can't call methods by direct class object

1
"semua metode secara otomatis bersifat pribadi" - ini tidak benar secara teknis, b / c jika sebenarnya metode tersebut bersifat pribadi maka metode tersebut tidak akan dapat dipanggil sama sekali, mentransmisikan atau tidak.
samis

0

Beginilah cara kita membuat Antarmuka Eksplisit: Jika kita memiliki 2 antarmuka dan kedua antarmuka memiliki metode yang sama dan satu kelas mewarisi 2 antarmuka ini, sehingga ketika kita memanggil satu metode antarmuka, kompilator bingung metode mana yang akan dipanggil, jadi kita bisa mengelola masalah ini menggunakan Antarmuka Eksplisit. Berikut adalah salah satu contoh yang saya berikan di bawah ini.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace oops3
{
    interface I5
    {
        void getdata();    
    }
    interface I6
    {
        void getdata();    
    }

    class MyClass:I5,I6
    {
        void I5.getdata()
        {
           Console.WriteLine("I5 getdata called");
        }
        void I6.getdata()
        {
            Console.WriteLine("I6 getdata called");
        }
        static void Main(string[] args)
        {
            MyClass obj = new MyClass();
            ((I5)obj).getdata();                     

            Console.ReadLine();    
        }
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.