การเชื่อมต่อกับเขตข้อมูลคงที่ใน java สำหรับการแบ่งปัน 'ค่าคงที่'


116

ฉันกำลังดูโปรเจ็กต์ Java โอเพ่นซอร์สเพื่อเข้าสู่ Java และสังเกตเห็นว่าหลาย ๆ โปรเจ็กต์มีอินเทอร์เฟซ 'ค่าคงที่' บางอย่าง

ตัวอย่างเช่นprocessing.orgมีอินเทอร์เฟซที่เรียกว่าPConstants.javaและคลาสหลักอื่น ๆ ส่วนใหญ่ใช้อินเทอร์เฟซนี้ อินเทอร์เฟซเต็มไปด้วยสมาชิกแบบคงที่ มีเหตุผลสำหรับแนวทางนี้หรือถือว่าเป็นการปฏิบัติที่ไม่ดี? ทำไมไม่ใช้ enums ในที่ที่เหมาะสมหรือคลาสคงที่?

ฉันคิดว่ามันแปลกที่จะใช้อินเทอร์เฟซเพื่ออนุญาต 'ตัวแปรส่วนกลาง' หลอกบางประเภท

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
หมายเหตุ: static finalไม่จำเป็นมันซ้ำซ้อนสำหรับอินเทอร์เฟซ
ThomasW

นอกจากนี้ทราบว่าplatformNamesอาจจะเป็นpublic, staticและfinalแต่ก็เป็นที่แน่นอนไม่คงที่ อาร์เรย์คงที่เท่านั้นคือหนึ่งที่มีความยาวเป็นศูนย์
Vlasec

@ThomasW ฉันรู้ว่านี่อายุไม่กี่ปี แต่ฉันต้องการชี้ให้เห็นข้อผิดพลาดในความคิดเห็นของคุณ static finalไม่จำเป็นต้องซ้ำซ้อน ฟิลด์คลาสหรืออินเทอร์เฟซที่มีเพียงfinalคีย์เวิร์ดจะสร้างอินสแตนซ์แยกต่างหากของฟิลด์นั้นเมื่อคุณสร้างอ็อบเจ็กต์ของคลาสหรืออินเทอร์เฟซ การใช้static finalจะทำให้แต่ละวัตถุแบ่งปันตำแหน่งหน่วยความจำสำหรับเขตข้อมูลนั้น กล่าวอีกนัยหนึ่งถ้าคลาส MyClass มีฟิลด์final String str = "Hello";สำหรับ N อินสแตนซ์ของ MyClass จะมี N อินสแตนซ์ของฟิลด์ str ในหน่วยความจำ การเพิ่มstaticคำหลักจะทำให้ได้อินสแตนซ์เพียง 1 รายการ
Sintrias

คำตอบ:


160

โดยทั่วไปถือว่าเป็นการปฏิบัติที่ไม่ดี ปัญหาคือค่าคงที่เป็นส่วนหนึ่งของ "อินเทอร์เฟซ" สาธารณะ (เพื่อต้องการคำที่ดีกว่า) ของคลาสการนำไปใช้ ซึ่งหมายความว่าคลาสที่นำไปใช้งานกำลังเผยแพร่ค่าเหล่านี้ทั้งหมดไปยังคลาสภายนอกแม้ว่าจะต้องการภายในเท่านั้น ค่าคงที่เพิ่มขึ้นตลอดรหัส ตัวอย่างคือSwingConstantsติดต่อในสวิงซึ่งดำเนินการโดยนับสิบของชั้นเรียนที่ทุกคน "การส่งออก" ทั้งหมดของค่าคงที่ของมัน (แม้คนที่พวกเขาไม่ได้ใช้) เป็นของตัวเอง

แต่อย่าเพิ่งใช้คำพูดของฉันJosh Bloch ยังบอกว่ามันไม่ดี:

รูปแบบอินเทอร์เฟซคงที่คือการใช้อินเทอร์เฟซที่ไม่ดี การที่ชั้นเรียนใช้ค่าคงที่ภายในเป็นรายละเอียดการนำไปใช้งาน การใช้อินเทอร์เฟซคงที่ทำให้รายละเอียดการใช้งานนี้รั่วไหลไปยัง API ที่ส่งออกของคลาส ไม่มีผลต่อผู้ใช้คลาสที่คลาสใช้อินเทอร์เฟซคงที่ ในความเป็นจริงมันอาจทำให้พวกเขาสับสนด้วยซ้ำ ที่แย่กว่านั้นคือแสดงถึงความมุ่งมั่น: หากในอนาคตรีลีสคลาสได้รับการแก้ไขเพื่อที่จะไม่ต้องใช้ค่าคงที่อีกต่อไปก็ยังต้องใช้อินเทอร์เฟซเพื่อให้แน่ใจว่าเข้ากันได้กับไบนารี ถ้าคลาสที่ไม่ใช่ขั้นสุดท้ายใช้อินเทอร์เฟซคงที่คลาสย่อยทั้งหมดจะมีเนมสเปซที่ปนเปื้อนจากค่าคงที่ในอินเทอร์เฟซ

