Antarmuka dengan bidang statis di java untuk berbagi 'konstanta'


116

Saya melihat beberapa proyek Java open source untuk masuk ke Java dan melihat banyak dari mereka memiliki semacam antarmuka 'konstanta'.

Misalnya, processing.org memiliki antarmuka yang disebut PConstants.java , dan sebagian besar kelas inti lainnya mengimplementasikan antarmuka ini. Antarmuka penuh dengan anggota statis. Apakah ada alasan untuk pendekatan ini, atau apakah ini dianggap praktik yang buruk? Mengapa tidak menggunakan enum yang masuk akal , atau kelas statis?

Saya merasa aneh menggunakan antarmuka untuk memungkinkan semacam 'variabel global' palsu.

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
Catatan: static finaltidak perlu, ini berlebihan untuk sebuah antarmuka.
ThomasW

Perhatikan juga bahwa platformNamesmungkin public, staticdan final, tetapi ini jelas bukan sebuah konstanta. Larik konstan hanya satu dengan panjang nol.
Vlasec

@ThomasW Saya tahu ini berumur beberapa tahun, tetapi saya perlu menunjukkan kesalahan dalam komentar Anda. static finaltidak selalu berlebihan. Bidang kelas atau antarmuka dengan hanya finalkata kunci akan membuat contoh terpisah dari bidang itu saat Anda membuat objek dari kelas atau antarmuka. Menggunakan static finalakan membuat setiap objek berbagi lokasi memori untuk bidang itu. Dengan kata lain, jika sebuah kelas MyClass memiliki sebuah field final String str = "Hello";, untuk N instance dari MyClass, akan ada N instance dari field str di memori. Menambahkan statickata kunci hanya akan menghasilkan 1 contoh.
Sintrias

Jawaban:


160

Ini umumnya dianggap praktik yang buruk. Masalahnya adalah bahwa konstanta merupakan bagian dari "antarmuka" publik (karena menginginkan kata yang lebih baik) dari kelas implementasi. Ini berarti bahwa kelas pelaksana memublikasikan semua nilai ini ke kelas eksternal meskipun hanya diperlukan secara internal. Konstanta berkembang biak di seluruh kode. Contohnya adalah antarmuka SwingConstants di Swing, yang diimplementasikan oleh lusinan kelas yang semuanya "mengekspor ulang" semua konstantanya (bahkan yang tidak mereka gunakan) sebagai milik mereka.

Tapi jangan hanya percaya kata-kata saya, Josh Bloch juga mengatakan itu buruk:

Pola antarmuka yang konstan adalah penggunaan antarmuka yang buruk. Bahwa kelas menggunakan beberapa konstanta secara internal adalah detail implementasi. Mengimplementasikan antarmuka yang konstan menyebabkan detail implementasi ini bocor ke API yang diekspor kelas. Bukan konsekuensi bagi pengguna kelas bahwa kelas tersebut mengimplementasikan antarmuka konstan. Bahkan, hal itu mungkin membingungkan mereka. Lebih buruk lagi, ini merepresentasikan sebuah komitmen: jika di rilis mendatang kelas tersebut dimodifikasi sehingga tidak lagi perlu menggunakan konstanta, ia masih harus mengimplementasikan antarmuka untuk memastikan kompatibilitas biner. Jika kelas nonfinal mengimplementasikan antarmuka konstan, semua subkelasnya akan memiliki namespace yang tercemar oleh konstanta di antarmuka.

Enum mungkin merupakan pendekatan yang lebih baik. Atau Anda bisa meletakkan konstanta sebagai bidang statis publik di kelas yang tidak bisa dibuat instance-nya. Ini memungkinkan kelas lain untuk mengaksesnya tanpa mencemari API-nya sendiri.


8
Enum adalah ikan haring merah di sini - atau setidaknya pertanyaan terpisah. Enum tentu saja harus digunakan, tetapi juga harus disembunyikan jika tidak diperlukan oleh pelaksana.
DJClayworth

