'Sabitleri' paylaşmak için java'da statik alanlara sahip arayüzler


116

Java'ya girmek için bazı açık kaynak Java projelerine bakıyorum ve çoğunun bir çeşit 'sabit' arayüze sahip olduğunu fark ediyorum.

Örneğin, processing.org'un PConstants.java adlı bir arabirimi vardır ve diğer çekirdek sınıfların çoğu bu arabirimi uygular. Arayüz, statik üyelerle dolu. Bu yaklaşımın bir nedeni var mı yoksa bu kötü bir uygulama olarak kabul ediliyor mu? Neden mantıklı olduğu yerde numaralandırmayı kullanmıyorsunuz? veya statik bir sınıfı ?

Bir tür sözde 'küresel değişkenlere' izin vermek için bir arayüz kullanmayı garip buluyorum.

public interface PConstants {

  // LOTS OF static fields...

  static public final int SHINE = 31;

  // emissive (by default kept black)
  static public final int ER = 32;
  static public final int EG = 33;
  static public final int EB = 34;

  // has this vertex been lit yet
  static public final int BEEN_LIT = 35;

  static public final int VERTEX_FIELD_COUNT = 36;


  // renderers known to processing.core

  static final String P2D    = "processing.core.PGraphics2D";
  static final String P3D    = "processing.core.PGraphics3D";
  static final String JAVA2D = "processing.core.PGraphicsJava2D";
  static final String OPENGL = "processing.opengl.PGraphicsOpenGL";
  static final String PDF    = "processing.pdf.PGraphicsPDF";
  static final String DXF    = "processing.dxf.RawDXF";


  // platform IDs for PApplet.platform

  static final int OTHER   = 0;
  static final int WINDOWS = 1;
  static final int MACOSX  = 2;
  static final int LINUX   = 3;

  static final String[] platformNames = {
    "other", "windows", "macosx", "linux"
  };

  // and on and on

}

15
Not: static finalgerekli değildir, bir arayüz için fazlalıktır.
ThomasW

Ayrıca , ve platformNamesolabilir , ancak kesinlikle sabit olmadığını unutmayın. Tek sabit dizi sıfır uzunluklu dizidir. publicstaticfinal
Vlasec

@ThomasW Bunun birkaç yıllık olduğunu biliyorum, ancak yorumunuzda bir hatayı belirtmem gerekiyor. static finalzorunlu olarak gereksiz değildir. Yalnızca finalanahtar kelimeyi içeren bir sınıf veya arabirim alanı, siz sınıfın veya arabirimin nesnelerini oluştururken o alanın ayrı örneklerini oluşturur. Kullanmak static final, her nesnenin o alan için bir bellek konumunu paylaşmasını sağlar. Başka bir deyişle, bir sınıf MyClass bir alana sahipse final String str = "Hello";, MyClass'ın N örneği için bellekte str alanının N örneği olacaktır. staticAnahtar kelimenin eklenmesi yalnızca 1 örnekle sonuçlanır.
Sintrias

Yanıtlar:


160

Genellikle kötü bir uygulama olarak kabul edilir. Sorun, sabitlerin uygulama sınıfının genel "arayüzünün" (daha iyi bir kelime istemek için) bir parçası olmasıdır. Bu, uygulama sınıfının tüm bu değerleri yalnızca dahili olarak gerekli olsalar bile harici sınıflara yayınladığı anlamına gelir. Sabitler kod boyunca çoğalır. Bir örnek Swing'deki SwingConstants arabirimidir ve düzinelerce sınıf tarafından uygulanır ve tüm sabitleri (kullanmadıkları bile) kendi sabitleri olarak "yeniden dışa aktarır " .

Ama sadece benim sözüme güvenmeyin , Josh Bloch bunun kötü olduğunu da söylüyor :

Sabit arayüz modeli, arayüzlerin kötü kullanımıdır. Bir sınıfın bazı sabitleri dahili olarak kullandığı bir uygulama detayıdır. Sabit bir arabirim uygulamak, bu uygulama ayrıntısının sınıfın dışa aktarılan API'sine sızmasına neden olur. Sınıfın sabit bir arabirim uygulamasının bir sınıfın kullanıcıları için hiçbir önemi yoktur. Hatta kafalarını karıştırabilir. Daha da kötüsü, bir taahhüdü temsil eder: gelecekteki bir sürümde sınıf, artık sabitleri kullanması gerekmeyecek şekilde değiştirilirse, ikili uyumluluğu sağlamak için yine de arabirimi uygulamalıdır. Son olmayan bir sınıf sabit bir arabirim uygularsa, tüm alt sınıflarının ad alanları arabirimdeki sabitler tarafından kirletilir.

