Yineleyici düzeni - dahili gösterimi göstermemek neden önemlidir?


24

C # Design Pattern Essentials okuyorum . Şu anda yineleyici düzeni hakkında okuyorum.

Nasıl uygulanacağını tamamen anlıyorum ama önemini anlamıyorum veya bir kullanım durumu görmüyorum. Kitapta, birinin nesnelerin listesini alması gereken bir örnek verilmiştir. Bunu, IList<T>ya da gibi bir kamu malını ifşa ederek yapabilirlerdi Array.

Kitap yazıyor

Bununla ilgili sorun, bu sınıfların her ikisinin de iç temsilinin dış projelere maruz kalmasıdır.

İçsel temsil nedir? Aslında bir mi arrayyoksa IList<T>? Bunun tüketici için neden bu kadar kötü bir şey olduğunu anlamıyorum.

Kitap daha sonra bu kalıbın GetEnumeratorişlevini göstererek çalıştığını söyler , böylece GetEnumerator()'listeyi' bu şekilde arayabilir ve gösterebiliriz .

Bu kalıpların belirli durumlarda (hepsinde olduğu gibi) bir yeri olduğunu kabul ediyorum, ancak nerede ve ne zaman olduğunu göremiyorum.



4
Çünkü uygulama, tüketici sınıfında herhangi bir değişiklik yapmadan bir dizi kullanmaktan, bağlantılı bir listeyi kullanmaktan geçmek isteyebilir. Tüketici geri dönen sınıfı tam olarak bilirse bu mümkün olmaz.
MTilsted

11
Tüketicinin bilmesi kötü bir şey değil, tüketicinin güvenmesi kötü bir şey . Bilgi gizleme terimini sevmiyorum, çünkü bu bilginin bir telefonun içi gibi özel değil, şifreniz gibi "özel" olduğuna inanmanıza neden oluyor. İç bileşenler gizlidir çünkü kullanıcıyla bir ilgisi yoktur, bir çeşit sır olduğundan dolayı. Bilmen gereken tek şey burada çevirmek, burada konuşmak, burada dinlemek.
Kaptan Adam

Biz güzel "arayüz" sahip olduğunu varsayalım Fbana bilgisine (yöntemleri) verir a, bve c. Her şey iyi ve güzel, farklı olan ama Fbenim için farklı şeyler olabilir . Yöntemi "kısıtlamalar" olarak düşünün veya bir sözleşmenin hükümleri, Fsınıfları yapmayı taahhüt eder. Bir şey eklediğimizi varsayalım d, çünkü buna ihtiyacımız var. Bu, ek bir kısıtlama ekler, bunu her yaptığımızda Fs'ye daha fazla empoze ederiz . Sonunda (en kötü durum) sadece bir şey olabilir, Fo yüzden de olmayabilir. Ve Fçok kısıtlayan bunu yapmanın tek bir yolu var.
Alec Teal

@ captainman, ne harika bir yorum. Evet, neden birçok şeyi 'soyutladığınızı' görüyorum, ama bilme ve güvenmeye ilişkin büküm ... Açıkçası ... Mükemmel. Ve gönderinizi okuyana kadar göz önünde bulundurmadığım önemli bir ayrım.
MyDaftQuestions

Yanıtlar:


56

Yazılım bir vaat ve ayrıcalık oyunudur. İş ortaklarınızdan daha fazlasını ya da iş ortaklarınızın ihtiyaçlarından fazlasını vaat etmek asla iyi bir fikir değildir.

Bu özellikle türler için geçerlidir. Yinelenebilir bir koleksiyon yazmanın amacı, kullanıcının üzerinde yineleyebileceğidir - daha fazla değil, daha az. Maruz beton türü Arraygenellikle, kendi seçtiği bir işlev tarafından toplanmasını sıralayabilir normal gerçeği söz değil birçok ek sözler, örneğin yaratır Arrayirade muhtemelen işbirlikçi izin değiştirmek içine depolanan verileri.