enum อาจเป็นแนวทางที่ดีกว่า หรือคุณสามารถใส่ค่าคงที่เป็นฟิลด์คงที่สาธารณะในคลาสที่ไม่สามารถสร้างอินสแตนซ์ได้ สิ่งนี้ช่วยให้คลาสอื่นสามารถเข้าถึงได้โดยไม่ทำให้เสีย API ของตัวเอง


8
Enums เป็นปลาชนิดหนึ่งสีแดงที่นี่หรืออย่างน้อยก็เป็นคำถามแยกต่างหาก แน่นอนว่าควรใช้ Enums แต่ควรซ่อนไว้ด้วยหากผู้ใช้ไม่ต้องการ
DJClayworth

12
BTW: คุณสามารถใช้ enum โดยไม่มีอินสแตนซ์เป็นคลาสที่ไม่สามารถสร้างอินสแตนซ์ได้ ;)
Peter Lawrey

5
แต่ทำไมต้องใช้อินเทอร์เฟซเหล่านี้ตั้งแต่แรก? ทำไมไม่ใช้เป็นที่เก็บค่าคงที่? หากฉันต้องการค่าคงที่บางประเภทที่ใช้ร่วมกันทั่วโลกฉันไม่เห็นวิธีที่ "สะอาดกว่า" ในการทำ
shadox

2
@DanDyer ใช่ แต่อินเทอร์เฟซทำให้การประกาศบางอย่างมีนัย เช่นเดียวกับสาธารณะคงสุดท้ายเป็นเพียงค่าเริ่มต้น ทำไมต้องกังวลกับชั้นเรียน? Enum - ก็ขึ้นอยู่กับ enum ควรกำหนดคอลเล็กชันของค่าที่เป็นไปได้สำหรับเอนทิตีไม่ใช่การรวบรวมค่าสำหรับเอนทิตีที่แตกต่างกัน
shadox

4
โดยส่วนตัวแล้วฉันรู้สึกว่า Josh ตีลูกผิด หากคุณไม่ต้องการให้ค่าคงที่ของคุณรั่วไหล - ไม่ว่าคุณจะวางวัตถุประเภทใดก็ตามคุณต้องตรวจสอบให้แน่ใจว่าไม่ได้เป็นส่วนหนึ่งของรหัสที่ส่งออก อินเทอร์เฟซหรือคลาสทั้งสองสามารถส่งออกได้ คำถามที่ถูกต้องที่จะถามไม่ใช่: ฉันจะใส่วัตถุประเภทไหน แต่ฉันจะจัดระเบียบวัตถุนี้ได้อย่างไร และหากใช้ค่าคงที่ในโค้ดที่ส่งออกคุณยังต้องแน่ใจว่าค่าคงที่พร้อมใช้งานเมื่อส่งออก ดังนั้นการกล่าวอ้าง "การปฏิบัติที่ไม่ดี" ในความเห็นที่ต่ำต้อยของฉันจึงไม่ถูกต้อง
Lawrence

99

แทนที่จะใช้ "อินเทอร์เฟซค่าคงที่" ใน Java 1.5+ คุณสามารถใช้การนำเข้าแบบคงที่เพื่ออิมพอร์ตค่าคงที่ / วิธีการคงที่จากคลาส / อินเทอร์เฟซอื่น:

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

วิธีนี้หลีกเลี่ยงความน่าเกลียดของการทำให้ชั้นเรียนของคุณใช้อินเทอร์เฟซที่ไม่มีฟังก์ชันการทำงาน

สำหรับการมีคลาสเพื่อเก็บค่าคงที่ฉันคิดว่าบางครั้งก็จำเป็น มีค่าคงที่บางอย่างที่ไม่มีสถานที่ตามธรรมชาติในชั้นเรียนดังนั้นจึงควรจัดให้อยู่ในที่ที่ "เป็นกลาง"

