Java'da “bir arayüze programlamak” her zaman mantıklı mıdır?


9

Bir arayüzden uygulayan bir sınıfın nasıl örnekleneceği ile ilgili bu sorudaki tartışmayı gördüm . Benim durumumda, Java'da bir örneği kullanan çok küçük bir program yazıyorum TreeMapve orada herkesin görüşüne göre, şu şekilde somutlaştırılmalıdır:

Map<X> map = new TreeMap<X>();

Programımda, arabirimde (ve arabirimde de bulunan birkaç başka) map.pollFirstEntry()bildirilmeyen işlevi çağırıyorum . Ben böyle bir yöntem olarak adlandırılan her yerde döküm yaparak bunu başardı :MapMapTreeMap<X>

someEntry = ((TreeMap<X>) map).pollFirstEntry();

Başlatma kılavuzlarının yukarıda açıklanan büyük programlar için avantajlarını anlıyorum, ancak bu nesnenin diğer yöntemlere aktarılmayacağı çok küçük bir program için gereksiz olduğunu düşünüyorum. Yine de, bir iş başvurusunun bir parçası olarak bu örnek kodu yazıyorum ve kodumu kötü görünmesini veya dağınık görünmesini istemiyorum. En zarif çözüm ne olurdu?

EDIT: Belirli bir işlevin uygulanması yerine, geniş iyi kodlama uygulamaları ile daha fazla ilgilendiğimi belirtmek isterim TreeMap. Bazı cevaplar zaten işaret ettiğinden (ve bunu ilk olarak cevapladım olarak işaretledim), işlevselliği kaybetmeden mümkün olan daha yüksek soyutlama seviyesi kullanılmalıdır.


1
Özelliklere ihtiyacınız olduğunda bir TreeMap kullanın. Bu muhtemelen belirli bir nedenden ötürü bir tasarım seçimiydi, bu yüzden uygulamanın bir parçası olmalıdır.

@JordanReiter Aynı içerikle yeni bir soru eklemem gerekir mi yoksa dahili bir çapraz gönderme mekanizması var mı?
jimijazz


2
"Büyük programlar için yukarıda açıklandığı gibi başlatma kılavuzlarının avantajlarını anlıyorum" Programın boyutu ne olursa olsun, her yere döküm yapmak avantajlı değil
Ben Aaronson

Yanıtlar:


23

"Bir arabirime programlama", "mümkün olan en soyut sürümü kullan" anlamına gelmez. Bu durumda herkes sadece kullanırdı Object.

Bunun anlamı, işlevselliğini kaybetmeden programınızı mümkün olan en düşük soyutlamaya karşı tanımlamanızdır . Bir şeye TreeMapihtiyacınız varsa, a kullanarak bir sözleşme tanımlamanız gerekir TreeMap.


2
TreeMap bir arayüz değil, bir uygulama sınıfıdır. Harita, SortedMap ve NavigableMap arayüzlerini uygular. Açıklanan yöntem NavigableMap arabiriminin bir parçasıdır . TreeMap kullanmak, uygulamanın, uygulama yerine bir arabirime kodlamanın tüm noktası olan bir ConcurrentSkipListMap'e (örneğin) geçmesini önler.

3
@MichaelT: Bu özel senaryoda ihtiyaç duyduğu soyutlamaya bakmadım, bu yüzden TreeMapörnek olarak kullandım . "Bir arayüze program" tam anlamıyla bir arayüz veya soyut bir sınıf olarak ele alınmamalıdır - bir uygulama aynı zamanda bir arayüz olarak da düşünülebilir.
Jeroen Vannevel

1
Bir uygulama sınıfının genel arabirimi / yöntemleri teknik olarak bir 'arabirim' olsa da, LSP'nin arkasındaki kavramı kırar ve farklı bir alt sınıfın değiştirilmesini önler, bu yüzden public interface'bir uygulamanın genel yöntemleri' yerine bir programlamak istiyorsunuz .

@JeroenVannevel Bir arayüze programlamanın , arayüz aslında bir sınıfla temsil edildiğinde yapılabileceğini kabul ediyorum . Ancak, ne faydası TreeMapkullanacağımı görmüyorum SortedMapveyaNavigableMap
toniedzwiedz

16

Hala bir arayüz kullanmak istiyorsanız,

NavigableMap <X, Y> map = new TreeMap<X, Y>();

her zaman bir arabirim kullanmak gerekli değildir, ancak genellikle uygulamayı değiştirmenize (belki de test için) izin veren daha genel bir görünüm almak istediğiniz bir nokta vardır ve nesneye yapılan tüm referanslar arayüz türü.


3

Bir uygulama yerine bir arayüze kodlamanın arkasındaki nokta, programınızı aksi takdirde kısıtlayacak uygulama ayrıntılarının sızmasını önlemektir.

Kodunuzun orijinal sürümünün kullanıldığı HashMapve bunu ortaya çıkardığı durumu düşünün .

private HashMap foo = new HashMap();
public HashMap getFoo() { return foo; }  // This is bad, don't do this.

Bu, yapılan herhangi bir değişikliğin getFoo()bir API değişikliği olduğu ve onu kullanan kişileri mutsuz edeceği anlamına gelir . Garanti ettiğiniz footek şey bu bir Harita ise, bunun yerine geri dönmelisiniz.

private Map foo = new HashMap();
public Map getFoo() { return foo; }

