Neden statik olmayan iç sınıfları statik olanlara tercih ediyorsun?


37

Bu soru, Java'da iç içe bir sınıfın statik iç içe bir sınıf mı yoksa iç içe bir sınıf mı olacağıyla ilgilidir. Buralarda ve Yığın Taşması ile ilgili araştırma yaptım ancak bu kararın tasarım sonuçlarıyla ilgili herhangi bir soru bulamadım.

Bulduğum sorular, bana açık olan statik ve iç içe sınıflar arasındaki farkı soruyor. Ancak, Java'da statik iç içe geçmiş bir sınıfı kullanmak için henüz ikna edici bir neden bulamadım - bu soru için dikkate almadığım anonim sınıflar dışında.

İşte statik iç içe sınıfları kullanmanın etkisini anladığımdan:

  • Daha az eşleşme: Sınıf dış sınıfın niteliklerine doğrudan erişemediğinden genel olarak daha az eşleşme elde ederiz. Daha az eşleşme genellikle daha iyi kod kalitesi, daha kolay test, yeniden düzenleme vb. Anlamına gelir.
  • Tek Class: Sınıf yükleyici her seferinde yeni bir sınıfa bakmaya ihtiyaç duymaz, biz dış sınıfa ait bir nesne yaratırız. Sadece aynı sınıf için tekrar tekrar yeni nesneler elde ediyoruz.

Bir iç sınıf için, genellikle insanların dış sınıfın niteliklerine bir profesyonel olarak erişebildiklerini düşünüyorum. Bu bağlamda tasarım açısından farklılık göstermek için yalvarıyorum, çünkü bu doğrudan erişim yüksek bir bağlantıya sahip olduğumuz anlamına gelir ve iç içe geçmiş sınıfı ayrı bir üst seviye sınıfına çıkarmak istediğimizde bunu ancak temel olarak döndükten sonra yapabiliriz. statik iç içe sınıfa.

Bu yüzden benim sorum şu şekildedir: Statik olmayan iç sınıflar için mevcut olan özellik erişiminin yüksek eşleşmeye, dolayısıyla düşük kod kalitesine yol açtığını ve bu nedenle (isimsiz) iç içe geçmiş sınıfların genellikle statik olmak?

Başka bir deyişle: İç içe geçmiş bir sınıfı tercih etmenin nedeninde ikna edici bir neden var mı?


2
dan Java öğretici : "Bu erişime ihtiyaç yoksa Kullanım olmayan statik iç içe sınıf (veya iç sınıfı) bir çevreleyen örneğinin kamuya açık olmayan alanlar ve yöntemlere erişim gerektiren eğer statik iç içe sınıfını kullanın."
tatarcık

5
Öğretici fiyat teklifi için teşekkürler, ancak bu sadece örnek alanlarına erişmek isteyip istemediğinizi değil, farkın ne olduğunu tekrar eder.
Frank,

ne tercih edeceğine dair genel bir rehberliktir. Bu cevabın
gnat

1
Eğer iç sınıf sıkı bir şekilde birleşme sınıfına bağlı değilse, muhtemelen ilk başta bir iç sınıf olmamalıdır.
Kevin Krumwiede

Yanıtlar:


23

"Etkin Java İkinci Baskısı" adlı kitabının 22. Maddesindeki Joshua Bloch, ne tür iç içe sınıfın ne zaman kullanılacağını ve neden kullanıldığını anlatır. Aşağıda bazı alıntılar var:

Bir statik üye sınıfının yaygın bir kullanımı, yalnızca dış sınıfı ile birlikte yararlı olan bir ortak yardımcı sınıfıdır. Örneğin, bir hesap makinesi tarafından desteklenen işlemleri açıklayan bir numaralandırmayı düşünün. İşlem enum sınıfın genel bir statik üye sınıfı olmalıdır Calculator. Müşterileri Calculatordaha sonra Calculator.Operation.PLUSve gibi isimleri kullanan işlemlere başvurabilirler Calculator.Operation.MINUS.

