ทำไมจึงชอบคลาสภายในที่ไม่คงที่มากกว่าสแตติก


37

คำถามนี้เกี่ยวกับว่าจะสร้างคลาสที่ซ้อนใน Java เป็นคลาสที่ซ้อนกันหรือคลาสซ้อนภายใน ฉันค้นหาแถว ๆ นี้และใน Stack Overflow แต่ไม่พบคำถามใด ๆ เกี่ยวกับความหมายของการออกแบบของการตัดสินใจนี้

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

นี่คือความเข้าใจของฉันเกี่ยวกับผลของการใช้คลาสที่ซ้อนกันคงที่:

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

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

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

หรืออีกนัยหนึ่ง: มีเหตุผลที่น่าเชื่อถือไหมว่าทำไมคนเราถึงชอบชนชั้นในที่ซ้อนกัน?


2
จากบทช่วยสอน Java : "ใช้คลาสที่ไม่คงที่แบบคงที่ (หรือคลาสภายใน) หากคุณต้องการเข้าถึงฟิลด์และวิธีการที่ไม่ใช่แบบสาธารณะของอินสแตนซ์ที่ปิดล้อมใช้คลาสที่ซ้อนแบบคงที่หากคุณไม่ต้องการการเข้าถึงนี้"
ริ้น

5
ขอบคุณสำหรับใบเสนอราคากวดวิชา แต่นี่เป็นการทำซ้ำสิ่งที่แตกต่างไม่ใช่เหตุผลที่คุณต้องการเข้าถึงฟิลด์อินสแตนซ์
แฟรงค์

มันเป็นคำแนะนำทั่วไปที่บอกเป็นนัยถึงสิ่งที่ต้องการ ฉันได้เพิ่มคำอธิบายโดยละเอียดเพิ่มเติมเกี่ยวกับวิธีตีความตีความในคำตอบนี้
gnat

1
ถ้าชั้นในไม่แน่นควบคู่ไปกับคลาสที่ปิดล้อมมันอาจไม่ควรเป็นชั้นในเลย
Kevin Krumwiede

คำตอบ:


23

Joshua Bloch ในข้อ 22 ของหนังสือของเขา "Effective Java Second Edition" บอกเวลาที่จะใช้คลาสที่ซ้อนกันชนิดใดและทำไม มีคำพูดบางส่วนด้านล่าง:

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

การใช้งานทั่วไปของคลาสสมาชิก nonstatic คือการกำหนดอะแด็ปเตอร์ที่อนุญาตให้อินสแตนซ์ของคลาสภายนอกถูกดูเป็นอินสแตนซ์ของคลาสที่ไม่เกี่ยวข้องบางตัว ยกตัวอย่างเช่นการใช้งานของMapอินเตอร์เฟซที่มักจะใช้เรียนสมาชิก nonstatic ในการดำเนินการของพวกเขามองเห็นวิวคอลเลกชันซึ่งจะถูกส่งกลับโดยMap's keySet, entrySetและvaluesวิธีการ ในทำนองเดียวกันการใช้งานของอินเทอร์เฟซการเก็บรวบรวมเช่นSetและListโดยทั่วไปจะใช้คลาสสมาชิก nonstatic เพื่อใช้ตัววนซ้ำ:

// 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> {
        ...
    }
}

หากคุณกำหนดระดับของสมาชิกที่ไม่จำเป็นต้องมีการเข้าถึงเช่นการปิดล้อม, เสมอใส่staticปรับปรุงในการประกาศของมันทำให้มันเป็นแบบคงที่มากกว่าระดับสมาชิก nonstatic


1
ฉันไม่แน่ใจว่าอะแดปเตอร์นี้เปลี่ยนมุมมองของคลาสภายนอกMySetหรือไม่ ฉันสามารถจินตนาการบางอย่างเช่นประเภทที่ขึ้นอยู่กับพา ธ ที่แตกต่างกันเช่นmyOneSet.iterator.getClass() != myOtherSet.iterator.getClass()แต่แล้วใน Java อีกครั้งมันเป็นไปไม่ได้จริง ๆ เพราะคลาสจะเหมือนกันสำหรับแต่ละคน
Frank

@ Frank มันเป็นคลาสอะแดปเตอร์ทั่วไปที่สวยงาม - มันช่วยให้คุณดูชุดเป็นสตรีมขององค์ประกอบ (Iterator)
user253751

@immibis ขอบคุณฉันตระหนักดีถึงสิ่งที่ iterator / อะแดปเตอร์ทำ แต่คำถามนี้เกี่ยวกับวิธีการใช้งาน
แฟรงค์