Bunun iyi bir şey olduğunu düşünseniz bile ("İşleyici yeni dışa aktarma seçeneğinin eksik olduğunu fark ederse, onu doğrudan düzeltebilir! Düzgün!"), Genel olarak bu, kod tabanının tutarlılığını azaltarak zorlaşmasını sağlar. nedeni - ve kodun nedenini basitleştirmek, yazılım mühendisliğinin en önemli amacıdır.

Şimdi, eğer ortak çalışanınızın herhangi bir şeyi kaçırmamalarını garanti altına almak için birkaç şeye erişmesi gerekiyorsa, bir Iterablearayüz uygular ve yalnızca bu arayüzün bildirdiği yöntemleri ortaya çıkarırsınız. Bu şekilde, gelecek yıl standart kütüphanenizde çok daha iyi ve daha verimli bir veri yapısı ortaya çıktığında, müşteri kodunuzu her yerde sabitlemeden temeldeki kodu değiştirebilecek ve bundan yararlanabileceksiniz . İhtiyaç duyulandan fazlasını vaat etmemenin başka faydaları da var, ancak bu tek başına o kadar büyük ki pratikte başkalarına ihtiyaç yok.


12
Exposing the concrete type Array usually creates many additional promises, e.g. that you can sort the collection by a function of your own choosing...- Parlak. Sadece düşünmedim. Evet, bir 'yineleme' ve sadece bir yineleme getirir!
MyDaftQuestions

18

Uygulamayı gizlemek OOP'un temel ilkesi ve tüm paradigmalarda iyi bir fikirdir, ancak tembel yinelemeyi destekleyen dillerde yineleyiciler (veya bu dilde ne denirse) için özellikle önemlidir.

Somut tür yinelemelerin (hatta benzeri arayüzlerin) açığa çıkmasındaki problem, IList<T>onları ortaya koyan nesnelerde değil, onları kullanan yöntemlerdedir. Örneğin, Foos listesini yazdırmak için bir işleviniz olduğunu varsayalım :

void PrintFoos(IList<Foo> foos)
{
    foreach (foo in foos)
    {
        Console.WriteLine(foo);
    }
}

Bu işlevi yalnızca foo listelerini yazdırmak için kullanabilirsiniz - yalnızca uygularlarsa IList<Foo>

IList<Foo> foos = //.....
PrintFoos(foos);

Peki ya listenin her çift indeksli öğesini yazdırmak istiyorsanız ? Yeni bir liste oluşturmanız gerekecek:

IList<Foo> everySecondFoo = new List<T>();
bool isIndexEven = true;
foreach (foo; foos)
{
    if (isIndexEven)
    {
        everySecondFoo.Add(foo);
    }
    isIndexEven = !isIndexEven;
}
PrintFoos(everySecondFoo);

Bu oldukça uzun, ancak LINQ ile bunu daha kolay okunabilen bir liner ile yapabiliriz:

PrintFoos(foos.Where((foo, i) => i % 2 == 0).ToList());

Şimdi, .ToList()sonunda farkettiniz mi? Bu, tembel sorguyu bir listeye dönüştürür, böylece onu iletebiliriz PrintFoos. Bu, ikinci bir listenin tahsis edilmesini gerektirir ve iki öğe (ikinci listeyi oluşturmak için birinci listede ve diğerini yazdırmak için ikinci listede) geçer. Ayrıca, eğer buna sahipsek:

void Print6Foos(IList<Foo> foos)
{
    int counter = 0;
    foreach (foo in foos)
    {
        Console.WriteLine(foo);
        ++ counter;
        if (6 < counter)
        {
            return;
        }
    }
}

// ........

Print6Foos(foos.Where((foo, i) => i % 2 == 0).ToList());

Ya foosbinlerce giriş varsa? Hepsini gözden geçirmek ve sadece 6'sını basmak için büyük bir liste ayırmamız gerekecek!