Statik olmayan bir üye sınıfın yaygın bir kullanımı , dış sınıfın bir örneğinin ilişkisiz bir sınıfın örneği olarak görülmesine izin veren bir Adaptör tanımlamaktır . Örneğin, uygulamalar Maparayüzü tipik olarak uygulamak için statik olmayan üye sınıfları kullanmak toplama Gösterim tarafından döndürülen, Map'nin keySet, entrySetve valuesyöntemler. Benzer şekilde, toplama arayüzlerinin uygulamaları , tipik olarak yinelemelerini uygulamak için statik olmayan üye sınıfları kullanırlar Setve Listkullanırlar:

// Typical use of a nonstatic member class
public class MySet<E> extends AbstractSet<E> {
    ... // Bulk of the class omitted

    public Iterator<E> iterator() {
        return new MyIterator();
    }

    private class MyIterator implements Iterator<E> {
        ...
    }
}

Ekli bir örneğe erişim gerektirmeyen bir üye sınıfı bildirirseniz , değiştiriciyi her zamanstatic bildirgesine koyun, durağan olmayan bir üye sınıfı yerine statik hale getirin.


1
Bu adaptörün dış sınıf MySetörnekleri görünümünü nasıl değiştirip değiştirmeyeceğinden emin değilim. Yola bağlı bir tür gibi bir fark olduğunu düşünebilirim, yani myOneSet.iterator.getClass() != myOtherSet.iterator.getClass(), ancak daha sonra tekrar Java'da, sınıf her biri için aynı olacağı için aslında mümkün değildir.
Frank,

@Frank Oldukça tipik bir adaptör sınıfı - seti, öğelerinin bir akışı (bir Yineleyici) olarak görmenizi sağlar.
user253751

@ immibis teşekkürler, yineleyici / bağdaştırıcının ne yaptığını çok iyi biliyorum, ama bu soru nasıl uygulandığı ile ilgiliydi .
Frank

@ Frank Dış görünüşün görünümünü nasıl değiştirdiğini söylüyordum. Bir adaptör tam olarak ne yapar - bazı nesneleri görüntüleme şeklinizi değiştirir. Bu durumda, bu nesne dış örnektir.
user253751 5:15

10

Statik olmayan iç sınıflar için mevcut olan özellik erişiminin yüksek eşleşmeye yol açtığını, dolayısıyla daha düşük kod kalitesine yol açtığını ve (anonim olmayan ve yerel olmayan ) iç sınıfların genellikle statik olması gerektiğini varsaymakta haklısınız.

İç sınıfı statik olmayan hale getirme kararının tasarım sonuçları Java Puzzlers , Puzzle 90'da (aşağıda belirtilen alıntı kalın yazı tipindedir) benimdir:

Ne zaman bir üye sınıfı yaz, kendine sor, Bu sınıfın gerçekten bir ek örneğe ihtiyacı var mı? Cevap hayır ise, yap static. İç sınıflar bazen yararlı olabilir, ancak bir programı anlaşılmasını zorlaştıran komplikasyonları kolayca ortaya koyabilirler. Bunlar, jenerik (Yapboz 89), yansıma (Yapboz 80) ve kalıtım (bu yapboz) ile karmaşık etkileşimlere sahiptir . Eğer bildirirseniz Inner1olmak static, sorun kaybolduktan. Ayrıca bildirirseniz Inner2olmak staticgüzel bir ikramiye gerçekten:, aslında programın ne yaptığını anlayabilir.

Özetle, bir sınıfın hem iç sınıf hem de diğerinin alt sınıfı olması nadiren uygundur. Daha genel olarak, bir iç sınıfı genişletmek nadiren uygundur; Gerekirse, ekli örnek için uzun ve sıkı düşünün. Ayrıca, staticiç içe sınıfları olmayanlara tercih edin static. Çoğu üye sınıfı ilan edilebilir ve ilan edilmelidir static.

Eğer ilgileniyorsanız, daha ayrıntılı bir analiz Puzzle 90 sağlanmıştır yığın taşması bu cevap .


Yukarıdakilerin, esasen Java Sınıfları ve Nesneleri eğitiminde verilen kılavuzun genişletilmiş bir versiyonu olduğunu belirtmek gerekir :

Çevreleyen bir örneğin genel olmayan alanlarına ve yöntemlerine erişmeniz gerekiyorsa statik olmayan bir iç içe sınıf (veya iç sınıf) kullanın. Bu erişime gerek duymuyorsanız statik iç içe bir sınıf kullanın.

