Java: การมองเห็น Subpackage หรือไม่


150

ฉันมีสองแพคเกจในโครงการของฉัน: และodp.proj odp.proj.testมีวิธีการบางอย่างที่ฉันต้องการให้ปรากฏเฉพาะกับคลาสในแพ็กเกจทั้งสองนี้ ฉันจะทำสิ่งนี้ได้อย่างไร

แก้ไข:หากไม่มีแนวคิดของแพ็กเกจย่อยใน Java มีวิธีแก้ไขหรือไม่? ฉันมีวิธีการบางอย่างที่ฉันต้องการให้ผู้ทดสอบและสมาชิกคนอื่น ๆ ของแพ็คเกจใช้งานได้เท่านั้น ฉันควรจะทิ้งทุกอย่างลงในแพ็คเกจเดียวกันหรือไม่? ใช้การสะท้อนที่ครอบคลุมหรือไม่




2
นอกเหนือจากการทดสอบควรทดสอบพฤติกรรมของวัตถุของคุณเท่าที่สังเกตได้จากนอกแพ็คเกจ การเข้าถึงวิธีการ / คลาสแพ็คเกจจากการทดสอบของคุณบอกฉันว่าการทดสอบนั้นอาจเป็นการทดสอบการใช้งานไม่ใช่พฤติกรรม การใช้เครื่องมือสร้างเช่น maven หรือ gradle พวกเขาจะทำให้ง่ายสำหรับการทดสอบของคุณทำงานใน classpath เดียวกัน แต่ไม่รวมอยู่ในขวดสุดท้าย (เป็นสิ่งที่ดี) จึงไม่จำเป็นต้องให้พวกเขามีชื่อแพคเกจที่แตกต่างกัน แต่การวางไว้ในแพ็คเกจที่แยกต่างหากยังไงก็ตามก็คือการบังคับใช้ว่าคุณไม่ได้เข้าถึงขอบเขตส่วนตัว / เริ่มต้นและทดสอบเฉพาะ API สาธารณะ
derekv

3
สิ่งนี้อาจเป็นจริงหากคุณกำลังทำงานในลักษณะที่ขับเคลื่อนด้วยพฤติกรรมล้วนๆและต้องการให้การทดสอบของคุณทำการทดสอบกล่องดำเท่านั้น แต่อาจมีบางกรณีที่การใช้งานพฤติกรรมที่ต้องการนั้นจำเป็นต้องมีความซับซ้อนสูงอย่างหลีกเลี่ยงไม่ได้ ในกรณีนี้มันเป็นการดีที่จะแยกการใช้งานออกเป็นชิ้นเล็ก ๆ ที่ง่ายกว่า (ยังเป็นส่วนตัวกับการนำไปใช้) และเขียนการทดสอบหน่วยบางอย่างเพื่อทำการทดสอบกล่องสีขาวบนเส้นทางที่แตกต่างกันผ่านชิ้นส่วนเหล่านี้
James Woods

คำตอบ:


165

คุณทำไม่ได้ ใน Java ไม่มีแนวคิดของแพ็กเกจย่อยดังนั้นodp.projและodp.proj.testเป็นแพ็กเกจแยกกันโดยสิ้นเชิง


10
แม้ว่าฉันจะชอบวิธีนี้ แต่ก็ทำให้เกิดความสับสนว่า IDEs ส่วนใหญ่ใส่แพคเกจที่มีชื่อเดียวกันเข้าด้วยกัน ขอบคุณสำหรับการชี้แจง
JacksOnF1re

สิ่งนี้ไม่ถูกต้องอย่างสมบูรณ์: JLSจะกำหนดแพ็กเกจย่อยแม้ว่าภาษาที่สำคัญเท่านั้นที่พวกเขาต้องห้าม "กับแพคเกจที่มีแพ็กเกจย่อยที่มีชื่อแบบง่ายเหมือนกับประเภทระดับบนสุด" ฉันเพิ่งเพิ่มคำตอบสำหรับคำถามนี้เพื่ออธิบายรายละเอียด
M. Justin

59

ชื่อของแพ็คเกจของคุณบอกเป็นนัยว่าแอปพลิเคชันที่นี่มีไว้สำหรับการทดสอบหน่วย รูปแบบทั่วไปที่ใช้คือการใส่คลาสที่คุณต้องการทดสอบและรหัสทดสอบหน่วยในแพ็คเกจเดียวกัน (ในกรณีของคุณodp.proj) แต่ในต้นไม้ต้นกำเนิดที่แตกต่างกัน ดังนั้นคุณจะใส่ในชั้นเรียนของคุณและรหัสการทดสอบในsrc/odp/projtest/odp/proj

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


