Neden arayüz açıkça uygulanmalıdır?


122

Öyleyse, bir arabirimi açıkça uygulamak için iyi bir kullanım durumu tam olarak nedir?

Sadece sınıfı kullanan insanların intellisense'deki tüm bu yöntemlere / özelliklere bakmasına gerek kalmaması için mi?

Yanıtlar:


146

Hem aynı yöntemle hem de farklı uygulamalarla iki arabirim uygularsanız, açıkça uygulamanız gerekir.

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

    void IDoItSlow.Go()
    {
    }
}

Evet tam olarak bu EIMI'nin çözdüğü bir durum. Ve diğer noktalar "Michael B" cevabının kapsamındadır.
20'de şifrelenmiş

10
Mükemmel örnek. Arayüz / sınıf adlarını seviyorum! :-)
Brian Rogers

11
Sevmiyorum, bir sınıfta çok farklı şeyler yapan aynı imzaya sahip iki yöntem? Bu son derece tehlikeli bir şey ve büyük olasılıkla herhangi bir büyük gelişmede hasara neden olacak. Böyle bir kodunuz varsa, analizinizin ve tasarımınızın wahzoo olduğunu söyleyebilirim.
Mick

4
@Mike Arayüzler bazı API'lere veya iki farklı API'ye ait olabilir. Belki aşk burada biraz abartılıyor ama en azından açık uygulamanın mevcut olmasına seviniyorum.
TobiMcNamobi

@BrianRogers Ve yöntem isimleri de ;-)
Sнаđошƒаӽ

66

Tercih edilmeyen üyeyi gizlemek faydalıdır. Örneğin, her ikisini de uygularsanız IComparable<T>ve insanlara farklı türdeki nesneleri karşılaştırabileceğiniz izlenimi vermemek IComparableiçin IComparableaşırı yükü gizlemek genellikle daha hoş olur . Benzer şekilde, bazı arabirimler CLS uyumlu değildir IConvertible, bu nedenle arabirimi açık bir şekilde uygulamazsanız, CLS uyumluluğu gerektiren dillerin son kullanıcıları nesnenizi kullanamaz. (BCL uygulayıcıları ilkellerin IConvertible üyelerini gizlemeseydi bu çok feci olurdu :))

Bir başka ilginç not ise, normalde böyle bir yapının kullanılması, bir arabirimi açıkça uygulayan yapının yalnızca arabirim türüne göre kutulama yaparak çağırabileceği anlamına gelir. Genel kısıtlamaları kullanarak bunun üstesinden gelebilirsiniz:

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

Bir int verdiğinizde kutuya girmeyecektir.


1
Kısıtlamanızda bir yazım hatası var. Doğrulamak için yukarıdaki kod çalışıyor. Arayüzdeki yöntem imzasının ilk bildiriminde olması gerekir. Orijinal gönderi bunu belirtmedi. Ayrıca, uygun format "void SomeMehtod <T> (T obj) şeklindedir burada T: IConvertible. Not,") "ve" nerede "arasında olmaması gereken fazladan bir iki nokta üst üste vardır. Yine de, akıllıca kullanımı için +1 Pahalı bokstan kaçınmak için jenerikler.
Zack Jannsen

1
Merhaba Michael B. Öyleyse neden .NET'te string uygulamasında IComparable'ın genel uygulaması var: public int CompareTo (Object value) {if (value == null) {return 1; } if (! (değer String)) {throw new ArgumentException (Environment.GetResourceString ("Arg_MustBeString")); } dönüş String.Compare (this, (String) değer, StringComparison.CurrentCulture); } Teşekkürler!
zzfima

stringjenerik ilaçlardan önce ortalıktaydı ve bu uygulama revaçtaydı. .Net 2 geldiğinde, genel arayüzünü kırmak istemediler, bu stringyüzden koruma önlemi yerinde olduğu gibi bıraktılar.
Michael B

37

Bir arabirimi açıkça uygulamak için bazı ek nedenler:

geriye dönük uyumluluk : ICloneableArabirimin değişmesi durumunda, yöntem sınıfı üyelerinin uygulanması yöntem imzalarını değiştirmek zorunda değildir.