12
BTW: Anda dapat menggunakan enum tanpa instance sebagai kelas yang tidak dapat dibuat instance-nya. ;)
Peter Lawrey

5
Tapi mengapa mengimplementasikan antarmuka tersebut? Mengapa tidak menggunakannya hanya sebagai repositori konstanta? Jika saya membutuhkan semacam konstanta yang dibagikan secara global, saya tidak melihat cara yang "lebih bersih" untuk melakukannya.
shadox

2
@DanDyer ya, tetapi antarmuka membuat beberapa deklarasi implisit. Seperti final statis publik hanyalah default. Mengapa repot-repot dengan kelas? Enum - yah, itu tergantung. Enum harus menentukan kumpulan nilai yang mungkin untuk entitas dan bukan kumpulan nilai untuk entitas yang berbeda.
shadox

4
Secara pribadi saya merasa Josh salah memukul bola. Jika Anda tidak ingin konstanta bocor - terlepas dari jenis objek yang Anda tempatkan - Anda perlu memastikan konstanta BUKAN bagian dari kode yang diekspor. Antarmuka atau kelas, keduanya dapat diekspor. Jadi pertanyaan yang tepat untuk ditanyakan bukanlah: dalam jenis objek apa saya meletakkannya tetapi bagaimana cara mengatur objek ini. Dan jika konstanta digunakan dalam kode yang diekspor, Anda masih harus memastikannya tersedia setelah diekspor. Jadi, klaim "praktik buruk" menurut pendapat saya tidak valid.
Lawrence

99

Daripada menerapkan "antarmuka konstanta", di Java 1.5+, Anda dapat menggunakan impor statis untuk mengimpor metode konstanta / statis dari kelas / antarmuka lain:

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

Ini untuk menghindari keburukan dalam membuat kelas Anda mengimplementasikan antarmuka yang tidak memiliki fungsionalitas.

Adapun praktik memiliki kelas hanya untuk menyimpan konstanta, saya pikir terkadang perlu. Ada konstanta tertentu yang tidak memiliki tempat alami di kelas, jadi lebih baik menempatkannya di tempat "netral".

Namun alih-alih menggunakan antarmuka, gunakan kelas terakhir dengan konstruktor pribadi. (Membuat tidak mungkin untuk membuat instance atau membuat subkelas kelas, mengirimkan pesan yang kuat bahwa itu tidak berisi fungsionalitas / data non-statis.)

Misalnya:

