Yazar herhangi bir uygulamaya arayüz referansı vererek ne anlama geliyor?


17

Şu anda C # konusunda uzmanlaşmaya çalışıyorum, bu yüzden Gary McLean Hall tarafından C # ile Adaptive Code okuyorum .

Desenler ve anti-desenler hakkında yazıyor. Uygulamalara karşı arayüzler kısmında şunları yazar:

Arayüzlere programlama kavramında yeni olan geliştiriciler genellikle arayüzün arkasındaki şeyleri bırakmakta zorluk çekerler.

Derleme zamanında, bir arabirimin herhangi bir istemcisinin, hangi arabirimin uygulandığını bilmemesi gerekir. Bu tür bilgiler, müşteriyi arayüzün belirli bir uygulamasına bağlayan yanlış varsayımlara yol açabilir.

Bir sınıfın kalıcı depolamada bir kayıt kaydetmesi gereken yaygın örneği düşünün. Bunu yapmak için, haklı olarak kullanılan kalıcı depolama mekanizmasının ayrıntılarını gizleyen bir arayüze delege eder. Ancak, arabirimin hangi uygulamasının çalışma zamanında kullanıldığına dair herhangi bir varsayımda bulunmak doğru olmaz. Örneğin, herhangi bir uygulamaya arabirim referansı vermek her zaman kötü bir fikirdir.

Dil engeli ya da deneyim eksikliğim olabilir, ama bunun ne anlama geldiğini tam olarak anlamıyorum. İşte anladıklarım:

C # uygulamak için serbest zaman eğlenceli bir projem var. Orada bir sınıfım var:

public class SomeClass...

Bu sınıf birçok yerde kullanılıyor. C # öğrenirken, bir arayüz ile soyutlamanın daha iyi olduğunu okudum, bu yüzden aşağıdakileri yaptım

public interface ISomeClass <- Here I made a "contract" of all the public methods and properties SomeClass needs to have.

public class SomeClass : ISomeClass <- Same as before. All implementation here.

Bu yüzden tüm sınıf referanslarına girdim ve bunları ISomeClass ile değiştirdim.

Ben yazdım inşaat hariç:

ISomeClass myClass = new SomeClass();

Bunun yanlış olduğunu doğru mu anlıyorum? Evet ise, neden öyleyse bunun yerine ne yapmalıyım?


25
Örneğinizin hiçbir yerinde uygulama türüne bir arayüz türü nesnesi yayınlamıyorsunuz. Bir arayüz değişkenine mükemmel ve doğru olan bir tür uygulama ataıyorsunuz.
Caleth

1
Ne demek istiyorsun "yazdığım kurucuda ISomeClass myClass = new SomeClass();? Gerçekten demek istiyorsan, bu yapıcıda özyineleme, muhtemelen istediğin değil. ?
Erik eidt

@Erik: Evet. Yapım aşamasında. Haklısın. Soruyu düzeltir. Teşekkürler
Marshall

Eğlenceli gerçek: F # bu konuda C # 'dan daha iyi bir hikayeye sahiptir - örtük arayüz uygulamaları ile ortadan kalkar, bu yüzden bir arayüz yöntemini çağırmak istediğinizde, arayüz türüne yayın yapmanız gerekir. Bu, kodunuzda arayüzleri ne zaman ve nasıl kullandığınızı çok açık hale getirir ve arayüzlere programlamayı dilde çok daha yerleşik hale getirir.
scrwtp

3
Bu biraz konu dışı, ancak yazarın konsepte yeni insanların sahip olduğu sorunu yanlış tanıdığını düşünüyorum. Bence sorun, konsepte yeni insanların iyi arayüzler yapmayı bilmemeleri. Aslında herhangi bir genelliği sağlamayan çok spesifik arayüzler yapmak çok kolaydır (ki bu da iyi bir şekilde gerçekleşebilir ISomeClass), ancak hangi seçeneklere karşı tek bir noktada yararlı kod yazmanın imkansız olduğu çok genel arayüzler yapmak da kolaydır. arabirimi yeniden düşünmek ve kodu yeniden yazmak veya yok etmek içindir.
Derek Elkins SE