Numaralandırıcıları Gir - Yineleyici Desenin C # sürümü. İşlevimizin bir listeyi kabul etmesi yerine, kabul etmesini sağlarız Enumerable:

void Print6Foos(Enumerable<Foo> foos)
{
    // everything else stays the same
}

// ........

Print6Foos(foos.Where((foo, i) => i % 2 == 0));

Şimdi Print6Fooslistenin ilk 6 öğesinde tembelce yinelenebilir ve geri kalan kısmına dokunmamız gerekmez.

İçsel temsili göstermemek burada anahtardır. Bir Print6Fooslisteyi kabul ettiğimizde, bir liste vermek zorundaydık - rastgele erişimi destekleyen bir şey - ve bu nedenle bir liste ayırmak zorunda kaldık, çünkü imza yalnızca üzerinde yineleneceğini garanti etmez. Uygulamayı gizleyerek, Enumerableyalnızca işlevin gerçekte ihtiyaç duyduğu şeyi destekleyen daha verimli bir nesne oluşturabiliriz.


6

İç temsilciliği açığa vurmak, neredeyse hiçbir zaman iyi bir fikir değildir. Bu sadece kodun nedenini zorlaştırmakla kalmaz aynı zamanda bakımını da zorlaştırır. Herhangi bir iç temsil seçtiğinizi düşünün - bir diyelim IList<T>- ve bu içselleri açığa çıkarın. Kodunuzu kullanan herhangi biri Listeye erişebilir ve kod, Listenin dahili temsiline güvenebilir.

Herhangi bir nedenle, iç gösterimi IAmWhatever<T>zamanın ilerleyen noktalarında değiştirmeye karar veriyorsunuz . Bunun yerine, sınıfınızın içindekilerini değiştirmek yerine, her tür kod satırını değiştirmek zorunda kalacaksınız IList<T>. Bu, kullanışsız ve en iyi ihtimalle, sınıfı kullanan yalnızca siz olduğunuzda hatalara açıktır, ancak sınıfınızı kullanan diğer halkların kodunu bozabilir. Herhangi bir internete maruz bırakmadan bir dizi genel metot ortaya çıkardıysanız, hiçbir şey değişmemiş gibi çalışarak, sınıf dışında herhangi bir kod satırı olmadan iç gösterimi değiştirmiş olabilirsiniz.

Bu nedenle kapsülleme, önemsiz herhangi bir yazılım tasarımının en önemli yönlerinden biridir.


6

Ne kadar az derseniz, söylemeye devam etmek o kadar az.

Ne kadar az istersen, o kadar az sana söylenmen gerekecek.

Eğer kodunuz sadece ortaya çıkarsa, IEnumerable<T>sadece GetEnumrator()onu destekleyen, onu destekleyen herhangi bir başka kodla değiştirilebilir IEnumerable<T>. Bu esneklik ekler.

Kodunuz yalnızca kullanıyorsa IEnumerable<T>, uygulayan herhangi bir kod tarafından desteklenebilir IEnumerable<T>. Yine, ekstra esneklik var.

Örneğin tüm nesnelere bağlantı, yalnızca nesnelere bağlıdır IEnumerable<T>. Bazı özel uygulamalarını hızlı bir şekilde yürütürken, bunu IEnumerable<T>yalnızca kullanmaya GetEnumerator()ve IEnumerator<T>geri dönen uygulamaya her zaman geri dönebilecek şekilde test etme ve kullanma şeklinde yapar . Bu, dizilerin veya listelerin üzerine inşa edilenden çok daha fazla esneklik sağlar.


Güzel açık cevap. Kısa tutmanız ve bu nedenle ilk cümleyle tavsiyenize uymanız çok uygun. Bravo!
Daniel Hollinrake

0

@CaptainMan 'in yorumuna mirrors kullanmayı sevdiğim bir ifade: "Bir tüketicinin iç detayları bilmesi iyidir. Bir müşterinin iç detayları bilmesi gerekir ."