แต่แทนที่จะใช้อินเทอร์เฟซให้ใช้คลาสสุดท้ายกับคอนสตรัคเตอร์ส่วนตัว (ทำให้ไม่สามารถสร้างอินสแตนซ์หรือคลาสย่อยของคลาสส่งข้อความที่ชัดเจนว่าไม่มีฟังก์ชัน / ข้อมูลที่ไม่คงที่)

เช่น:

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

ดังนั้นคุณกำลังอธิบายว่าเนื่องจากการนำเข้าแบบคงที่เราควรใช้คลาสแทนการใช้อินเทอร์เฟซเพื่อทำข้อผิดพลาดซ้ำเหมือนเดิม?! มันโง่!
gizmo

11
ไม่นั่นไม่ใช่สิ่งที่ฉันพูดเลย ฉันกำลังพูดสองสิ่งที่เป็นอิสระ 1: ใช้การนำเข้าแบบคงที่แทนการใช้มรดกในทางที่ผิด 2: หากคุณต้องมีที่เก็บค่าคงที่ให้ตั้งเป็นคลาสสุดท้ายแทนอินเทอร์เฟซ
Zarkonnen

"อินเทอร์เฟซคงที่" ซึ่งไม่ได้ออกแบบมาเพื่อเป็นส่วนหนึ่งของการสืบทอดใด ๆ ไม่เคย ดังนั้นการนำเข้าแบบคงที่จึงมีไว้สำหรับน้ำตาลซินแทติกและการสืบทอดจากอินเทอร์เฟซดังกล่าวเป็นข้อผิดพลาดที่น่ากลัว ฉันรู้ว่าซันทำสิ่งนี้ แต่พวกเขายังทำข้อผิดพลาดพื้นฐานอื่น ๆ อีกมากมายนั่นไม่ใช่ข้ออ้างที่จะเลียนแบบพวกเขา
gizmo

3
ปัญหาอย่างหนึ่งเกี่ยวกับรหัสที่โพสต์สำหรับคำถามคือการใช้งานอินเทอร์เฟซถูกใช้เพื่อให้เข้าถึงค่าคงที่ได้ง่ายขึ้น เมื่อฉันเห็นบางสิ่งบางอย่างใช้ FooInterface ฉันคาดว่าสิ่งนั้นจะส่งผลกระทบต่อการทำงานของมันและข้างต้นละเมิดสิ่งนี้ การนำเข้าแบบคงที่จะช่วยแก้ปัญหานั้นได้
Zarkonnen

2
gizmo - ฉันไม่ใช่แฟนของการนำเข้าแบบคงที่ แต่สิ่งที่เขาทำอยู่ที่นั่นคือการหลีกเลี่ยงการใช้ชื่อคลาสนั่นคือ ConstClass.SOME_CONST การทำการนำเข้าแบบคงที่ไม่ได้เพิ่มสมาชิกเหล่านั้นในคลาสที่คุณ Z ไม่ได้บอกว่าจะสืบทอดจากอินเทอร์เฟซเขาพูดในทางตรงกันข้าม
mtruesdell

8

ฉันไม่ได้แสร้งทำเป็นว่าถูกต้อง แต่ให้ดูตัวอย่างเล็ก ๆ นี้:

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 และ FordCar ไม่รู้เกี่ยวกับ ToyotaCar หลักการ CarConstants ควรเปลี่ยน แต่ ...

ไม่ควรเปลี่ยนค่าคงที่เนื่องจากล้อมีลักษณะกลมและมีลักษณะเป็นกลไก แต่ ... ในอนาคตวิศวกรวิจัยของโตโยต้าได้คิดค้นเครื่องยนต์อิเล็กทรอนิกส์และล้อแบน! มาดูอินเทอร์เฟซใหม่ของเรากัน

public interface InnovativeCarConstants {

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

และตอนนี้เราสามารถเปลี่ยนสิ่งที่เป็นนามธรรมได้:

public interface ToyotaCar extends CarConstants

ถึง

public interface ToyotaCar extends InnovativeCarConstants 

และตอนนี้ถ้าเราจำเป็นต้องเปลี่ยนค่าหลักถ้า ENGINE หรือ WHEEL เราสามารถเปลี่ยน ToyotaCar Interface ในระดับนามธรรมได้อย่าแตะต้องการนำไปใช้

มันไม่ปลอดภัยฉันรู้ แต่ฉันยังอยากรู้ว่าคุณคิดอย่างไรกับเรื่องนี้


ฉันต้องการทราบความคิดของคุณตอนนี้ในปี 2019 สำหรับฉันช่องอินเทอร์เฟซมีไว้เพื่อใช้ร่วมกันระหว่างวัตถุบางอย่าง
ฝนตก

ฉันเขียนคำตอบที่เกี่ยวข้องกับความคิดของคุณ: stackoverflow.com/a/55877115/5290519
ฝนตก

นี่เป็นตัวอย่างที่ดีว่ากฎของ PMD มีข้อ จำกัด มากเพียงใด การพยายามหารหัสที่ดีขึ้นผ่านระบบราชการยังคงเป็นความพยายามที่ไร้ประโยชน์
bebbo

6

มีความเกลียดชังรูปแบบนี้ใน Java มาก อย่างไรก็ตามอินเทอร์เฟซของค่าคงที่ในบางครั้งอาจมีค่า โดยพื้นฐานแล้วคุณต้องปฏิบัติตามเงื่อนไขต่อไปนี้:

