Java koleksiyonlarını anlamlı sınıf adlarıyla maskelemek için iyi veya kötü uygulama?


46

Son zamanlarda, insan dostu sınıf isimlerinde Java koleksiyonlarını "maskeleme" alışkanlığı oldum. Bazı basit örnekler:

// Facade class that makes code more readable and understandable.
public class WidgetCache extends Map<String, Widget> {
}

Veya:

// If you saw a ArrayList<ArrayList<?>> being passed around in the code, would you
// run away screaming, or would you actually understand what it is and what
// it represents?
public class Changelist extends ArrayList<ArrayList<SomePOJO>> {
}

Bir meslektaşım bana bunun kötü bir uygulama olduğunu belirtti ve gecikme / gecikme ile birlikte OO karşıtı bir kalıp oluşturduğunu söyledi. Genel olarak çok küçük bir performans derecesi getirdiğini anlayabiliyorum , ancak bunun önemli olduğunu hayal bile edemiyorum. Bu yüzden soruyorum: Bu yapmak iyi ya da kötü, ve neden?


11
Bundan daha basit. Bu kötü bir uygulamadır çünkü Java temel JDK Koleksiyonunun uygulamalarını genişlettiğinizi hayal ediyorum. Java'da yalnızca bir sınıfı genişletebilirsiniz, bu nedenle bir uzantınız olduğunda daha fazla düşünmeniz ve tasarlamanız gerekir. Java'da, genişletme özelliğini az miktarda kullanın.
InformedA

ChangeListDerleme kırılacak extends, çünkü List bir arayüzdür, gerektirir implements. @randomA hayal ettiğiniz şey bu hatanın sebebini özlüyor
gnat

@gnat Meseleyi eksik değil, bir implementationyani HashMapya da TreeMaporada olan bir yazım hatası olduğunu yattığını farz ediyorum .
bilgilendirildi.

4
Bu bir KÖTÜ uygulamasıdır. KÖTÜ KÖTÜ KÖTÜ. Bunu yapma Herkes bir <String, Widget> haritasının ne olduğunu bilir. Ama bir WidgetCache? Şimdi WidgetCache.java dosyasını açmam gerekiyor, WidgetCache'in sadece bir harita olduğunu hatırlamam gerekiyor. WidgetCache’e yeni bir şey eklemediğinizi her yeni sürümün gelip gelmediğini kontrol etmeliyim. Tanrım hayır, bunu asla yapma.
Miles Rout,

“Kod içinde bir ArrayList <ArrayList <? >> iletildiğini görürseniz, çığlık atarak kaçar mısın?” Hayır, iç içe genel koleksiyonlarla oldukça rahatım. Ve sen de öyle olmalısın.
Kevin Krumwiede 28:16

Yanıtlar:


75

/ Gecikme Lag? Buna BS deniyorum. Bu uygulamadan tam olarak sıfır ek yük olmalıdır. ( Düzenleme: Yorumlarda, bunun aslında HotSpot VM tarafından gerçekleştirilen optimizasyonları engelleyebileceği belirtildi. VM uygulamasının bunu onaylaması veya reddetmesi için yeterince bilgim yok. sanal fonksiyonların uygulanması.)

Yükü biraz kod var. İstediğiniz temel sınıftan tüm yapıcıları, parametrelerini ileterek oluşturmalısınız.

Ayrıca, bunu bir anti-patern olarak görmüyorum. Ancak bunu kaçırılmış bir fırsat olarak görüyorum. Yalnızca yeniden adlandırma uğruna temel sınıfı türeten bir sınıf oluşturmak yerine, bunun yerine koleksiyonu içeren ve büyük küçük harf özel, gelişmiş bir arayüz sunan bir sınıf oluşturmaya ne dersiniz ? Widget önbelleğiniz gerçekten bir haritanın tam arabirimini sunmalı mı? Yoksa bunun yerine özel bir arayüz sunmalı mı?