Bir müşterinin kodunu girmesi arrayveya IList<T>kod yazması gerekiyorsa , bu türlerin dahili ayrıntıları olduğunda, tüketici bunları doğru şekilde almalıdır . Yanlış ise, tüketici derleyemez, hatta yanlış sonuçlar alabilir. Bu, "uygulama ayrıntılarını her zaman göstermelisiniz, çünkü onları ifşa etmemelisiniz" ifadesinin diğer cevaplarıyla aynı davranışları verir, ancak maruz bırakmamanın avantajlarını kazmaya başlar. Bir uygulama detayını asla ortaya çıkarmazsanız, müşteriniz bunu kullanarak kendilerini asla sıkıntıya sokamaz!

Bu ışıkta düşünmeyi seviyorum, çünkü uygulamanın gizlenme kavramını bir anlamsızlıktan ziyade anlamını saklamak kavramını açıyor, "her zaman uygulama detaylarını sakla". Uygulamanın gösterilmesinin tamamen geçerli olduğu birçok durum vardır. Geçmiş soyutlama katmanlarını atlayıp baytları doğru yerlere yerleştirme yeteneğinin zamanlama yapma veya yapmama arasındaki fark olabileceği gerçek zamanlı aygıt sürücülerinin durumunu düşünün. Veya "iyi API'ler" oluşturmak için zamanınızın olmadığı ve hemen bunları kullanmanız gereken bir geliştirme ortamı düşünün. Genellikle, bu tür ortamlarda temel API'leri göstermek, son tarih verme veya sonlandırma arasında fark olabilir.

Ancak, bu özel davaları geride bırakıp daha genel davalara baktıkça, uygulamanın gizlenmesi daha da önem kazanmaya başlıyor. Uygulama detaylarını göstermek ip gibidir. Doğru kullanıldığında, yüksek dağları ölçeklemek gibi onunla her türlü şeyi yapabilirsiniz. Yanlış kullanılırsa ve sizi bir kemente bağlayabilir. Ürününüzün nasıl kullanılacağı hakkında ne kadar az şey bilirseniz, o kadar dikkatli olmanız gerekir.

Zaman ve tekrar tekrar gördüğüm örnek olay, bir geliştiricinin uygulama detaylarını gizleme konusunda çok dikkatli olmayan bir API yaptığı bir çalışma. Bu kütüphaneyi kullanan ikinci bir geliştirici, API'nin istedikleri bazı temel özellikleri kaçırdığını tespit ediyor. Bir özellik isteği yazmak yerine (ayın geri dönüş süresi olabilir), bunun yerine gizli uygulama ayrıntılarına erişimlerini kötüye kullanmaya karar verirler (bu işlem birkaç saniye sürer). Bu, kendi başına fena değil, ancak çoğu kez API geliştiricisi, API'nin bir parçası olmayı asla planlamamıştır. Daha sonra, API'yi geliştirip bu detayı değiştirdiklerinde, eski API'ye zincirlenmiş olduklarını fark ederler, çünkü birisi onu API'nin bir parçası olarak kullanmıştır. Bunun en kötü hali, birinin müşteri odaklı bir özellik sağlamak için API’nizi kullandığı ve şimdi de şirketinizin maaş çekimi bu hatalı API'ye bağlandı! Müşterileriniz hakkında yeterince bilginiz olmadığı zaman uygulama detaylarını açığa vurarak API'nizin etrafını birbirine bağlayacak kadar ip olduğunu kanıtlayın!