11

นี่ไม่ใช่ความสัมพันธ์พิเศษระหว่างodp.projและodp.proj.test- พวกเขาเพิ่งถูกตั้งชื่อว่าเกี่ยวข้องอย่างชัดเจน

หากแพ็คเกจ odp.proj.test ให้การทดสอบอย่างง่ายคุณสามารถใช้ชื่อแพ็คเกจเดียวกันได้ ( odp.proj) IDEs เช่น Eclipse และ Netbeans จะสร้างโฟลเดอร์แยกต่างหาก ( src/main/java/odp/projและsrc/test/java/odp/proj) ด้วยชื่อแพ็กเกจเดียวกัน แต่มีความหมาย JUnit

โปรดทราบว่า IDE เหล่านี้จะสร้างการทดสอบสำหรับวิธีการodp.projและสร้างโฟลเดอร์ที่เหมาะสมสำหรับวิธีการทดสอบที่ไม่มีอยู่


5

เมื่อฉันทำสิ่งนี้ใน IntelliJ ต้นไม้ต้นกำเนิดของฉันจะเป็นดังนี้:

src         // source root
- odp
   - proj   // .java source here
- test      // test root
  - odp
     - proj // JUnit or TestNG source here

4

แก้ไข: หากไม่มีแนวคิดของแพ็กเกจย่อยใน Java มีวิธีแก้ไขหรือไม่? ฉันมีวิธีการบางอย่างที่ฉันต้องการให้มีเฉพาะผู้ทดสอบและสมาชิกคนอื่น ๆ ของแพ็คเกจ

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

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


3

อย่างที่คนอื่น ๆ อธิบายไว้ไม่มีสิ่งใดที่เป็น "subpackage" ใน Java: แพคเกจทั้งหมดจะถูกแยกและไม่ได้รับมรดกใด ๆ จากผู้ปกครอง

วิธีที่ง่ายในการเข้าถึงสมาชิกคลาสที่ได้รับการป้องกันจากแพ็คเกจอื่นคือการขยายชั้นเรียนและแทนที่สมาชิก

ตัวอย่างเช่นการเข้าถึงClassInAในแพ็คเกจa.b:

package a;

public class ClassInA{
    private final String data;

    public ClassInA(String data){ this.data = data; }

    public String getData(){ return data; }

    protected byte[] getDataAsBytes(){ return data.getBytes(); }

    protected char[] getDataAsChars(){ return data.toCharArray(); }
}

สร้างคลาสในแพ็คเกจที่แทนที่วิธีที่คุณต้องการในClassInA:

package a.b;

import a.ClassInA;

public class ClassInAInB extends ClassInA{
    ClassInAInB(String data){ super(data); }

    @Override
    protected byte[] getDataAsBytes(){ return super.getDataAsBytes(); }
}

ซึ่งช่วยให้คุณใช้คลาสแทนที่ในคลาสอื่นในแพ็คเกจ:

package a.b;

import java.util.Arrays;

import a.ClassInA;

public class Driver{
    public static void main(String[] args){
        ClassInA classInA = new ClassInA("string");
        System.out.println(classInA.getData());
        // Will fail: getDataAsBytes() has protected access in a.ClassInA
        System.out.println(Arrays.toString(classInA.getDataAsBytes()));

        ClassInAInB classInAInB = new ClassInAInB("string");
        System.out.println(classInAInB.getData());
        // Works: getDataAsBytes() is now accessible
        System.out.println(Arrays.toString(classInAInB.getDataAsBytes()));
    }
}

โปรดทราบว่าจะใช้งานได้เฉพาะสมาชิกที่ได้รับความคุ้มครองซึ่งสามารถมองเห็นการขยายคลาส (การสืบทอด) และไม่ใช่สมาชิกแพ็กเกจส่วนตัวซึ่งสามารถมองเห็นได้เฉพาะคลาสย่อย / การขยายภายในแพ็คเกจเดียวกัน หวังว่านี่จะช่วยให้ใครบางคน!


3