/** 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;
}

Jadi, Anda menjelaskan bahwa, karena impor statis, kita harus menggunakan kelas daripada antarmuka untuk melakukan ulang kesalahan yang sama seperti sebelumnya ?! Itu konyol!
alat

11
Tidak, sama sekali bukan itu yang saya katakan. Saya mengatakan dua hal independen. 1: gunakan impor statis alih-alih menyalahgunakan warisan. 2: Jika Anda harus memiliki repositori konstanta, jadikan itu sebagai kelas terakhir, bukan antarmuka.
Zarkonnen

"Antarmuka Konstan" yang tidak dirancang untuk menjadi bagian dari warisan apa pun, tidak akan pernah. Jadi impor statis hanya untuk gula sintaksis, dan mewarisi dari antarmuka semacam itu merupakan kesalahan yang mengerikan. Saya tahu Sun melakukan ini, tetapi mereka juga membuat banyak kesalahan dasar lainnya, itu bukan alasan untuk meniru mereka.
alat

3
Salah satu masalah dengan kode yang diposting untuk pertanyaan tersebut adalah bahwa implementasi antarmuka digunakan hanya untuk mendapatkan akses yang lebih mudah ke konstanta. Ketika saya melihat sesuatu mengimplementasikan FooInterface, saya berharap hal itu memengaruhi fungsinya, dan hal di atas melanggar ini. Impor statis memperbaiki masalah itu.
Zarkonnen

2
gizmo - Saya bukan penggemar impor statis, tetapi yang dia lakukan di sana adalah menghindari penggunaan nama kelas, yaitu ConstClass.SOME_CONST. Melakukan impor statis tidak menambahkan anggota tersebut ke kelas yang Anda Z. tidak katakan untuk mewarisi dari antarmuka, dia malah mengatakan sebaliknya.
mtruesdell

8

Saya tidak berpura-pura berhak menjadi benar, tetapi mari kita lihat contoh kecil ini:

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 tidak tahu apa-apa tentang FordCar, dan FordCar tidak tahu tentang ToyotaCar. prinsip CarConstants harus diubah, tapi ...

Konstanta tidak boleh diubah, karena roda itu bulat dan egine adalah mekanis, tetapi ... Di masa depan, para insinyur riset Toyota menemukan mesin elektronik dan roda datar! Mari kita lihat antarmuka baru kita

public interface InnovativeCarConstants {

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

dan sekarang kita bisa mengubah abstraksi kita:

public interface ToyotaCar extends CarConstants

untuk

public interface ToyotaCar extends InnovativeCarConstants 

Dan sekarang jika kita perlu mengubah nilai inti jika ENGINE atau WHEEL kita dapat mengubah Antarmuka ToyotaCar pada tingkat abstraksi, jangan menyentuh implementasi

Ini TIDAK AMAN, saya tahu, tapi saya masih ingin tahu apa pendapat Anda tentang hal ini


Saya ingin mengetahui ide Anda sekarang di tahun 2019. Bagi saya bidang antarmuka dimaksudkan untuk dibagikan di antara beberapa objek.
Hujan

Saya menulis jawaban terkait dengan ide Anda: stackoverflow.com/a/55877115/5290519
Hujan

ini adalah contoh yang baik tentang bagaimana aturan PMD sangat terbatas. Mencoba mendapatkan kode yang lebih baik melalui birokrasi tetap merupakan upaya yang sia-sia.
bebbo

6

Ada banyak kebencian atas pola ini di Jawa. Namun, antarmuka konstanta statis terkadang memiliki nilai. Pada dasarnya Anda harus memenuhi ketentuan berikut:

  1. Konsep adalah bagian dari antarmuka publik dari beberapa kelas.

  2. Nilai mereka mungkin berubah dalam rilis mendatang.

  3. Sangat penting bahwa semua implementasi menggunakan nilai yang sama.

Misalnya, Anda menulis ekstensi ke bahasa kueri hipotetis. Dalam ekstensi ini Anda akan memperluas sintaks bahasa dengan beberapa operasi baru, yang didukung oleh indeks. Misalnya, Anda akan memiliki R-Tree yang mendukung kueri geospasial.

Jadi Anda menulis antarmuka publik dengan konstanta statis:

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

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

Sekarang nanti, pengembang baru berpikir dia perlu membangun indeks yang lebih baik, jadi dia datang dan membangun implementasi R *. Dengan mengimplementasikan antarmuka ini di pohon barunya, dia menjamin bahwa indeks yang berbeda akan memiliki sintaks yang identik dalam bahasa kueri. Selain itu, jika Anda kemudian memutuskan bahwa "nearTo" adalah nama yang membingungkan, Anda dapat mengubahnya menjadi "withinDistanceInKm", dan mengetahui bahwa sintaks baru akan diterapkan oleh semua implementasi indeks Anda.

PS: Inspirasi untuk contoh ini diambil dari kode spasial Neo4j.


5

Dengan melihat ke belakang, kita bisa melihat bahwa Java rusak dalam banyak hal. Salah satu kegagalan utama Java adalah pembatasan antarmuka ke metode abstrak dan bidang final statis. Bahasa OO yang lebih baru dan lebih canggih seperti Scala menggolongkan antarmuka dengan ciri-ciri yang dapat (dan biasanya memang) menyertakan metode konkret, yang mungkin memiliki arity nol (konstanta!). Untuk penjelasan tentang sifat-sifat sebagai unit perilaku yang dapat disusun, lihat http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf . Untuk penjelasan singkat tentang bagaimana ciri-ciri di Scala dibandingkan dengan antarmuka di Java, lihat http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-5. Dalam konteks pengajaran desain OO, aturan sederhana seperti menyatakan bahwa antarmuka tidak boleh menyertakan bidang statis adalah hal yang konyol. Banyak ciri yang secara alami menyertakan konstanta dan konstanta ini secara tepat merupakan bagian dari "antarmuka" publik yang didukung oleh sifat tersebut. Dalam penulisan kode Java, tidak ada cara yang rapi dan elegan untuk merepresentasikan ciri-ciri, tetapi menggunakan bidang akhir statis dalam antarmuka sering kali merupakan bagian dari solusi yang baik.


12
Sangat megah dan sekarang sudah ketinggalan zaman.
Esko

1
Wawasan yang luar biasa (+1), meskipun mungkin sedikit terlalu kritis terhadap Java.
peterh - Pulihkan Monica

0

Menurut spesifikasi JVM, kolom dan metode dalam Antarmuka hanya dapat memiliki Publik, Statis, Final, dan Abstrak. Referensi dari Inside Java VM

Secara default, semua metode dalam antarmuka abstrak bahkan meskipun Anda tidak menyebutkannya secara eksplisit.

Antarmuka dimaksudkan untuk memberikan spesifikasi saja. Itu tidak boleh berisi implementasi apa pun. Sehingga untuk menghindari implementasi class untuk merubah spesifikasi maka dibuat final. Karena Antarmuka tidak dapat dipakai, mereka dibuat statis untuk mengakses bidang menggunakan nama antarmuka.


0

Saya tidak memiliki reputasi yang cukup untuk memberikan komentar kepada Pleerock, untuk itu saya harus membuat jawaban. Saya minta maaf untuk itu, tapi dia berusaha keras dan saya ingin menjawabnya.

Pleerock, Anda membuat contoh sempurna untuk menunjukkan mengapa konstanta tersebut harus independen dari antarmuka dan independen dari pewarisan. Untuk klien aplikasi tidak penting bahwa ada perbedaan teknis antara implementasi mobil tersebut. Mereka sama untuk klien, hanya mobil. Jadi, klien ingin melihatnya dari perspektif itu, yang merupakan antarmuka seperti I_Somecar. Melalui aplikasi ini klien hanya akan menggunakan satu perspektif dan bukan perspektif yang berbeda untuk setiap merek mobil yang berbeda.

Jika klien ingin membandingkan mobil sebelum membeli, dia dapat memiliki metode seperti ini:

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

Antarmuka adalah kontrak tentang perilaku dan menunjukkan objek yang berbeda dari satu perspektif. Cara Anda mendesainnya, akankah setiap merek mobil memiliki garis warisannya masing-masing. Meskipun pada kenyataannya cukup benar, karena mobil bisa begitu berbeda sehingga bisa seperti membandingkan jenis objek yang sama sekali berbeda, pada akhirnya ada pilihan di antara mobil yang berbeda. Dan itulah perspektif antarmuka yang dimiliki semua merek. Pemilihan konstanta seharusnya tidak membuat hal ini menjadi tidak mungkin. Tolong, pertimbangkan jawaban Zarkonnen.


-1

Ini datang dari waktu sebelum Java 1.5 ada dan membawa enum kepada kami. Sebelumnya, tidak ada cara yang baik untuk mendefinisikan sekumpulan konstanta atau nilai yang dibatasi.

Ini masih digunakan, sebagian besar waktu baik untuk kompatibilitas ke belakang atau karena jumlah refactoring yang diperlukan untuk menghilangkannya, dalam banyak proyek.


2
Sebelum Java 5, Anda dapat menggunakan pola enum tipe aman (lihat java.sun.com/developer/Books/shiftintojava/page1.html ).
Dan Dyer
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.