1
Re: "Veya 'iyi API'lar yapmak için vaktinizin olmadığı ve derhal bunları kullanmanız gereken bir geliştirme ortamını düşünün": Kendinizi böyle bir ortamda bulursanız ve bir nedenden dolayı istifa etmek istemezseniz ( ya da emin olup olmadığınızdan emin değilseniz), Death March: 'Mission Impossible' Projelerini Sürdürmek İçin Yazılım Geliştiricisinin Kılavuzu . Konuyla ilgili mükemmel tavsiyeler var. (Ayrıca,
istifa etmeme

@ruakh İyi API'ler yapacak vaktinizin olmadığı bir ortamda nasıl çalışacağınızı anlamanın her zaman önemli olduğunu anladım. Açıkçası, fildişi kule yaklaşımını sevdiğimiz kadar, gerçek kod da faturaları ödeyen şey. Bunu ne kadar erken kabul edebilirsek, o kadar çabuk bir şekilde yazılımlara denge kurarak başlayabiliriz, API kalitesini üretken kodla dengeleyerek ortamımıza tam olarak uyuyor. Çok fazla genç geliştiricinin bir karmaşaya hapsolduğunu, onları esnetmeleri gerektiğini anlamadıklarını gördüm. (Ben de o genç geliştirici oldum .. hala 5 yıl "iyi API" tasarımından kurtarılıyor)
Cort Ammon - Reinstate Monica

1
Gerçek kod faturaları öder, evet. İyi API tasarımı, gerçek kod üretmeye devam edeceğinizden nasıl emin olacağınızla ilgilidir. Crappy kod, bir proje sürdürülemez hale geldiğinde kendi başına ödeme yapmayı durdurur ve hataları yenileri oluşturmadan düzeltmek imkansız hale gelir. (Her neyse, bu yanlış bir ikilemdir. İyi bir kodun gerektirdiği şey omurga kadar zaman değildir .)
ruakh

1
@ruakh Bulduğum şey "iyi API'ler" olmadan iyi bir kod yapmanın mümkün olduğudur. Fırsat verilirse, iyi API'ler iyidir, ancak bunları bir zorunluluk olarak değerlendirmek, değerinden daha fazla çekişmeye neden olur. Şunu düşünün: İnsan vücudu için API korkunç, ama yine de mühendislik konusunda oldukça iyi olduklarını düşünüyoruz =)
Cort Ammon - Reinstate Monica

Vereceğim diğer örnek, zamanlama konusundaki tartışmanın ilham kaynağı olan örnek. Çalıştığım gruplardan birinde, zor gerçek zamanlı gereksinimlere sahip, geliştirmek için ihtiyaç duydukları bazı aygıt sürücüsü şeyleri vardı. Çalışacak 2 API'si vardı. Biri iyiydi, biri daha azdı. İyi API, uygulamayı gizlediği için zamanlama yapamadı. Daha az API uygulaması ortaya çıkardı ve bunu yaparken çalışan bir ürün oluşturmak için işlemciye özgü teknikleri kullanmanıza izin verdi. İyi API kibarca rafa kondu ve zamanlama gereksinimlerini karşılamaya devam ettiler.
Cort Ammon - Monica,

0

Verilerinizin içsel temsilinin ne olduğunu mutlaka bilmiyorsunuz - veya gelecekte olabilir.

Bir dizi olarak temsil ettiğiniz bir veri kaynağınız olduğunu varsayalım, bunun için düzenli bir döngü için yineleyebilirsiniz.

Şimdi refactor yapmak istediğini söyle. Patronunuz veri kaynağının bir veritabanı nesnesi olmasını istedi. Ayrıca, bir API, bir hashmap veya bağlantılı bir listeden veya dönen bir manyetik tamburdan veya bir mikrofondan veya Dış Moğolistan'da bulunan gerçek zamanlı bir yak talaş sayısından veri çekme seçeneğine sahip olmak istiyor.

Eğer sadece bir döngü için bir kodunuz varsa, veri nesnesine erişmeyi beklediği şekilde erişmek için kodunuzu kolayca değiştirebilirsiniz.

Veri nesnesine erişmeye çalışan 1000 sınıfınız varsa, şimdi büyük bir yeniden düzenleme probleminiz var.

Bir yineleyici, listeye dayalı bir veri kaynağına erişmenin standart bir yoludur. Bu ortak bir arayüz. Bunu kullanmak kod veri kaynağınızı agnostik hale getirecektir.

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.