temiz kod : Cloneyöntem ICloneable'dan kaldırılırsa bir derleyici hatası olur , ancak yöntemi örtük olarak uygularsanız kullanılmayan 'öksüz' genel yöntemlerle sonuçlanabilirsiniz

Güçlü tipleme : Supercat'ın hikayesini bir örnekle göstermek için, bu benim tercih ettiğim örnek koddur, ICloneableaçıkça uygulamak, Clone()onu doğrudan bir MyObjectörnek üyesi olarak çağırdığınızda güçlü bir şekilde yazılmasına izin verir :

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

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

Bunun için tercih ederim interface ICloneable<out T> { T Clone(); T self {get;} }. ICloneable<T>T üzerinde kasıtlı olarak hiçbir kısıtlama olmadığına dikkat edin. Bir nesne genellikle yalnızca tabanı olabiliyorsa güvenli bir şekilde klonlanabilirken, bir kişi, güvenli bir şekilde klonlanamayan bir sınıf nesnesini klonlayabilen bir temel sınıftan türetmek isteyebilir. Buna izin vermek için, devralınabilir sınıfların genel bir klon yöntemini açığa çıkarmasını önermiyorum. Bunun yerine, bir protectedklonlama yöntemine sahip devralınabilir sınıflara ve bunlardan türetilen ve genel klonlamayı ortaya çıkaran mühürlenmiş sınıflara sahip olun.
supercat

Elbette bu daha iyi olurdu, ancak BCL'de ICloneable'ın eşdeğişken sürümü yok, bu yüzden bir tane oluşturmanız gerekir, değil mi?
Wiebe Tijsma

3 örneğin tümü, olası olmayan durumlara ve ara arabirimlerin en iyi uygulamalarına bağlıdır.
MickyD

13

Bir başka yararlı teknik, bir fonksiyonun bir yöntemin genel uygulamasının bir arayüzde belirtilenden daha spesifik bir değer döndürmesini sağlamaktır.

Örneğin, bir nesne uygulayabilir ICloneable, ancak yine de genel olarak görülebilen Cloneyöntemi kendi türünü döndürür.

Benzer şekilde, bir, bir döndüren bir yönteme sahip IAutomobileFactoryolabilir , ancak uygulayan a , kendi yönteminin dönüşüne ( a) sahip olabilir (türetilen ). A tarafından döndürülen bir nesnede kullanabileceğine özgü özelliklere sahip olduğunu bilen kod, yalnızca bir tür olduğunu bilen kod , bir .ManufactureAutomobileFordExplorerFactoryIAutomobileFactoryManufactureFordExplorerAutomobileFordExplorerFactoryFordExplorerFordExplorerFactoryIAutomobileFactoryAutomobile


2
+1 ... bu, açık arayüz uygulamalarını tercih ettiğim kullanım olabilir, ancak küçük bir kod örneği muhtemelen bu hikayeden biraz daha açık olacaktır :)
Wiebe Tijsma

7

Aynı üye adı ve imzasına sahip iki arabiriminiz olduğunda, ancak nasıl kullanıldığına bağlı olarak davranışını değiştirmek istediğinizde de yararlıdır. (Böyle bir kod yazmayı önermiyorum):

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
Şimdiye kadar gördüğüm en garip OO ile ilgili örnek:public class Animal : Cat, Dog
mbx

38
@mbx: Animal, Parrot'ı da uygulasaydı, Polly-morphing bir hayvan olurdu.
RenniePet

3
Bir ucunda Kedi, diğer ucunda Köpek olan bir çizgi film karakterini hatırlıyorum ;-)
George Birbilis

2
80'lerde bir dizi vardı .. "Manimal" .. bir adamın bir .. oh boşver
bkwdesign

Morkies bir tür kediye benziyor ve ninja kedisi gibi.
samis

6

Bir arabirimi açıkça uygulamak için genel arabirimi daha temiz tutabilir, yani Filesınıfınız IDisposableaçıkça uygulayabilir ve Close()bir tüketiciye göre daha anlamlı olabilecek bir genel yöntem sağlayabilir Dispose().

F # yalnızca açık arabirim uygulaması sunar, bu nedenle işlevine erişmek için her zaman belirli arabirime çevirmeniz gerekir, bu da arabirimin çok açık (amaçsız) kullanımını sağlar.