คำตอบส่วนใหญ่ที่นี่ระบุว่าไม่มีสิ่งใดในแพคเกจย่อยใน Java แต่ไม่ถูกต้องอย่างเคร่งครัด คำนี้อยู่ในข้อกำหนดภาษาจาวากลับไปเป็นจาวา 6 และอาจจะถอยกลับไปอีก ภาษารอบแพ็กเกจย่อยไม่ได้เปลี่ยนแปลงมากนักใน JLS ตั้งแต่ Java 6

Java 13 JLS :

สมาชิกของแพ็กเกจเป็นแพ็กเกจย่อยและชนิดคลาสระดับบนทั้งหมดและชนิดอินเตอร์เฟสระดับบนสุดที่ประกาศในหน่วยการรวบรวมทั้งหมดของแพ็กเกจ

ตัวอย่างเช่นใน Java SE Platform API:

  • แพคเกจที่javaมีแพ็กเกจย่อยawt, applet, io, lang, netและutilแต่ไม่มีหน่วยรวบรวม
  • แพ็กเกจjava.awtมีชื่อแพ็กเกจย่อยimageรวมถึงจำนวนหน่วยการคอมไพล์ที่มีการประกาศคลาสและชนิดอินเตอร์เฟส

แนวคิดของแพ็กเกจย่อยนั้นมีความเกี่ยวข้องเช่นเดียวกับบังคับใช้ข้อ จำกัด ในการตั้งชื่อระหว่างแพ็กเกจและคลาส / อินเตอร์เฟส:

แพ็คเกจอาจไม่มีสมาชิกสองคนที่มีชื่อเดียวกันหรือผลลัพธ์ข้อผิดพลาดในการคอมไพล์

นี่คือตัวอย่างบางส่วน:

  • เพราะแพคเกจjava.awtมีแพ็กเกจย่อยimageจึงไม่สามารถ (และไม่ได้) imageมีการประกาศของชั้นเรียนหรืออินเตอร์เฟซชนิดชื่อ
  • หากมีแพคเกจที่มีชื่อmouseและประเภทสมาชิกButtonในแพคเกจที่ (ซึ่งก็อาจจะเรียกว่าmouse.Button) แล้วมีไม่สามารถเป็นแพคเกจใด ๆ ที่มีชื่อที่มีคุณสมบัติครบถ้วนหรือmouse.Buttonmouse.Button.Click
  • ถ้าcom.nighthacks.java.jagเป็นชื่อที่มีคุณสมบัติครบถ้วนประเภทแล้วมีไม่สามารถเป็นแพคเกจใด ๆ ที่มีคุณสมบัติครบถ้วนชื่อเป็นอย่างใดอย่างหนึ่งหรือcom.nighthacks.java.jagcom.nighthacks.java.jag.scrabble

อย่างไรก็ตามการ จำกัด การตั้งชื่อนี้มีความสำคัญเพียงอย่างเดียวในการใช้แพ็คเกจย่อยโดยภาษา:

โครงสร้างการตั้งชื่อแบบลำดับชั้นสำหรับแพ็คเกจมีไว้เพื่อความสะดวกในการจัดระเบียบแพ็คเกจที่เกี่ยวข้องในลักษณะทั่วไป แต่ไม่มีความสำคัญในตัวเองนอกเหนือจากข้อห้ามกับแพคเกจที่มีแพ็กเกจย่อยที่มีชื่อง่ายเหมือนกับชื่อระดับบนสุดที่ประกาศในแพ็คเกจนั้น .

ยกตัวอย่างเช่นไม่มีความสัมพันธ์ระหว่างการเข้าถึงพิเศษแพคเกจที่มีชื่อoliverและแพคเกจอีกคนหนึ่งชื่อoliver.twistหรือระหว่างแพคเกจชื่อและevelyn.wood evelyn.waughนั่นคือรหัสในแพคเกจที่oliver.twistมีชื่อไม่มีการเข้าถึงชนิดที่ประกาศในแพคเกจoliverดีกว่ารหัสในแพคเกจอื่น ๆ


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

ทั้งวิธีการที่สามารถทำให้เป็นสาธารณะและแพคเกจทั้งหมด (รวมถึงodp.projและodp.proj.test) จะสามารถเข้าถึงวิธีการที่กำหนดหรือวิธีการที่จะทำให้แพคเกจส่วนตัว (การมองเห็นเริ่มต้น) และรหัสทั้งหมดที่จำเป็นในการเข้าถึงโดยตรงจะต้องใส่ แพคเกจ (sub) เดียวกันกับวิธีการ