Yanıtlar:


37

Sınıfınızı bir arabirime dönüştürmek, ancak söz konusu arabirimin diğer uygulamalarını yazmak istiyorsanız veya gelecekte bunu yapma olasılığınız varsa dikkate almanız gereken bir şeydir.

Belki de SomeClassve ISomeClasskötü bir örnek, çünkü bir OracleObjectSerializersınıfa ve bir IOracleObjectSerializerarayüze sahip olmak gibi bir şey olurdu .

Daha doğru bir örnek OracleObjectSerializerve a IObjectSerializer. Programınızda hangi uygulamayı kullanacağınızı önemsediğiniz tek örnek, örnek oluşturulduğunda gerçekleşir. Bazen bu, fabrika deseni kullanılarak daha da ayrıştırılır.

Programınızdaki her yerde IObjectSerializernasıl çalıştığını önemsememek gerekir . Bir saniye daha varsayalım ki bir SQLServerObjectSerializeruygulama daha var OracleObjectSerializer. Şimdi, ayarlamak için bazı özel özellikler ayarlamanız gerektiğini ve bu yöntemin yalnızca OracleObjectSerializer'da mevcut olduğunu ve SQLServerObjectSerializer'da olmadığını varsayalım.

Bununla ilgili iki yol vardır: yanlış yol ve Liskov ikame ilkesi yaklaşımı.

Yanlış yol

Yanlış yol ve kitabınızda atıfta bulunulan örnek, bir örneğini alıp bu dosyaya IObjectSerializeryayınlayıp yalnızca kullanılabilir OracleObjectSerializerolan yöntemi çağırmak setPropertyolacaktır OracleObjectSerializer. Bu kötü bir durumdur, çünkü bir örneği bir örnek olarak bilseniz bileOracleObjectSerializer , programınızda bunun hangi uygulamanın olduğunu bilmek istediğiniz başka bir noktayı daha tanıtırsınız. Bu uygulama değiştiğinde ve muhtemelen birden fazla uygulamanız, en iyi durum senaryosunuz varsa er ya da geç olacaktır, tüm bu yerleri bulmanız ve doğru ayarlamaları yapmanız gerekecektir. En kötü senaryo, bir IObjectSerializerörneğini OracleObjectSerializeryayınlarsınız ve üretimde bir çalışma zamanı hatası alırsınız.

Liskov İkame İlkesi yaklaşımı

Liskov, uygun şekilde yapıldığımda setPropertyolduğu gibi uygulama sınıfında olduğu gibi yöntemlere asla ihtiyacınız olmaması gerektiğini söyledi OracleObjectSerializer. Eğer soyut bir sınıf ise OracleObjectSerializerhiç IObjectSerializeryapamazsın, sen o sınıfı kullanmak için gerekli tüm yöntemleri kapsar ve gerektiği, sonra bir şey (bir yapmaya çalışıyorum sizin soyutlama ile yanlış Dogbir şekilde sınıf çalışması IPersonörneği için uygulanması).

Doğru yaklaşım, bir setPropertyyöntem sağlamak olacaktır IObjectSerializer. Benzer yöntemler SQLServerObjectSerializerideal olarak bu setPropertyyöntemle işe yarayacaktır . Daha da iyisi, özellik adlarını, Enumher uygulamanın bu numaralandırmayı kendi veritabanı terminolojisine eşdeğer hale getirdiği bir yerde standartlaştırırsınız .

Basitçe söylemek gerekirse, a'nın ISomeClasskullanımı sadece yarısıdır. Asla, yaratılmasından sorumlu olan yöntemin dışına dökmeniz gerekmez. Bunu yapmak neredeyse kesinlikle ciddi bir tasarım hatasıdır.