Yani, sorunun cevabı size sorulan bir başka deyişle öğretici başına, sadece inandırıcı nedeni bir çevreleyen örneğinin kamuya açık olmayan alanlar ve yöntemlere erişim olduğunda dışı statik kullanmaktır gerekli .

Öğretici ifadeler biraz geniştir (Java Puzzlers'ın onu güçlendirmeye ve daraltmaya çalışmasının nedeni bu olabilir). Özellikle, doğrudan iliştirilen örnek alanlarına doğrudan erişme deneyimimde hiçbir zaman gerekli olmadı - bu bağlamda bunları yapıcı / yöntem parametreleri olarak geçirmek gibi alternatif yollar her zaman hata ayıklama ve bakım işlemlerini kolaylaştırdı.


Genel olarak, benim (oldukça acı veren), etrafını çevreleyen örnek alanlarına doğrudan erişen iç sınıfların hata ayıklamasıyla karşılaştığında, bu uygulamanın kendisiyle ilişkili bilinen kötülüklerle birlikte küresel devlet kullanımına benzer olduğu izlenimini uyandırdı .

Elbette, Java böyle bir "yarı küresel" nin zararının sınıf içerisine alınmasını sağladı, ancak belirli bir iç sınıfın hata ayıklaması gerektiğinde, böyle bir grup yardımcısının ağrıyı azaltmaya yardımcı olmadığını hissettim: Belirli bir sorunlu nesnenin analizine tamamen odaklanmak yerine "yabancı" anlambilim ve detayları göz önünde bulundurmak.


Bütünlüğün iyiliği için, yukarıdaki akıl yürütmenin uygulanmadığı durumlar olabilir . Örneğin, map.keySet javadocs okumalarıma göre , bu özellik sıkı bağlantı sağlar ve bunun sonucunda statik olmayan sınıflara karşı argümanları geçersiz kılar:

SetBu haritada bulunan tuşların görünümünü döndürür . Küme harita tarafından desteklenir, bu nedenle haritadaki değişiklikler kümeye yansır ve bunun tam tersi ...

Yukarıdakilerin bir şekilde ilgili kodun aklınızı korumasını, test etmesini ve hata ayıklamasını kolaylaştıracağını düşünmemekle birlikte, en azından birisinin komplikasyonun amaçlanan işlevsellik ile eşleştiğini / gerekçelendirdiğini iddia etmesine izin vermesi mümkün değildir.


Deneyiminizi bu yüksek eşleşmenin yol açtığı problemlerle ilgili olarak paylaşıyorum, ancak eğitimin gerektirdiği kullanımla ilgili bir sorunum yok . Kendinize söylüyorsunuz, hiçbir zaman sahaya erişim gerekmedi, bu yüzden ne yazık ki, bu da sorumu tam olarak ele almıyor.
Frank

@ Gördüğünüz kadarıyla, öğretici rehberlik yorumum (Java Puzzlers tarafından destekleniyor gibi) yorumum, sadece bunun gerekli olduğunu kanıtlayabildiğiniz zaman statik olmayan sınıfları kullanmak ve özellikle de alternatif yolların olduğunu kanıtlamak gibi. daha da kötüsü . Tecrübelerime alternatif yolları her zaman daha iyi çıktı dikkati çekiyor
tatarcık

Mesele bu. Her zaman daha iyiyse neden rahatsız ediyoruz?
Frank

@ Başka bir cevap , her zaman böyle olmadığına dair çarpıcı örnekler sunar . Benim okuma Başına map.keySet javadocs bu özellik sıkı bağlantı önerir ve bunun sonucunda statik olmayan sınıflar "haritaya değişiklikler sette yansıtılmasını sağlayan seti, harita tarafından desteklenmektedir ve tersi karşı Geçersiz Kılmaktadır argümanlar ... "
gnat

1

Bazı kavram yanılgıları:

Daha az eşleşme: [statik iç] sınıf dış sınıfının özelliklerine doğrudan erişemediğinden genel olarak daha az eşleşme elde ederiz.

IIRC - Aslında yapabilir. (Tabii ki, eğer onlar nesne alanlarıysa, bakacak bir dış nesneye sahip olmayacaktır. Bununla birlikte, eğer böyle bir nesneyi herhangi bir yerden alırsa, o zaman bu alanlara doğrudan erişebilir).

Tek Sınıf: Sınıf yükleyici her seferinde yeni bir sınıfa bakmaya ihtiyaç duymaz, biz dış sınıfa ait bir nesne yaratırız. Sadece aynı sınıf için tekrar tekrar yeni nesneler elde ediyoruz.

Burada ne demek istediğinizi tam olarak anlayamadım. Sınıf yükleyici, her bir sınıf için (sınıf yüklendiğinde) toplam olarak sadece iki kez, bir kez katılır.


Rambling şöyle:

Javaland'da sınıflar oluşturmaktan biraz korkmuş gibiyiz. Kendini önemli bir kavram gibi hissetmesi gerektiğini düşünüyorum. Genelde kendi dosyasına aittir. Önemli bir kazan plakasına ihtiyacı var. Tasarımına dikkat etmesi gerekiyor.

Diğer dillere karşı - örneğin, Scala veya belki de C ++: İki bit veri bir araya geldiğinde, küçük bir tutucu (sınıf, yapı, her neyse) yaratan hiçbir drama yoktur. Esnek erişim kuralları, kazanı kısarak size ilgili kodu fiziksel olarak daha yakın tutmanıza yardımcı olur. Bu, iki sınıf arasındaki 'zihin mesafenizi' azaltır.

İç sınıfın özel kalmasını sağlayarak tasarımla ilgili endişeleri doğrudan erişimden uzaklaştırabiliriz.

(... 'Neden statik olmayan bir kamu iç sınıfı?' Diye sorsaydınız , bu çok iyi bir soru - bunun için henüz haklı bir dava bulduğumu sanmıyorum).

Bunları kod okunabilirliğine ve DRY ihlallerinden ve kaynatmadan kaçınmaya yardımcı olduğunda istediğiniz zaman kullanabilirsiniz. Hazır bir örneğim yok, çünkü deneyimimde bu kadar sık ​​olmuyor, ama oluyor.


Statik iç sınıflar hala iyi bir varsayılan yaklaşımdır, btw. Statik olmayan, iç sınıfın dışa atıfta bulunduğu, oluşturulmuş ekstra bir gizli alana sahiptir.


0

Fabrika modeli oluşturmak için kullanılabilir. Bir fabrika deseni, genellikle yaratılan nesnelerin çalışması için ek yapıcı parametrelerine ihtiyaç duydukları durumlarda kullanılır, ancak her seferinde sağlamak için can sıkıcıdırlar:

Düşünmek:

public class QueryFactory {
    @Inject private Database database;

    public Query create(String sql) {
        return new Query(database, sql);
    }
}

public class Query {
    private final Database database;
    private final String sql;

    public Query(Database database, String sql) {
        this.database = database;
        this.sql = sql;
    } 

    public List performQuery() {
        return database.query(sql);
    }
}

Olarak kullanılır:

Query q = queryFactory.create("SELECT * FROM employees");

q.performQuery();

Aynı şey statik olmayan bir iç sınıfla da başarılabilir:

public class QueryFactory {
    @Inject private Database database;

    public class Query {
        private final String sql;

        public Query(String sql) {
            this.sql = sql;
        }

        public List performQuery() {
            return database.query(sql);
        }
    }
}

Olarak kullan:

Query q = queryFactory.new Query("SELECT * FROM employees");

q.performQuery();

Fabrikalar create, createFromSQLveya benzeri özel olarak adlandırılmış yöntemlere sahip olmaktan yararlanır createFromTemplate. Aynısı burada da çoklu iç sınıflar şeklinde yapılabilir:

public class QueryFactory {

    public class SQL { ... }
    public class Native { ... }
    public class Builder { ... }

}

Fabrikadan inşa edilmiş sınıfa daha az parametre geçmesi gerekir, ancak okunabilirlik biraz zorlanabilir.

Karşılaştırmak:

Queries.Native q = queries.new Native("select * from employees");

vs

Query q = queryFactory.createNative("select * from employees");
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.