Bir sıralama daha iyi bir yaklaşım olabilir. Veya sabitleri basitçe somutlaştırılamayan bir sınıfa genel statik alanlar olarak koyabilirsiniz. Bu, başka bir sınıfın kendi API'sini kirletmeden onlara erişmesine izin verir.


8
Enums burada kırmızı ringa balığı - ya da en azından ayrı bir soru. Elbette numaralandırmalar kullanılmalı, ancak uygulayıcılar tarafından ihtiyaç duyulmuyorsa bunlar da gizlenmelidir.
DJClayworth

12
BTW: Örneği olmayan bir numaralandırmayı, başlatılamayan bir sınıf olarak kullanabilirsiniz. ;)
Peter Lawrey

5
Ama neden bu arayüzleri ilk etapta uyguluyorsunuz? Neden onları sabitler deposu olarak kullanmıyorsunuz? Küresel olarak paylaşılan bir tür sabitlere ihtiyacım olursa, bunu yapmanın "daha temiz" yollarını görmüyorum.
shadox

2
@DanDyer evet, ancak bir arayüz bazı bildirimleri örtük hale getiriyor. Genel statik final gibi sadece bir varsayılandır. Neden bir sınıfla uğraşıyorsun? Enum - duruma göre değişir. Bir enum, farklı varlıklar için bir değer koleksiyonu değil, bir varlık için olası değerlerin bir koleksiyonunu tanımlamalıdır.
shadox

4
Şahsen ben Josh'un topa yanlış vurduğunu hissediyorum. Sabitlerinizin - onları ne tür bir nesneye koyduğunuza bakılmaksızın - sızmasını istemiyorsanız, bunların dışa aktarılan kodun parçası OLMADIĞINDAN emin olmanız gerekir. Arayüz veya sınıf, her ikisi de dışa aktarılabilir. Yani sorulacak doğru soru şu değil: onları ne tür bir nesneye koyarım ama bu nesneyi nasıl düzenlerim. Ve sabitler dışa aktarılan kodda kullanılıyorsa, yine de dışa aktarıldıktan sonra kullanılabilir olduklarından emin olmalısınız. Bu yüzden "kötü uygulama" iddiası benim mütevazı görüşüme göre geçersiz.
Lawrence

99

Java 1.5+ sürümünde bir "sabitler arabirimi" uygulamak yerine, sabitleri / statik yöntemleri başka bir sınıftan / arabirimden içe aktarmak için statik içe aktarmalar kullanabilirsiniz:

import static com.kittens.kittenpolisher.KittenConstants.*;

Bu, sınıflarınızın işlevselliği olmayan arayüzler uygulamasının çirkinliğini önler.

Sadece sabitleri saklamak için bir sınıfa sahip olma uygulamasına gelince, bazen bunun gerekli olduğunu düşünüyorum. Bir sınıfta doğal bir yeri olmayan bazı sabitler vardır, bu yüzden onları "nötr" bir yerde bulundurmak daha iyidir.

Ancak bir arabirim kullanmak yerine, özel bir kurucuya sahip bir son sınıf kullanın. (Statik olmayan işlevsellik / veri içermediğine dair güçlü bir mesaj göndererek sınıfın örneğini veya alt sınıfını oluşturmayı imkansız hale getirir.)

Örneğin:

/** Set of constants needed for Kitten Polisher. */
public final class KittenConstants
{
    private KittenConstants() {}

    public static final String KITTEN_SOUND = "meow";
    public static final double KITTEN_CUTENESS_FACTOR = 1;
}

Öyleyse, statik içe aktarım nedeniyle, önceki hatayı yeniden yapmak için arayüzler yerine sınıfları kullanmamız gerektiğini açıklıyorsunuz ?! Bu aptalca!
gizmo

11
Hayır, benim söylediğim bu değil. İki bağımsız şey söylüyorum. 1: Devralmayı kötüye kullanmak yerine statik içe aktarmaları kullanın. 2: Bir sabit havuzunuz olması gerekiyorsa, onu bir arayüz yerine son sınıf yapın.
Zarkonnen

"Sabit Arayüzler", herhangi bir kalıtımın parçası olacak şekilde tasarlanmamışsa, asla. Dolayısıyla, statik içe aktarma sadece sözdizimsel şeker içindir ve bu tür bir arayüzden miras almak korkunç bir hatadır. Sun'ın bunu yaptığını biliyorum ama onlarca başka temel hata da yaptılar, bu onları taklit etmek için bir bahane değil.
gizmo