  1. แนวคิดนี้เป็นส่วนหนึ่งของอินเทอร์เฟซสาธารณะของคลาสต่างๆ

  2. ค่าของพวกเขาอาจเปลี่ยนแปลงไปในอนาคต

  3. จำเป็นอย่างยิ่งที่การใช้งานทั้งหมดจะใช้ค่าเดียวกัน

ตัวอย่างเช่นสมมติว่าคุณกำลังเขียนส่วนขยายเป็นภาษาแบบสอบถามสมมุติ ในส่วนขยายนี้คุณจะขยายไวยากรณ์ของภาษาด้วยการดำเนินการใหม่บางอย่างซึ่งได้รับการสนับสนุนโดยดัชนี เช่นคุณจะมี R-Tree ที่รองรับการสืบค้นข้อมูลเชิงพื้นที่

ดังนั้นคุณจึงเขียนอินเทอร์เฟซสาธารณะด้วยค่าคงที่:

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

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

ต่อมานักพัฒนาใหม่คิดว่าเขาต้องสร้างดัชนีที่ดีขึ้นเขาจึงมาสร้างการใช้งาน R * ด้วยการใช้อินเทอร์เฟซนี้ในโครงสร้างใหม่ของเขาเขารับประกันว่าดัชนีต่างๆจะมีไวยากรณ์ที่เหมือนกันในภาษาแบบสอบถาม ยิ่งไปกว่านั้นหากคุณตัดสินใจในภายหลังว่า "nearTo" เป็นชื่อที่น่าสับสนคุณสามารถเปลี่ยนเป็น "withinDistanceInKm" และทราบว่าไวยากรณ์ใหม่จะได้รับการเคารพโดยการใช้ดัชนีทั้งหมดของคุณ

PS: แรงบันดาลใจสำหรับตัวอย่างนี้มาจากรหัสเชิงพื้นที่ Neo4j


5

ด้วยข้อได้เปรียบของการมองย้อนกลับเราจะเห็นว่า Java เสียในหลาย ๆ ด้าน ความล้มเหลวที่สำคัญอย่างหนึ่งของ Java คือข้อ จำกัด ของอินเทอร์เฟซสำหรับเมธอดนามธรรมและฟิลด์สุดท้ายแบบคงที่ ภาษา OO ที่ใหม่กว่าและซับซ้อนกว่าเช่นอินเทอร์เฟซย่อยของ Scala ตามลักษณะที่สามารถ (และโดยทั่วไปทำได้) รวมถึงวิธีการที่เป็นรูปธรรมซึ่งอาจมีค่าเป็นศูนย์ (ค่าคงที่!) สำหรับการอธิบายเกี่ยวกับลักษณะเป็นหน่วยของพฤติกรรมที่ประกอบได้โปรดดู http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf สำหรับคำอธิบายสั้น ๆ เกี่ยวกับลักษณะเฉพาะใน Scala เมื่อเปรียบเทียบกับอินเทอร์เฟซใน Java โปรดดูที่http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-5. ในบริบทของการสอนการออกแบบ OO กฎที่เรียบง่ายเช่นการยืนยันว่าอินเทอร์เฟซไม่ควรรวมฟิลด์แบบคงที่เป็นเรื่องงี่เง่า โดยธรรมชาติหลายลักษณะรวมถึงค่าคงที่และค่าคงที่เหล่านี้เป็นส่วนหนึ่งของ "อินเทอร์เฟซ" สาธารณะที่สนับสนุนโดยลักษณะ ในการเขียนโค้ด Java ไม่มีวิธีที่สวยงามและสวยงามในการแสดงลักษณะ แต่การใช้ฟิลด์สุดท้ายแบบคงที่ภายในอินเทอร์เฟซมักเป็นส่วนหนึ่งของวิธีแก้ปัญหาที่ดี


12
อวดรู้มากและล้าสมัยในปัจจุบัน
Esko

1
ข้อมูลเชิงลึกที่ยอดเยี่ยม (+1) แม้ว่าจะมีความสำคัญกับ Java เล็กน้อย
peterh - คืนสถานะ Monica

0

ตามข้อกำหนดของ JVM ฟิลด์และวิธีการในอินเทอร์เฟซสามารถมีได้เฉพาะสาธารณะแบบคงที่ขั้นสุดท้ายและบทคัดย่อเท่านั้น อ้างอิงจาก Inside Java VM

ตามค่าเริ่มต้นวิธีการทั้งหมดในอินเทอร์เฟซเป็นนามธรรมแม้ว่าคุณจะไม่ได้กล่าวถึงอย่างชัดเจนก็ตาม

อินเทอร์เฟซมีขึ้นเพื่อให้ข้อมูลจำเพาะเท่านั้น ไม่สามารถมีการใช้งานใด ๆ ดังนั้นเพื่อหลีกเลี่ยงการใช้คลาสเพื่อเปลี่ยนข้อกำหนดจึงถือว่าเป็นขั้นสุดท้าย เนื่องจากไม่สามารถสร้างอินสแตนซ์อินเทอร์เฟซได้จึงถูกทำให้เป็นแบบคงที่เพื่อเข้าถึงฟิลด์โดยใช้ชื่ออินเตอร์เฟส


0

ฉันไม่มีชื่อเสียงมากพอที่จะแสดงความคิดเห็นกับ Pleerock ดังนั้นฉันจึงต้องสร้างคำตอบ ฉันขอโทษสำหรับเรื่องนี้ แต่เขาพยายามอย่างเต็มที่และฉันอยากจะตอบเขา

Pleerock คุณสร้างตัวอย่างที่สมบูรณ์แบบเพื่อแสดงว่าเหตุใดค่าคงที่เหล่านั้นจึงควรเป็นอิสระจากอินเทอร์เฟซและเป็นอิสระจากการสืบทอด สำหรับลูกค้าของแอปพลิเคชันนั้นไม่สำคัญว่าจะมีความแตกต่างทางเทคนิคระหว่างการใช้งานรถยนต์ พวกเขาเหมือนกันสำหรับลูกค้าเพียงแค่รถยนต์ ดังนั้นลูกค้าจึงต้องการมองจากมุมมองนั้นซึ่งเป็นอินเทอร์เฟซเช่น I_Somecar ตลอดทั้งแอปพลิเคชันลูกค้าจะใช้มุมมองเดียวและไม่แตกต่างกันสำหรับรถยนต์แต่ละยี่ห้อ

หากลูกค้าต้องการเปรียบเทียบรถก่อนซื้อสามารถมีวิธีดังนี้

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

อินเทอร์เฟซคือสัญญาเกี่ยวกับพฤติกรรมและแสดงวัตถุที่แตกต่างกันจากมุมมองเดียว ในแบบที่คุณออกแบบรถยนต์ทุกยี่ห้อจะมีมรดกตกทอดของตัวเองหรือไม่ แม้ว่าในความเป็นจริงแล้วจะค่อนข้างถูกต้องเนื่องจากรถยนต์อาจมีความแตกต่างกันซึ่งอาจเหมือนกับการเปรียบเทียบวัตถุประเภทต่างๆโดยสิ้นเชิงในที่สุดก็มีทางเลือกระหว่างรถยนต์ที่แตกต่างกัน และนั่นคือมุมมองของอินเทอร์เฟซที่ทุกแบรนด์ต้องแชร์ การเลือกค่าคงที่ไม่ควรทำให้เป็นไปไม่ได้ โปรดพิจารณาคำตอบของ Zarkonnen


-1

สิ่งนี้มาจากช่วงเวลาก่อนที่ Java 1.5 จะมีอยู่และนำ enums มาให้เรา ก่อนหน้านั้นไม่มีวิธีที่ดีในการกำหนดชุดของค่าคงที่หรือค่าที่ จำกัด

สิ่งนี้ยังคงใช้อยู่โดยส่วนใหญ่แล้วสำหรับความเข้ากันได้แบบย้อนหลังหรือเนื่องจากจำนวนการปรับโครงสร้างใหม่ที่จำเป็นในการกำจัดออกไปในหลาย ๆ โครงการ


2
ก่อนหน้า Java 5 คุณสามารถใช้รูปแบบ enum ประเภทปลอดภัย (ดูjava.sun.com/developer/Books/shiftintojava/page1.html )
Dan Dyer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.