Bu, kodunuzun içinde işlerin çalışma şeklini değiştirme esnekliği sağlar. Bunun foo, belirli bir sırada şeyleri geri getiren bir Harita olması gerektiğinin farkındasınız .

private NavigableMap foo = new TreeMap();
public Map getFoo() { return foo; }
private void doBar() { ... foo.lastEntry(); ... }

Ve bu kodun geri kalanı için hiçbir şey kırmaz.

Daha sonra, sınıfın verdiği sözleşmeyi hiçbir şey kırmadan da güçlendirebilirsiniz.

private NavigableMap foo = new TreeMap();
public NavigableMap getFoo() { return foo; }
private void doBar() { ... foo.lastEntry(); ... }

Bu Liskov oyuncu değiştirme prensibini araştırıyor

İkame edilebilirlik, nesne yönelimli programlamada bir prensiptir. Bir bilgisayar programında, S, T'nin bir alt tipiyse, T tipi nesnelerin, S tipindeki nesnelerle (yani, S tipi nesnelerin, T tipindeki nesnelerin yerine geçebileceğini), istenen herhangi bir şeyi değiştirmeden değiştirilebileceğini belirtir. programın özellikleri (doğruluk, yapılan görev vb.).

NavigableMap, Harita'nın bir alt türü olduğundan, bu değişiklik programı değiştirmeden yapılabilir.

Uygulama türlerinin ortaya çıkarılması, bir değişiklik yapılması gerektiğinde programın dahili olarak çalışma şeklini değiştirmeyi zorlaştırır. Bu acı verici bir süreçtir ve çoğu zaman kodlayıcıya daha sonra daha fazla acı çekmeye hizmet eden çirkin geçici çözümler yaratır (size bir nedenle her ne zaman bir LinkedHashMap ve bir TreeMap arasında veri karıştırmaya devam eden önceki kodlayıcıya bakıyorum - bana güvenin svn suçlama adınızı görmek endişeleniyorum).

Sen olur hala uygulama türlerini sızıntı önlemek istiyorum. Örneğin, bazı performans özellikleri nedeniyle bunun yerine ConcurrentSkipListMap'i uygulamak isteyebilirsiniz veya içe aktarma ifadelerinde veya herhangi bir şekilde kullanmak java.util.concurrent.ConcurrentSkipListMapyerine istediğiniz gibi olabilir java.util.TreeMap.


1

Gerçekte bir şeye ihtiyacınız olan en genel sınıfı (veya arabirimi) kullanmanız gereken diğer cevapları kabul ederek, bu durumda TreeMap (veya NavigableMap tarafından önerilen biri gibi). Ama bunun her yerde döküm yapmaktan kesinlikle daha iyi olduğunu eklemek isterim, ki bu her durumda çok daha büyük bir koku olurdu. Bazı nedenlerle /programming/4167304/why-should-casting-be-avoided adresine bakın .


1

Bu sizin iletişim hakkında niyet ait nasıl nesne kullanılmalıdır. Örneğin, yönteminiz tahmin edilebilir bir yineleme sırasınaMap sahip bir nesne bekliyorsa :

private Map<String, String> processOrderedMap(LinkedHashMap<String, String> input) {
    // ...
}

Daha sonra, yukarıdaki yöntemin arayanlarına kesinlikle tahmin edilebilir bir yineleme sırasınaMap sahip bir nesne döndürdüğünü söylemeniz gerekiyorsa , çünkü bir nedenden dolayı böyle bir beklenti vardır:

private LinkedHashMap<String,String> processOrderedMap(LinkedHashMap<String,String> input) {
    // ...
}

Tabii ki, arayanlar dönüş nesnesini Mapbu şekilde ele alabilirler, ancak bu yönteminizin kapsamı dışındadır:

private Map<String, String> output = processOrderedMap(input);

Geri adım atmak

Bir arabirime kodlamanın genel tavsiyesi (genellikle) uygulanabilir, çünkü genellikle nesnenin ne yapabilmesi gerektiğini, yani sözleşmeyi garanti eden arabirimdir . Birçok yeni başlayanlar ile başlar HashMap<K, V> map = new HashMap<>()ve bunu bir olarak ilan etmeleri önerilir Map, çünkü a'nın yapılması HashMapgerekenden daha fazla bir şey sunmaz Map. Bundan sonra, yöntemlerinin neden a Mapyerine alması gerektiğini (umarız) anlayabilecekler HashMapve bu da OOP'taki kalıtım özelliğini gerçekleştirmelerini sağlıyor.

Bu konuyla ilgili herkesin favori prensibinin Wikipedia girişinden sadece bir satır alıntı yapmak için :

Bir hiyerarşide türlerin semantik birlikte çalışabilirliğini garanti etmeyi amaçladığı için, sadece sözdizimsel bir ilişkiden ziyade anlamsal bir ilişkidir ...

Başka bir deyişle, bir Mapbildirimin kullanılması, 'sözdizimsel olarak' mantıklı olduğu için değil, nesnenin çağrılması yalnızca bir tür olduğunu ummalıdır Map.

Temiz kod

Bu, özellikle birim test söz konusu olduğunda, zaman zaman daha temiz kod yazmama izin verdiğini buldum. HashMapTek bir salt okunur test girişi ile bir oluşturmak , bunu kolayca değiştirebileceğim bir satırdan daha fazla (çift ayraç başlatma kullanımı hariç) alır Collections.singletonMap().

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.