3
Soru için gönderilen kodla ilgili sorunlardan biri, arayüz uygulamasının yalnızca sabitlere daha kolay erişim sağlamak için kullanılmasıdır. FooInterface'i uygulayan bir şey gördüğümde, bunun işlevselliğini etkilemesini bekliyorum ve yukarıdakiler bunu ihlal ediyor. Statik içe aktarma bu sorunu çözer.
Zarkonnen

2
gizmo - Statik içe aktarımların hayranı değilim, ama onun orada yaptığı şey sınıf adını, yani ConstClass.SOME_CONST'u kullanmak zorunda kalmamak. Statik bir içe aktarma yapmak, bu üyeleri Z. arayüzden miras al demediğiniz sınıfa eklemez, aslında tam tersini söylüyor.
mtruesdell

8

Haklıymışım gibi davranmıyorum ama şu küçük örneğe bakalım:

public interface CarConstants {

      static final String ENGINE = "mechanical";
      static final String WHEEL  = "round";
      // ...

}

public interface ToyotaCar extends CarConstants //, ICar, ... {
      void produce();
}

public interface FordCar extends CarConstants //, ICar, ... {
      void produce();
}

// and this is implementation #1
public class CamryCar implements ToyotaCar {

      public void produce() {
           System.out.println("the engine is " + ENGINE );
           System.out.println("the wheel is " + WHEEL);
      }
}

// and this is implementation #2
public class MustangCar implements FordCar {

      public void produce() {
           System.out.println("the engine is " + ENGINE );
           System.out.println("the wheel is " + WHEEL);
      }
}

ToyotaCar, FordCar hakkında hiçbir şey bilmiyor ve FordCar, ToyotaCar hakkında hiçbir şey bilmiyor. ilke CarConstants değiştirilmelidir, ancak ...

Sabitler değiştirilmemelidir, çünkü tekerlek yuvarlak ve egine mekaniktir, ancak ... Gelecekte Toyota'nın araştırma mühendisleri elektronik motor ve düz tekerlekleri icat etti! Yeni arayüzümüzü görelim

public interface InnovativeCarConstants {

          static final String ENGINE = "electronic";
          static final String WHEEL  = "flat";
          // ...
}

ve şimdi soyutlamamızı değiştirebiliriz:

public interface ToyotaCar extends CarConstants

için

public interface ToyotaCar extends InnovativeCarConstants 

Ve şimdi, eğer MOTOR veya TEKERLEK ise temel değeri değiştirmemiz gerekirse, ToyotaCar Arayüzünü soyutlama düzeyinde değiştirebiliriz, uygulamalara dokunmayın.

GÜVENLİ DEĞİL, biliyorum, ama yine de bunu bilmek istiyorum


Fikrinizi şimdi 2019'da bilmek istiyorum. Benim için arayüz alanlarının bazı nesneler arasında paylaşılması gerekiyor.
Raining

Fikrinizle ilgili bir cevap yazdım: stackoverflow.com/a/55877115/5290519
Raining

bu, PMD kurallarının ne kadar sınırlı olduğuna dair güzel bir örnektir. Bürokrasi yoluyla daha iyi kod elde etmeye çalışmak boşuna bir girişim olmaya devam ediyor.
bebbo

6

Java'da bu kalıp için çok fazla nefret var. Bununla birlikte, statik sabitlerden oluşan bir arayüzün bazen bir değeri vardır. Temel olarak aşağıdaki koşulları yerine getirmeniz gerekir:

  1. Kavramlar, birkaç sınıfın genel arayüzünün bir parçasıdır.

  2. Değerleri gelecekteki sürümlerde değişebilir.

  3. Tüm uygulamaların aynı değerleri kullanması kritiktir.

Örneğin, varsayımsal bir sorgu diline bir uzantı yazdığınızı varsayalım. Bu uzantıda, bir dizin tarafından desteklenen bazı yeni işlemlerle dil sözdizimini genişleteceksiniz. Örneğin, jeo uzamsal sorguları destekleyen bir R-Tree'ye sahip olacaksınız.

Yani statik sabit ile bir genel arayüz yazarsınız:

public interface SyntaxExtensions {
     // query type
     String NEAR_TO_QUERY = "nearTo";

     // params for query
     String POINT = "coordinate";
     String DISTANCE_KM = "distanceInKm";
}

Şimdi daha sonra, yeni bir geliştirici daha iyi bir dizin oluşturması gerektiğini düşünür, bu nedenle gelir ve bir R * uygulaması oluşturur. Bu arayüzü yeni ağacına uygulayarak, farklı dizinlerin sorgu dilinde aynı sözdizimine sahip olacağını garanti eder. Dahası, daha sonra "nearTo" nun kafa karıştırıcı bir ad olduğuna karar verirseniz, bunu "insideDistanceInKm" olarak değiştirebilir ve yeni sözdiziminin tüm dizin uygulamalarınız tarafından dikkate alınacağını bilirsiniz.