@ Frank ฉันบอกว่ามันเปลี่ยนมุมมองของอินสแตนซ์ด้านนอก นั่นคือสิ่งที่อะแดปเตอร์ทำ - มันเปลี่ยนวิธีการดูวัตถุบางอย่าง ในกรณีนี้วัตถุนั้นเป็นอินสแตนซ์ด้านนอก
user253751

10

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

การออกแบบที่เกี่ยวข้องกับการตัดสินใจที่จะทำให้ชั้นภายในคงที่อยู่ในJava Puzzlers , Puzzle 90 (ตัวอักษรตัวหนาด้านล่างอ้างเป็นของฉัน):

เมื่อใดก็ตามที่คุณเขียนคลาสสมาชิกให้ถามตัวเองว่าคลาสนี้จำเป็นต้องมีอินสแตนซ์ล้อมรอบหรือไม่? staticถ้าคำตอบคือไม่ให้มัน บางครั้งชั้นในมีประโยชน์ แต่ก็สามารถแนะนำภาวะแทรกซ้อนที่ทำให้โปรแกรมยากต่อการเข้าใจ พวกเขามีปฏิสัมพันธ์ที่ซับซ้อนกับยาชื่อสามัญ (ปริศนาที่ 89) สะท้อน (ปริศนาที่ 80) และมรดก (ปริศนานี้) ถ้าคุณบอกว่าInner1เป็นstaticปัญหาจะหายไป หากคุณยังประกาศInner2จะเป็นstaticคุณจริงสามารถเข้าใจสิ่งที่โปรแกรมไม่: โบนัสที่ดีแน่นอน

โดยสรุปแล้วมันไม่ค่อยเหมาะสำหรับชั้นหนึ่งที่จะเป็นทั้งชั้นในและชั้นย่อยของอีกชั้นหนึ่ง โดยทั่วไปแล้วมันไม่ค่อยเหมาะสมที่จะขยายชั้นใน; ถ้าคุณต้องคิดให้ยาวและหนักเกี่ยวกับอินสแตนซ์ที่แนบมา นอกจากนี้ยังชอบเรียนซ้อนไม่ใช่static ส่วนใหญ่เรียนสมาชิกสามารถและควรจะได้รับการประกาศstaticstatic

หากคุณสนใจการวิเคราะห์รายละเอียดเพิ่มเติมของจิ๊กซอว์ 90ที่ระบุไว้ในคำตอบนี้ที่กองมากเกิน


มันเป็นที่น่าสังเกตว่าข้างต้นเป็นหลักรุ่นที่ขยายของคำแนะนำที่ให้ไว้ในJava คลาสและวัตถุกวดวิชา :

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

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

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


โดยรวม, ฉัน (เจ็บปวดมาก) การเผชิญหน้ากับการแก้จุดบกพร่องการเรียนภายในโดยตรงเข้าถึงด้านของการปิดล้อมเช่นสร้างความประทับใจที่แข็งแกร่งที่นี้คล้ายกับการปฏิบัติใช้ของรัฐทั่วโลกพร้อมกับความชั่วร้ายที่รู้จักกันที่เกี่ยวข้องกับมัน

แน่นอน Java สร้างขึ้นเพื่อให้ความเสียหายของ "quasi global" นั้นอยู่ในชั้นเรียนที่ล้อมรอบ แต่เมื่อฉันต้อง debug ชั้นในโดยเฉพาะมันรู้สึกเหมือนตัวช่วยวงไม่ได้ช่วยลดความเจ็บปวด: ฉันยังคงมี เพื่อระลึกไว้ในความหมาย "ต่างประเทศ" และรายละเอียดแทนที่จะมุ่งเน้นการวิเคราะห์วัตถุที่มีปัญหาโดยเฉพาะ


เพื่อความสมบูรณ์อาจมีบางกรณีที่ไม่มีการใช้เหตุผลด้านบน ตัวอย่างเช่นต่อการอ่านmap.keySet javadocsของฉันคุณลักษณะนี้จะแนะนำการมีเพศสัมพันธ์อย่างแน่นหนาและเป็นผลให้อาร์กิวเมนต์ขัดแย้งกับคลาสที่ไม่คงที่:

ส่งคืนSetมุมมองของคีย์ที่มีอยู่ในแผนที่นี้ ชุดนี้ได้รับการสนับสนุนโดยแผนที่ดังนั้นการเปลี่ยนแปลงของแผนที่จะปรากฎในชุดและในทางกลับกัน ...

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


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

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