Dahası, koleksiyonlar söz konusu olduğunda, desen basit bir şekilde uygulamaların değil, arayüzlerin kullanımıyla ilgili genel kurallarla birlikte çalışmaz - yani düz toplama kodunda, bir tür oluşturacak HashMap<String, Widget>ve sonra onu bir değişkene atayacaksınız Map<String, Widget>. Kişisel WidgetCachekutu uzatmaz Map<String, Widget>bir ara yüz, çünkü. Temel arayüzü genişleten bir arayüz olamaz, çünkü HashMap<String, Widget>bu arayüzü uygulamaz ve diğer standart koleksiyonları da kullanmaz. Ve onu genişleten bir sınıf yapabilirken, o HashMap<String, Widget>zaman değişkenleri WidgetCacheveya olarak tanımlamanız gerekir Map<String, Widget>ve birincisi farklı bir koleksiyonun yerine koyma esnekliğini kaybedersiniz (belki bazı ORM'nin tembel yükleme koleksiyonunu) sınıfın olması.

Bu karşılaştırma noktalarından bazıları benim önerilen uzmanlık sınıfım için de geçerli.

Bunların hepsi dikkate alınması gereken noktalar. Doğru seçim olabilir veya olmayabilir. Her iki durumda da meslektaşınızın önerdiği argümanlar geçerli değildir. Bunun bir anti-patern olduğunu düşünüyorsa, ismini yazmalı.


1
Her ne kadar bazı performans etkisine sahip olmak oldukça mümkün olsa da, bunun korkunç olmayacağına dikkat edin (bazı inlining optimizasyonları kaçırmak ve en kötü durumda ekstra bir arama yapmak - en içteki döngünüz için harika değil ama muhtemelen iyi).
Voo

5
Bu gerçekten iyi bir cevap, herkese "kararı genel olarak vermeyecek şekilde karar vermeyin" - ve aşağıda yer alan tek tartışma "HotSpot bununla nasıl başa çıktığı", bazılarının (tüm vakaların% 99.9'unda) alakasız performans etkisi "olduğunu söylüyor - çocuklar, ilk cümlenden daha fazla okumayı bile başardınız mı?
Doc Brown

16
+1 "Yalnızca yeniden adlandırma amacıyla temel sınıfı türeten bir sınıf oluşturmak yerine, bunun yerine koleksiyonu içeren ve büyük / küçük harf özel, gelişmiş bir arayüz sunan bir sınıf oluşturmaya ne dersiniz?"
user11153

6
Bu yanıtında ana nokta gösteren Pratik örnek: dokümantasyon için public class Properties extends Hashtable<Object,Object>de java.utilHashtable, koymak ve putAll yöntemleri Özellikler devralır bir Özellikler nesneye uygulanabilir Çünkü" diyor onlar arayan Anahtarları girdilerini eklemek için izin olarak kullanımları kesinlikle önerilmez. veya değerler String değil. " Kompozisyon daha temiz olurdu.
Patricia Shanahan

3
@DocBrown Hayır, bu cevap hiç bir performans etkisi olmadığını iddia ediyor. Güçlü ifadeler yaparsanız, onları daha iyi destekleyebiliyorsanız, aksi halde insanlar muhtemelen sizi arayacaklardır. Yorumların tamamı (cevaplar üzerine) yanlış gerçekleri veya varsayımları belirtmek veya faydalı notlar eklemek, ancak kesinlikle insanları tebrik etmek değil, oylama sistemi bunun için.
Voo

25

IBM'e göre bu aslında bir anti-kalıptır. Bu 'typedef' benzeri sınıflara psuedo türleri denir.

Makale benden çok daha iyi açıklıyor, ancak bağlantının kopması durumunda özetlemeye çalışacağım:

  • Bekleyen bir kod WidgetCacheişleyemezMap<String, Widget>
  • Bu Pseudotipler birden fazla paket kullanırken 'viral' olurlar; temel tür (sadece aptal bir Harita <...>) tüm paketlerde her durumda çalışabilirdi.
  • Sözde türler genellikle somuttur, temel arayüzleri yalnızca genel sürümü uyguladığı için belirli arayüzleri uygulamazlar.

Makalede, sözde türler kullanmadan hayatı kolaylaştırmak için aşağıdaki numarayı önerdiler:

public static <K,V> Map<K,V> newHashMap() {
    return new HashMap<K,V>(); 
}

Map<Socket, Future<String>> socketOwner = Util.newHashMap();

Otomatik tip çıkarımı nedeniyle çalışır.

( Bu ilgili yığın taşması sorusu ile bu cevaba geldim )


13
newHashMapŞimdi elmas operatörü tarafından çözülmesi gerekmiyor mu?
svick

Kesinlikle doğru, bunu unuttum. Normalde Java ile çalışmıyorum.
Roy T.

Dillerin, diğer türlerin örneklerine gönderme yapan değişken türlerinin bir evrenine izin vermesini, ancak derleme zamanı kontrolünü destekleyebilmesini, böylece bir türün WidgetCachebir değişkene bir tür değişkenine atanmış WidgetCacheveya Map<String,Widget>dökümsüz bir şekilde atanmasına izin vermesini diliyorum. içinde statik bir yöntemin WidgetCachereferans alabildiği Map<String,Widget>ve WidgetCacheistenen herhangi bir doğrulamayı yaptıktan sonra türü olarak geri döndürdüğü bir yöntemdi . Böyle bir özellik, özellikle silinmesi gerekmeyen jeneriklerle özellikle yararlı olabilir (... ...
supercat, 16:15

... türler yine de sadece derleyici aklına gelirdi). Birçok değişken tip için, iki ortak referans alanı tipi vardır: biri mutasyona uğratılabilecek bir örneğe tek referansı tutarken, biri kimsenin mutasyona uğramasına izin verilmeyen bir örneğe paylaşılabilir referansı tutar. Farklı kullanım şekillerine ihtiyaç duydukları için, bu iki tür alana farklı adlar verebilmek yararlı olacaktır, ancak her ikisi de aynı tür nesne örnekleri için referanslar tutmalıdır.
supercat

5
Bu, Java'nın neden takma adlara ihtiyaç duyduğu konusunda harika bir argümandır.
GlenPeterson 10:15

13

Performans isabeti, büyük olasılıkla zaten gerçekleşmiş olan canlı bir aramayla sınırlı olacaktır. Buna karşı çıkmak için geçerli bir sebep değil.

Bu durum, statik olarak yazılan tüm programlama dillerinin çoğunun, genellikle a olarak adlandırılan diğer ad türlerine yönelik özel sözdizimlerine sahip olması durumunda yaygındır typedef. Java muhtemelen başlangıçta parametreleştirilmiş tiplere sahip olmadığı için bunları kopyalamamıştır. Bir sınıfı genişletmek, Sebastian'ın cevabında çok iyi yer almasının nedenleri nedeniyle ideal değildir, ancak Java'nın sınırlı sözdizimi için makul bir geçici çözüm olabilir.

Typedefs'in bir takım avantajları vardır. Programcının amacını daha iyi bir adla, daha uygun bir soyutlama düzeyinde daha net bir şekilde ifade ederler. Hata ayıklama veya yeniden düzenleme amacıyla arama yapmak daha kolaydır. Her yerde bulmayı düşünün a WidgetCache, a'nın belirli kullanımlarını bulmak yerine kullanılır Map. Bunları değiştirmek daha kolaydır, örneğin daha sonra bir LinkedHashMapşeye ihtiyacınız olduğunu , hatta kendi özel kabınızı bulursanız .


Ayrıca, inşaatçılar arasında bir minik başlık var.
Stephen C,

11

Daha önce de bahsettiğim gibi, kalıtımın üstünde bir kompozisyon kullanmanızı öneririm, bu nedenle yalnızca kullanım amacına uygun isimlerle gerçekten gerekli olan yöntemleri gösterebilirsiniz. Sınıfınızın kullanıcılarının gerçekten WidgetCachebunun bir harita olduğunu bilmesi gerekiyor mu? Ve istedikleri herhangi bir şey onunla yapmak mümkün olabilir? Ya da sadece bunun widgetlar için bir önbellek olduğunu bilmeleri gerekir?

Benim kod tabanımdaki örnek sınıf, tanımladığınız soruna benzer bir çözüm:

public class Translations {

    private Map<Locale, Properties> translations = new HashMap<>();

    public void appendMessage(Locale locale, String code, String message) {
        /* code */
    }

    public void addMessages(Locale locale, Properties messages) {
        /* code */
    }

    public String getMessage(Locale locale, String code) {
        /* code */
    }

    public boolean localeExists(Locale locale) {
        /* code */
    }
}

Bunun dahili olarak "sadece bir harita" olduğunu ancak genel arayüzün bunu göstermediğini görebilirsiniz. Ve appendMessage(Locale locale, String code, String message)yeni girişler eklemek için daha kolay ve daha anlamlı bir yol gibi "programcı dostu" yöntemleri var . Ve örneğin sınıftaki kullanıcılar bunu yapamazlar translations.clear()çünkü Translationsgenişleyemez Map.

İsteğe bağlı olarak, gerekli yöntemlerden bazılarını dahili olarak kullanılan haritaya atayabilirsiniz.


6

Bunu anlamlı bir soyutlamanın örneği olarak görüyorum. İyi bir soyutlamanın birkaç özelliği vardır:

  1. Onu tüketen kodla ilgisi olmayan uygulama detaylarını gizler.

  2. Sadece olması gerektiği kadar karmaşık.

Genişleyerek, ebeveynin tüm arayüzünü sergiliyor olursunuz, ancak çoğu durumda daha iyi gizlenmiş olabilir, bu nedenle Sebastian Redl'in önerdiği şeyi yapmak ve kalıtımla ilgili kompozisyonu tercih etmek ve ebeveynin bir örneğini eklemek isteyebilirsiniz. Özel sınıfınızın özel bir üyesi. Soyutlamanız için anlam ifade eden arayüz yöntemlerinden herhangi biri, (sizin durumunuzda) iç koleksiyona kolayca atanabilir.

Bir performans etkisine gelince, ilk önce okunabilirlik için kodu optimize etmek her zaman iyi bir fikirdir ve bir performans etkisinden şüpheleniliyorsa, iki uygulamayı karşılaştırmak için kodu profilleyin.


4

Burada diğer cevaplara +1. Ayrıca, Domain Driven Design (DDD) topluluğu tarafından gerçekten iyi bir uygulama olarak kabul edildiğini de ekleyeceğim. Etki alanınızın ve onunla etkileşimlerin, altta yatan veri yapısının tersine anlamsal etki alanına sahip olması gerektiğini savunuyorlar. Bir Map<String, Widget>önbellek olabilir, ama başka bir şey de olabilir, doğru yaptığınız şey benim o kadar mütevazi düşüncem (IMNSHO) koleksiyonun neyi temsil ettiğini, bu durumda bir önbelleği modellemektir.

Temel veri yapısının etrafındaki etki alanı sınıfı sarmalayıcısının, yalnızca veri yapısının aksine etkileşimli bir etki alanı sınıfı yapan başka üye değişkenleri veya işlevleri olması gerektiğine (yalnızca Java'da Değer Türleri varsa) önemli bir düzenleme ekleyeceğim. , onları Java 10'da bulacağız - söz!

Java 8'in akışının tüm bunlar üzerinde ne gibi bir etkisi olacağını görmek ilginç olacak, belki de bazı ortak arayüzlerin bir Java nesnesinin aksine bir Akış (ortak Java ilkel veya String ekleyiniz) ile başa çıkmayı tercih edeceğini hayal edebiliyorum.

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.