1
Döküm olacak eğer geliyor bana IObjectSerializerkarşı OracleObjectSerializerbu ne olduğunu “bilmek” çünkü, o zaman geleceğinizi içerebilen bu kodu muhafaza edebilir başkalarıyla Kendine karşı dürüst (ve daha da önemlisi olmalıdır kendiliğinden) ve OracleObjectSerializeroluşturulduğu yerden kullanıldığı yere kadar tüm yolu kullanın . Bu, belirli bir uygulamaya bağımlı olduğunuzu çok açık ve net hale getirir - ve bunu yapmakla ilgili iş ve çirkinlik, bir şeyin yanlış olduğuna dair güçlü bir ipucu haline gelir.
KRyan

Eğer gerçekten bir nedenle eğer (Ve do belirli uygulanmasına güvenmek zorunda, bu ne yaptığınızı ve niyet ve amaç ile yapıyor olmasıdır bunu çok daha net hale gelir. Bu tabii asla “gerektiği”, ve zamanın% 99'u gerçekte görünmüyor gibi görünebilir ve bir şeyleri düzeltmelisiniz, ancak hiçbir şey% 100 kesin değildir veya işlerin olması gerektiği gibi değildir.)
KRyan

@KRyan Kesinlikle. Soyutlama sadece buna ihtiyacınız varsa kullanılmalıdır. Soyutlamanın gerekli olmadığında kullanılması, kodun anlaşılmasını biraz daha zor hale getirir.
Neil

29

Kabul edilen cevap doğru ve çok yararlıdır, ancak kısaca özellikle sorduğunuz kod satırını ele almak istiyorum:

ISomeClass myClass = new SomeClass();

Genel olarak, bu korkunç değil. Mümkün olduğunda kaçınılması gereken şey bunu yapmak olacaktır:

void someMethod(ISomeClass interface){
    SomeClass cast = (SomeClass)interface;
}

Kodunuz dışarıdan bir arayüz sağlandığında, ancak dahili olarak belirli bir uygulamaya çevirirse, "Çünkü bunun sadece bu uygulama olacağını biliyorum". Bu doğru olsa bile, bir arayüz kullanarak ve bir uygulamaya dökerek gönüllü olarak gerçek tip güvenlikten vazgeçiyorsunuz, böylece soyutlama gibi davranabilirsiniz. Başka biri daha sonra kod üzerinde çalışacak ve bir arabirim parametresini kabul eden bir yöntem görecek olsaydı, o arabirimin herhangi bir uygulamasının geçmek için geçerli bir seçenek olduğunu varsayacaklar. belirli bir yöntemin hangi parametrelere ihtiyaç duyduğunu unuttuğunu unutmayın. Bir arayüzden belirli bir uygulamaya geçiş yapma gereğini hissederseniz, arayüz, uygulama, veya bunlara referans veren kod yanlış tasarlanmış ve değişmelidir. Örneğin, yöntem yalnızca iletilen bağımsız değişken belirli bir sınıf olduğunda çalışıyorsa, parametre yalnızca bu sınıfı kabul etmelidir.

Şimdi, yapıcı çağrınıza geri dönelim

ISomeClass myClass = new SomeClass();

dökümden kaynaklanan sorunlar gerçekten geçerli değildir. Bunların hiçbiri harici olarak açığa çıkmıyor gibi görünüyor, bu yüzden onunla ilişkili herhangi bir risk yok. Esasen, bu kod satırının kendisi, arayüzlerin başlamak üzere soyut olarak tasarlandığı bir uygulama detayıdır, bu nedenle harici bir gözlemci, yaptıkları ne olursa olsun aynı şekilde çalıştığını görür. Ancak, bu aynı zamanda bir arayüzün varlığından da bir şey kazanmaz. Türünüz myClassvar ISomeClass, ancak her zaman belirli bir uygulamaya atandığı için herhangi bir nedeni yok,SomeClass. Yalnızca yapıcı çağrısını değiştirerek veya bu değişkeni daha sonra farklı bir uygulamaya yeniden atayarak kodu kod içinde değiştirebilme gibi bazı küçük potansiyel avantajlar vardır, ancak değişkenin arabirime yazılması yerine başka bir yere yazılmaması gerekir. Bu modelin uygulanması, kodunuzun arabirimlerin faydalarının gerçek anlaşılmasından değil, arayüzlerin yalnızca rote tarafından kullanıldığı gibi görünmesini sağlar.


1
Apache Math bunu gerçekten can sıkıcı olabilen Cartesian3D Source, 286 satırı ile yapıyor .
J_F_B_M

1
Bu asıl soruyu ele alan doğru cevaptır.
Benjamin Gruenbaum

2

Kodunuzu kötü bir örnekle göstermek daha kolay olduğunu düşünüyorum:

public interface ISomeClass
{
    void DoThing();
}

public class SomeClass : ISomeClass
{
    public void DoThing()
    {
       // Mine for BitCoin
    }

}

public class AnotherClass : ISomeClass
{
    public void DoThing()
    {
        // Mine for oil
    }
    public Decimal Depth;
 }

 void main()
 {
     ISomeClass task = new SomeClass();

     task.DoThing(); //  This is good

     Console.WriteLine("Depth = {0}", ((AnotherClass)task).Depth); <-- The task object will not have this field
 }

Sorun şu ki, kodu ilk olarak yazdığınızda, muhtemelen bu arabirimin yalnızca bir uygulaması vardır, böylece döküm yine de çalışır, sadece gelecekte başka bir sınıf uygulayabilir ve sonra (örneğim gösterdiği gibi), kullandığınız nesnede bulunmayan verilere erişmeyi deneyin.


Neden merhaba, efendim. Sana ne kadar yakışıklı olduğunu söyleyen var mı?
Neil

2

Sadece netlik için, dökümü tanımlayalım.

Döküm, bir şeyi bir türden diğerine zorla dönüştürmektir. Yaygın bir örnek, bir tamsayı tipine bir kayan nokta sayısı atamaktır. Döküm yaparken belirli bir dönüşüm belirtilebilir, ancak varsayılan değer sadece bitleri yeniden yorumlamaktır.

İşte bu Microsoft dokümanlar sayfasından yayın yapmanın bir örneği .

// Create a new derived type.  
Giraffe g = new Giraffe();  

// Implicit conversion to base type is safe.  
Animal a = g;  

// Explicit conversion is required to cast back  
// to derived type. Note: This will compile but will  
// throw an exception at run time if the right-side  
// object is not in fact a Giraffe.  
Giraffe g2 = (Giraffe) a;  

Sen olabilir uygular o arayüzün bir özel uygulama ile bir arayüz, ancak aynı şeyi ve dökme şey yapmak olmamalıdır beklediğinizden daha farklı bir uygulama kullanılırsa o bir hata veya beklenmeyen davranışlara neden olur çünkü.


1
"Döküm bir şeyi bir türden diğerine dönüştürüyor." - Hayır. Döküm, açıkça bir şeyi bir türden diğerine dönüştürüyor. (Özellikle "cast", bu dönüşümü belirtmek için kullanılan sözdiziminin adıdır.) Örtük dönüşümler döküm değildir. "Döküm yaparken belirli bir dönüşüm belirtilebilir, ancak varsayılan değer sadece bitleri yeniden yorumlamaktır." -- Kesinlikle değil. Bit kalıplarında önemli değişiklikler içeren hem örtük hem de açık birçok dönüşüm vardır.
hvd

@hvd Şu anda dökümün açıklığı konusunda bir düzeltme yaptım. Varsayılanın sadece bitleri yeniden yorumlamak olduğunu söylediğimde, kendi türünüzü yapacak olsaydınız, o zaman bir dökümün otomatik olarak tanımlandığı durumlarda, başka bir türe döktüğünüzde bitlerin yeniden yorumlanacağını ifade etmeye çalışıyordum . Gelen Animal/ Giraffeyapmak için olsaydı, yukarıdaki örnekte, Animal a = (Animal)g;bitler reinterpreted olacaktır (herhangi bir zürafa belirli bir veri "bu nesnenin parçası olmayan" olarak yorumlanır).
Ryan1729

DVD'nin söylediklerine rağmen, insanlar genellikle örtük dönüşümler için "döküm" terimini kullanırlar; bkz. örneğin https://www.google.com/search?q="implicit+cast"&tbm=bks . Teknik olarak, başkaları farklı kullandığında kafanız karışmadıkça, "dönüşüm" terimini açık dönüşümler için ayırmanın daha doğru olduğunu düşünüyorum.
ruakh

0

5 sentim:

Tüm bu örnekler iyi, ama gerçek dünya örnekleri değiller ve gerçek dünya niyetlerini göstermediler.

C # bilmiyorum bu yüzden soyut bir örnek (Java ve C ++ arasında karışımı) verecektir. Umarım bu tamam.

Arayüzünüz olduğunu varsayalım iList:

interface iList<Key,Value>{
   bool add(Key k, Value v);
   bool remove(Element e);
   Value get(Key k);
}

Şimdi bir çok uygulama olduğunu varsayın:

  • DynamicArrayList - sonunda takmak ve çıkarmak için hızlı düz dizi kullanır.
  • LinkedList - önde ve sonda hızlı eklemek için çift bağlantılı liste kullanır.
  • AVLTreeList - her şeyi hızlı yapmak için AVL Tree kullanır, ancak çok fazla bellek kullanır
  • SkipList - AVL Tree'den daha yavaş, her şeyi hızlı yapmak için SkipList kullanır, ancak daha az bellek kullanır.
  • HashList - HashTable kullanır

Bir çok farklı uygulama düşünülebilir.

Şimdi aşağıdaki kodu kullandığımızı varsayalım:

uint begin_size = 1000;
iList list = new DynamicArrayList(begin_size);

Kullanmak istediğimizi açıkça gösteriyor iList. Elbette artık DynamicArrayListbelirli işlemleri yapamıyoruz, ancak a iList.

Aşağıdaki kodu düşünün:

iList list = factory.getList();

Artık uygulamanın ne olduğunu bile bilmiyoruz. Bu son örnek genellikle diskten bir dosya yüklediğinizde ve dosya türüne (gif, jpeg, png, bmp ...) ihtiyacınız olmadığında görüntü işlemede kullanılır, ancak tek yapmanız gereken bazı görüntü manipülasyonları (flip, ölçek, sonunda png olarak kaydedin).


0

ISomeClass arabiriminiz var ve myObject öğeniz kodunuzdan ISomeClass'ı uygulamak için bildirilmesi dışında hiçbir şey bilmediğiniz bir nesneniz var.

ISomeClass arabirimini uyguladığını bildiğiniz bir sınıf SomeClass var. ISomeClass'ı uyguladığı bildirildiğinden veya ISomeClass'ı uygulamak için kendiniz uyguladığınızdan emin olabilirsiniz.

MyClass'ı SomeClass'a yayınlamanın nesi yanlış? İki şey yanlış. Birincisi, myClass'ın SomeClass'a (SomeClass'ın bir örneği veya SomeClass'ın bir alt sınıfı) dönüştürülebilecek bir şey olduğunu bilmiyorsunuz, bu nedenle oyuncular yanlış gidebilir. İki, bunu yapmak zorunda değilsiniz. İSomeClass olarak bildirilen myClass ile çalışmalı ve ISomeClass yöntemlerini kullanmalısınız.

SomeClass nesnesini aldığınız nokta bir arabirim yöntemi çağrıldığındadır. Bir noktada, arabirimde bildirilen ancak SomeClass'ta ve elbette ISomeClass'ı uygulayan diğer birçok sınıfta bir uygulaması olan myClass.myMethod () öğesini çağırırsınız. Bir çağrı SomeClass.myMethod kodunuzda sonlanırsa, kendiliğin SomeClass örneği olduğunu bilirsiniz ve bu noktada bir SomeClass nesnesi olarak kullanmak kesinlikle iyidir. Tabii ki bu aslında SomeClass yerine OtherClass örneğiyse, SomeClass koduna ulaşmazsınız.

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.