VB'nin çoğu sürümünün yalnızca açık arabirim tanımlarını desteklediğini düşünüyorum.
Gabe

1
@Gabe - VB için olduğundan daha incelikli - bir arayüz uygulayan üyelerin adlandırılması ve erişilebilirliği, uygulamanın bir parçası olduklarını belirtmekten ayrıdır. Dolayısıyla, VB'de ve @ Iain'in cevabına (mevcut en iyi cevap) bakarak, IDoItFast ve IDoItSlow'u sırasıyla genel üyeler "GoFast" ve "GoSlow" ile uygulayabilirsiniz.
Damien_The_Unbeliever

2
Sizin özel örneğinizi beğenmedim (IMHO, saklanması gereken tek şey Disposeasla temizlik gerektirmeyecek olanlardır); daha iyi bir örnek, değişmez bir koleksiyonun uygulaması gibi bir şey olabilir IList<T>.Add.
supercat

5

Dahili bir arayüzünüz varsa ve üyeleri sınıfınızda herkese açık olarak uygulamak istemiyorsanız, bunları açıkça uygularsınız. Örtük uygulamaların halka açık olması gerekir.


Tamam, bu projenin neden örtük bir uygulama ile derlenmediğini açıklıyor.
pauloya

4

Açık uygulamanın bir başka nedeni de sürdürülebilirliktir .

Bir sınıf "meşgul" olduğunda - evet öyle, hepimiz diğer ekip üyelerinin kodunu yeniden düzenleme lüksüne sahip değiliz - o zaman açık bir uygulamaya sahip olmak, bir arayüz sözleşmesini yerine getirmek için bir yöntemin orada olduğunu açıkça ortaya koyuyor.

Böylece kodun "okunabilirliğini" geliştirir.


IMHO, sınıfın yöntemi müşterilerine göstermesi gerekip gerekmediğine karar vermek daha önemlidir. Bu, ister açık ister örtük olsun. Birkaç yöntemin birbirine ait olduğunu belgelemek için, bu durumda bir sözleşmeyi karşıladıkları için - #regionuygun bir başlık dizesiyle bunun için olan şey budur. Ve yöntem hakkında bir yorum.
ToolmakerSteve

1

System.Collections.ImmutableYazarların, yeni türleri için hiçbir anlam taşımayan arayüz parçalarını sıyırırken koleksiyon türleri için tanıdık bir API'yi korumak için tekniği kullanmayı seçtikleri farklı bir örnekle verilmiştir .

Somut olarak, ImmutableList<T>uygular IList<T>, böylece ve ICollection<T>( sırayla izin ImmutableList<T>henüz eski kodu ile daha kolay kullanılmak üzere) void ICollection<T>.Add(T item)bir anlamsızdır ImmutableList<T>, mevcut listesini değiştirmek gerekir değişmez bir listeye bir öğe ekleyerek tarihi: ImmutableList<T>ayrıca türeten gelen IImmutableList<T>kimin IImmutableList<T> Add(T item)için kullanılabilir değişmez listeler.

Dolayısıyla, söz konusu Adduygulamalarda ImmutableList<T>aşağıdaki gibi sonuçlanır:

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

Açıkça tanımlanmış arabirimler olması durumunda, tüm yöntemler otomatik olarak özeldir, bunlara erişim değiştiriciyi genel olarak veremezsiniz. varsayalım:

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
"tüm yöntemler otomatik olarak özeldir" - bu teknik olarak doğru değildir, b / c eğer gerçekten özel olsalardı, o zaman hiç çağrılabilir olmazlardı, çevrim yapsalar da değiller.
samis

0

Açık Arabirimi bu şekilde oluşturabiliriz: Eğer 2 arabirimimiz varsa ve her iki arabirim de aynı yönteme sahipse ve tek bir sınıf bu 2 arabirimi miras alır, bu nedenle bir arabirim yöntemini çağırdığımızda derleyici hangi yöntemin çağrılacağını karıştırır, böylece yapabiliriz Explicit Interface kullanarak bu sorunu yönetin. İşte aşağıda verdiğim bir örnek.

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();    
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.