นั่นคือประเด็น ถ้าดีกว่าเสมอทำไมเราต้องมายุ่ง
Frank

@Frank อีกคำตอบให้ตัวอย่างที่น่าสนใจซึ่งไม่ได้เป็นเช่นนั้นเสมอไป ตามการอ่านของฉันmap.keySet javadocsคุณลักษณะนี้จะแนะนำการมีเพศสัมพันธ์อย่างแน่นหนาและเป็นผลให้อาร์กิวเมนต์ขัดแย้งกับคลาสที่ไม่คงที่ "ชุดได้รับการสนับสนุนโดยแผนที่ดังนั้นการเปลี่ยนแปลงของแผนที่จะปรากฏในชุดและในทางกลับกัน ... "
ริ้น

1

ความเข้าใจผิดบางประการ:

การมีเพศสัมพันธ์น้อยลง: โดยทั่วไปเราจะมีเพศสัมพันธ์น้อยลงเนื่องจากคลาส [static Inner] ไม่สามารถเข้าถึงคุณลักษณะของคลาสภายนอกได้โดยตรง

IIRC - จริงๆแล้วมันสามารถ (แน่นอนถ้าพวกเขาเป็นเขตข้อมูลวัตถุก็จะไม่มีวัตถุด้านนอกที่จะดูอย่างไรก็ตามถ้ามันได้รับวัตถุดังกล่าวจากที่ใดก็ได้จากนั้นก็สามารถเข้าถึงเขตข้อมูลเหล่านั้นได้โดยตรง)

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

ฉันไม่แน่ใจว่าคุณหมายถึงอะไรที่นี่ ตัวโหลดคลาสเกี่ยวข้องทั้งหมดสองครั้งเท่านั้นหนึ่งครั้งสำหรับแต่ละคลาส (เมื่อโหลดคลาส)


การท่องเที่ยวดังนี้

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

เมื่อเทียบกับภาษาอื่น - เช่น Scala หรือ C ++: ไม่มีละครที่สร้างผู้ถือขนาดเล็ก (class, struct หรืออะไรก็ตาม) ได้ทุกเวลาที่มีข้อมูลสองบิตอยู่ด้วยกัน กฎการเข้าถึงที่ยืดหยุ่นช่วยให้คุณลดการสร้างสำเร็จรูปทำให้คุณสามารถเก็บรหัสที่เกี่ยวข้องไว้ใกล้ตัว สิ่งนี้จะลด 'ระยะห่างจากสมอง' ระหว่างสองคลาส

เราสามารถส่งความกังวลเกี่ยวกับการออกแบบไปยังการเข้าถึงโดยตรงได้โดยรักษาความเป็นส่วนตัวของคนชั้นในเอาไว้

(... หากคุณถามว่า 'เพราะเหตุใดประชาชนในชั้นเรียนแบบไม่คงที่?' เป็นคำถามที่ดีมาก - ฉันไม่คิดว่าฉันยังเคยพบกรณีที่ถูกต้องสำหรับพวกเขา)

คุณสามารถใช้งานได้ทุกเมื่อที่มีการอ่านโค้ดและหลีกเลี่ยงการละเมิด DRY และแผ่นสร้าง ฉันไม่มีตัวอย่างที่พร้อมเพราะมันไม่ได้เกิดขึ้นบ่อยครั้งในประสบการณ์ของฉัน แต่มันเกิดขึ้น


คลาสภายในแบบคงที่ยังคงเป็นวิธีการเริ่มต้นที่ดีbtw ไม่คงที่มีเขตข้อมูลที่ซ่อนอยู่พิเศษที่สร้างขึ้นซึ่งระดับชั้นในหมายถึงด้านนอก


0

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

พิจารณา:

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);
    }
}

ใช้เป็น:

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

q.performQuery();

สิ่งเดียวกันนี้สามารถเกิดขึ้นได้กับคลาสภายในที่ไม่คงที่:

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);
        }
    }
}

ใช้เป็น:

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

q.performQuery();

โรงงานได้รับประโยชน์จากการมีวิธีการตั้งชื่อเฉพาะเช่นcreate, หรือcreateFromSQL createFromTemplateสิ่งเดียวกันสามารถทำได้ที่นี่เช่นกันในรูปแบบของคลาสภายในหลายชั้น:

public class QueryFactory {

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

}

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

เปรียบเทียบ:

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

VS

Query q = queryFactory.createNative("select * from employees");
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.