ที่กล่าวว่าการปฏิบัติมาตรฐานมากใน Java คือการวางรหัสทดสอบในแพคเกจเดียวกันกับซอร์สโค้ด แต่ในตำแหน่งอื่นในระบบไฟล์ ตัวอย่างเช่นในเครื่องมือสร้างMavenการประชุมจะต้องใส่ซอร์สและไฟล์ทดสอบเหล่านี้ในsrc/main/java/odp/projและ src/test/java/odp/projตามลำดับ เมื่อเครื่องมือบิลด์รวบรวมสิ่งนี้ชุดของไฟล์ทั้งสองจะอยู่ในodp.projแพ็คเกจ แต่มีเพียงsrcไฟล์เท่านั้นที่รวมอยู่ในส่วนการผลิต ไฟล์ทดสอบจะใช้ในเวลาบิลด์เพื่อตรวจสอบไฟล์ที่ใช้งานจริงเท่านั้น ด้วยการตั้งค่านี้รหัสทดสอบสามารถเข้าถึงแพคเกจส่วนตัวหรือรหัสป้องกันของรหัสที่กำลังทดสอบได้อย่างอิสระเนื่องจากจะอยู่ในแพ็คเกจเดียวกัน

ในกรณีที่คุณต้องการใช้รหัสร่วมกันในแพ็คเกจย่อยหรือแพ็คเกจพี่น้องที่ไม่ใช่กรณีทดสอบ / การผลิตโซลูชันหนึ่งที่ฉันเห็นห้องสมุดบางแห่งใช้คือการใส่รหัสที่ใช้ร่วมกันเป็นสาธารณะ แต่เอกสารที่มีไว้สำหรับไลบรารีภายใน ใช้เฉพาะ.


0

โดยไม่ต้องใส่ตัวดัดแปลงการเข้าถึงด้านหน้าของวิธีการที่คุณบอกว่ามันเป็นแพคเกจส่วนตัว
ดูตัวอย่างต่อไปนี้

package odp.proj;
public class A
{
    void launchA() { }
}

package odp.proj.test;
public class B
{
    void launchB() { }
}

public class Test
{
    public void test()
    {
        A a = new A();
        a.launchA()    // cannot call launchA because it is not visible
    }
}

0

ด้วยคลาส PackageVisibleHelper และเก็บไว้เป็นส่วนตัวก่อน PackageVisibleHelperFactory ถูกแช่แข็งเราสามารถเรียกใช้เมธอด launchA (โดย PackageVisibleHelper) ได้ทุกที่ :)

package odp.proj;
public class A
 {
    void launchA() { }
}

public class PackageVisibleHelper {

    private final PackageVisibleHelperFactory factory;

    public PackageVisibleHelper(PackageVisibleHelperFactory factory) {
        super();
        this.factory = factory;
    }

    public void launchA(A a) {
        if (factory == PackageVisibleHelperFactory.INSTNACNE && !factory.isSampleHelper(this)) {
            throw new IllegalAccessError("wrong PackageVisibleHelper ");
        }
        a.launchA();
    }
}


public class PackageVisibleHelperFactory {

    public static final PackageVisibleHelperFactory INSTNACNE = new PackageVisibleHelperFactory();

    private static final PackageVisibleHelper HELPER = new PackageVisibleHelper(INSTNACNE);

    private PackageVisibleHelperFactory() {
        super();
    }

    private boolean frozened;

    public PackageVisibleHelper getHelperBeforeFrozen() {
        if (frozened) {
            throw new IllegalAccessError("please invoke before frozen!");
        }
        return HELPER;
    }

    public void frozen() {
        frozened = true;
    }

    public boolean isSampleHelper(PackageVisibleHelper helper) {
        return HELPER.equals(helper);
    }
}
package odp.proj.test;

import odp.proj.A;
import odp.proj.PackageVisibleHelper;
import odp.proj.PackageVisibleHelperFactory;

public class Test {

    public static void main(String[] args) {

        final PackageVisibleHelper helper = PackageVisibleHelperFactory.INSTNACNE.getHelperBeforeFrozen();
        PackageVisibleHelperFactory.INSTNACNE.frozen();


        A a = new A();
        helper.launchA(a);

        // illegal access       
        new PackageVisibleHelper(PackageVisibleHelperFactory.INSTNACNE).launchA(a); 
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.