Not: Bu örnek için ilham, Neo4j uzaysal kodundan alınmıştır.


5

Geçmişe bakmanın avantajı göz önüne alındığında, Java'nın birçok yönden kırıldığını görebiliriz. Java'nın önemli bir başarısızlığı, arayüzlerin soyut yöntemlerle ve statik son alanlarla sınırlandırılmasıdır. Scala gibi daha yeni, daha sofistike OO dilleri, arayüzleri, arity sıfıra sahip olabilen (sabitler!) Somut yöntemleri içerebilen (ve tipik olarak yapan) özelliklere göre sınıflandırır. Birleştirilebilir davranış birimleri olarak özellikler hakkında bir açıklama için bkz. Http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf . Scala'daki özelliklerin Java'daki arayüzlerle nasıl karşılaştırıldığına dair kısa bir açıklama için bkz. Http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-5. OO tasarımını öğretme bağlamında, arayüzlerin asla statik alanlar içermemesi gerektiğini iddia etmek gibi basit kurallar saçmadır. Pek çok özellik doğal olarak sabitleri içerir ve bu sabitler uygun şekilde özellik tarafından desteklenen genel "arayüzün" bir parçasıdır. Java kodunu yazarken, özellikleri temsil etmenin temiz ve zarif bir yolu yoktur, ancak arayüzlerde statik son alanlar kullanmak genellikle iyi bir çözümün parçasıdır.


12
Son derece iddialı ve günümüzde modası geçmiş.
Esko

1
Harika fikir (+1), ancak Java'ya karşı biraz fazla kritik olabilir.
peterh - Monica'yı eski durumuna getir

0

JVM spesifikasyonuna göre, bir Arayüzdeki alanlar ve yöntemler yalnızca Genel, Statik, Nihai ve Özet içerebilir. Inside Java VM'den referans

Varsayılan olarak, arayüzdeki tüm yöntemler, açıkça belirtmemiş olsanız bile soyuttur.

Arayüzler sadece spesifikasyon vermek içindir. Herhangi bir uygulama içeremez. Bu nedenle, spesifikasyonu değiştirmek için sınıfları uygulamaktan kaçınmak için, son haline getirilir. Arabirim somutlaştırılamadığından, arabirim adını kullanarak alana erişim için statik hale getirilirler.


0

Pleerock'a yorum yapacak kadar itibarım yok, bu yüzden bir cevap oluşturmam gerekiyor. Bunun için üzgünüm ama o bunda biraz çaba gösterdi ve ona cevap vermek istiyorum.

Pleerock, bu sabitlerin neden arayüzlerden ve kalıtımdan bağımsız olması gerektiğini göstermek için mükemmel bir örnek yarattınız. Uygulamanın müşterisi için, arabaların uygulamaları arasında teknik bir fark olması önemli değil. Müşteri için aynıdır, sadece arabalar. Yani, müşteri onlara I_Somecar gibi bir arayüz olan bu perspektiften bakmak istiyor. Uygulama boyunca müşteri, her bir farklı otomobil markası için farklı değil, yalnızca bir bakış açısı kullanacaktır.

Bir müşteri arabaları satın almadan önce karşılaştırmak isterse, aşağıdaki gibi bir yönteme sahip olabilir:

public List<Decision> compareCars(List<I_Somecar> pCars);

Arayüz, davranışla ilgili bir sözleşmedir ve farklı nesneleri tek bir perspektiften gösterir. Tasarlama şeklinize göre, her otomobil markasının kendi mirası olacak. Gerçekte oldukça doğru olmasına rağmen, arabalar tamamen farklı türdeki nesneleri karşılaştırmak gibi olabileceğinden o kadar farklı olabilir, sonunda farklı arabalar arasında seçim vardır. Ve bu, tüm markaların paylaşması gereken arayüzün perspektifidir. Sabitlerin seçimi bunu imkansız hale getirmemelidir. Lütfen Zarkonnen'in cevabını düşünün.


-1

Bu, Java 1.5'in varlığından ve bize numaralandırmadan önceki bir zamandan geldi. Bundan önce, bir dizi sabiti veya kısıtlı değeri tanımlamanın iyi bir yolu yoktu.

Bu hala, çoğu zaman ya geriye dönük uyumluluk için ya da birçok projede kurtulmak için gereken yeniden düzenleme miktarı nedeniyle kullanılmaktadır.


2
Java 5'ten önce, tür güvenli numaralandırma modelini kullanabiliyordunuz (bkz. Java.sun.com/developer/Books/shiftintojava/page1.html ).
Dan Dyer
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.