- 1. เป้าหมายการเรียนรู้
- 2. ปัญหาที่ Composite Pattern ช่วยแก้
- 3. องค์ประกอบของแพตเทิร์น
- 4. สถานการณ์ตัวอย่าง: โครงการที่มีไฟล์และโฟลเดอร์
- 5. โค้ด Java ฉบับสมบูรณ์
- 6. อธิบายการทำงานของโค้ด
- 7. การคำนวณแบบ Recursive เกิดขึ้นอย่างไร
- 8. เหตุใดต้องป้องกันวงจรและโหนดที่มีแม่หลายตัว
- 9. ควรวาง add() และ remove() ไว้ที่ไหน
- 10. เวิร์กช็อป: ขยายตัวอย่างทีละขั้น
- 11. ข้อดีและข้อจำกัด
- 12. เปรียบเทียบกับแพตเทิร์นที่ใกล้เคียง
- 13. แนวคิดลักษณะเดียวกันใน Java API
- 14. คำถามทบทวน
- 15. แหล่งอ้างอิงและอ่านเพิ่มเติม
ออกแบบโครงสร้างแบบต้นไม้ เพื่อเรียกใช้งานวัตถุชิ้นเดียวและกลุ่มวัตถุผ่านชนิดข้อมูลร่วมกัน

Composite Pattern เป็น Structural Design Pattern ที่ใช้จัดวัตถุเป็นโครงสร้างแบบต้นไม้ (Tree) เพื่อแทนความสัมพันธ์ระหว่างส่วนย่อยกับส่วนรวม (Part–Whole) โดยให้ทั้งวัตถุเดี่ยวและวัตถุที่รวมสมาชิกหลายชิ้นใช้สัญญาการทำงานร่วมกัน
ตัวอย่างที่เข้าใจได้ง่ายคือระบบไฟล์: ไฟล์ มีขนาดของตัวเอง ส่วน โฟลเดอร์ มีไฟล์หรือโฟลเดอร์ย่อย และคำนวณขนาดจากสมาชิกทั้งหมด ผู้เรียกสามารถใช้ sizeInBytes() ได้เหมือนกัน
#1. เป้าหมายการเรียนรู้
เมื่อจบบทความ ผู้อ่านจะสามารถ:
- อธิบายบทบาท Component, Leaf, Composite และ Client
- ออกแบบโครงสร้างที่ซ้อนกันได้หลายระดับด้วย Java
- ใช้ Polymorphism ร่วมกับ Recursion โดยไม่ตรวจชนิดวัตถุใน Client
- เลือกตำแหน่งของเมธอดจัดการสมาชิกให้เหมาะกับงาน
- ป้องกันวงจรและการใช้โหนดร่วมกันเมื่อโดเมนต้องเป็นต้นไม้
#2. ปัญหาที่ Composite Pattern ช่วยแก้
สมมติว่าโปรแกรมมีทั้งไฟล์และโฟลเดอร์ หากไม่มีชนิดข้อมูลร่วมกัน ผู้พัฒนาอาจแยกรายการไฟล์กับรายการโฟลเดอร์ หรือเขียนเงื่อนไขตรวจชนิดซ้ำในหลายจุด เช่น การหาขนาด การแสดงรายการ และการส่งออกรายงาน
เมื่อเพิ่มโฟลเดอร์ซ้อนหลายระดับ โปรแกรมยังต้องเดินสมาชิกต่อไปเรื่อย ๆ หาก Client รับผิดชอบรายละเอียดเหล่านี้เอง ความรู้เกี่ยวกับโครงสร้างจะกระจายไปหลายส่วน
Composite Pattern ย้ายความรับผิดชอบของการจัดการกลุ่มไปไว้ที่ Composite และให้ Client ทำงานผ่าน Component
| สิ่งที่ต้องการ | แนวทางในตัวอย่าง |
|---|---|
| เรียกขนาดของไฟล์และโฟลเดอร์ด้วยเมธอดเดียวกัน | ประกาศ sizeInBytes() ใน FsNode |
| โฟลเดอร์เก็บได้ทั้งไฟล์และโฟลเดอร์ | ใช้ List<FsNode> |
| รองรับโฟลเดอร์หลายระดับ | ให้โฟลเดอร์เรียกการทำงานของลูกแบบ Recursive |
| Client ไม่ต้องรู้ชนิดจริง | รับพารามิเตอร์เป็น FsNode |
จุดสำคัญ: การมี List อยู่ในคลาสเพียงอย่างเดียวยังไม่เพียงพอที่จะเป็น Composite Pattern กลุ่มและสมาชิกต้องใช้งานผ่าน Component ร่วมกัน และกลุ่มสามารถมีสมาชิกชนิดเดียวกับตัวมันเองได้ผ่าน Component นั้น
#3. องค์ประกอบของแพตเทิร์น
| บทบาท | หน้าที่ | คลาสในตัวอย่าง |
|---|---|---|
| Component | กำหนดการทำงานร่วมกันของทุกโหนด | FsNode |
| Leaf | วัตถุปลายทาง ไม่มีลูก | FileNode |
| Composite | เก็บลูกและประสานการทำงานของลูก | FolderNode |
| Client | เรียกใช้งานผ่าน Component | showSummary(FsNode node) |
Component จะเป็น interface หรือ abstract class ก็ได้ ตัวอย่างนี้เลือก abstract class เพื่อเก็บชื่อและข้อมูลโฟลเดอร์แม่ร่วมกัน
classDiagram
class FsNode {
<<abstract>>
+getName() String
+sizeInBytes() long
+print(indent) void
}
class FileNode {
-bytes : long
+sizeInBytes() long
+print(indent) void
}
class FolderNode {
-children : List~FsNode~
+add(child) void
+remove(child) boolean
+sizeInBytes() long
+print(indent) void
}
FsNode <|-- FileNode
FsNode <|-- FolderNode
FolderNode "0..1" *-- "0..*" FsNode : children
FolderNode เก็บสมาชิกเป็น FsNode จึงเก็บได้ทั้ง FileNode และ FolderNode โดยไม่ต้องแยกคอลเลกชันตามชนิดจริง ส่วนจำนวนแม่ไม่เกินหนึ่งตัวในแผนภาพเป็นข้อกำหนดของตัวอย่างนี้
#4. สถานการณ์ตัวอย่าง: โครงการที่มีไฟล์และโฟลเดอร์
เราจะสร้างโฟลเดอร์ project ซึ่งมีไฟล์ README.md และโฟลเดอร์ย่อย src กับ docs
flowchart TD
P["project: FolderNode"] --> R["README.md: 100 bytes"]
P --> S["src: FolderNode"]
P --> D["docs: FolderNode"]
S --> M["Main.java: 300 bytes"]
S --> U["Utils.java: 200 bytes"]
D --> G["guide.md: 400 bytes"]
กติกาของแบบจำลอง:
- ไฟล์คืนขนาดที่กำหนดไว้เป็นจำนวนไบต์
- โฟลเดอร์คืนผลรวมขนาดของสมาชิกทุกระดับ
- โฟลเดอร์ว่างมีขนาดรวมเท่ากับ
0 - ขนาดรวมไม่รวมพื้นที่ metadata ของโฟลเดอร์
- โหนดหนึ่งมีโฟลเดอร์แม่ได้ไม่เกินหนึ่งตัว และไม่อนุญาตวงจร
นี่เป็นแบบจำลองเพื่อเรียนรู้แพตเทิร์น ไม่ได้จำลองพฤติกรรมระบบไฟล์จริงทั้งหมด เช่น symbolic link, hard link หรือพื้นที่จัดเก็บที่ใช้จริง
#5. โค้ด Java ฉบับสมบูรณ์
สร้างไฟล์ชื่อ CompositeDemo.java แล้ววางโค้ดต่อไปนี้ คลาสย่อยอยู่ภายในคลาสหลักเพื่อให้ทดลองได้ด้วยไฟล์เดียว
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class CompositeDemo {
// Component: operations shared by a file and a folder.
static abstract class FsNode {
private final String name;
private FolderNode parent;
protected FsNode(String name) {
this.name = Objects.requireNonNull(name, "name");
if (name.isBlank()) {
throw new IllegalArgumentException("Name must not be blank");
}
}
public final String getName() {
return name;
}
public abstract long sizeInBytes();
public abstract void print(String indent);
}
// Leaf: an individual item without children.
static final class FileNode extends FsNode {
private final long bytes;
public FileNode(String name, long bytes) {
super(name);
if (bytes < 0) {
throw new IllegalArgumentException("Size must not be negative");
}
this.bytes = bytes;
}
@Override
public long sizeInBytes() {
return bytes;
}
@Override
public void print(String indent) {
System.out.printf("%s- %s (%d bytes)%n",
indent, getName(), bytes);
}
}
// Composite: stores children using the shared Component type.
static final class FolderNode extends FsNode {
private final List<FsNode> children = new ArrayList<>();
public FolderNode(String name) {
super(name);
}
public void add(FsNode child) {
Objects.requireNonNull(child, "child");
// An ancestor cannot become a child: that would create a cycle.
for (FsNode current = this; current != null;
current = current.parent) {
if (current == child) {
throw new IllegalArgumentException("Cycle is not allowed");
}
}
// Keep a tree: each node has at most one parent.
if (child.parent != null) {
throw new IllegalStateException("Node already has a parent");
}
children.add(child);
child.parent = this;
}
public boolean remove(FsNode child) {
if (children.remove(child)) {
child.parent = null;
return true;
}
return false;
}
@Override
public long sizeInBytes() {
long total = 0;
for (FsNode child : children) {
total = Math.addExact(total, child.sizeInBytes());
}
return total;
}
@Override
public void print(String indent) {
System.out.printf("%s+ %s/%n", indent, getName());
for (FsNode child : children) {
child.print(indent + " ");
}
}
}
// Client: accepts the Component type without inspecting concrete classes.
static void showSummary(FsNode node) {
System.out.printf("%s = %d bytes%n",
node.getName(), node.sizeInBytes());
}
public static void main(String[] args) {
FolderNode project = new FolderNode("project");
FolderNode src = new FolderNode("src");
FolderNode docs = new FolderNode("docs");
FileNode readme = new FileNode("README.md", 100);
src.add(new FileNode("Main.java", 300));
src.add(new FileNode("Utils.java", 200));
docs.add(new FileNode("guide.md", 400));
project.add(readme);
project.add(src);
project.add(docs);
project.print("");
System.out.println();
showSummary(readme);
showSummary(src);
showSummary(project);
}
}
#วิธีรัน
ตรวจสอบว่าเครื่องติดตั้ง JDK แล้ว:
java -version
javac -version
คอมไพล์และรันจากโฟลเดอร์ที่เก็บไฟล์:
javac -encoding UTF-8 CompositeDemo.java
java CompositeDemo
หรือใช้การรันไฟล์ซอร์สโดยตรง:
java CompositeDemo.java
#ผลลัพธ์ที่คาดหวัง
+ project/
- README.md (100 bytes)
+ src/
- Main.java (300 bytes)
- Utils.java (200 bytes)
+ docs/
- guide.md (400 bytes)
README.md = 100 bytes
src = 500 bytes
project = 1000 bytes
#6. อธิบายการทำงานของโค้ด
#6.1 Component: FsNode
FsNode กำหนดเมธอดที่ใช้ร่วมกัน คือ getName(), sizeInBytes() และ print() ส่วน name และ parent เป็นข้อมูลของโหนด
ตัวแปร parent ใช้รักษาเงื่อนไขของต้นไม้และตรวจวงจร ไม่ใช่ส่วนที่ทุก Composite Pattern ต้องมี หากโดเมนอนุญาตให้ใช้สมาชิกซ้ำในหลายกลุ่ม อาจออกแบบแตกต่างออกไป
#6.2 Leaf: FileNode
ไฟล์ไม่มีสมาชิกภายใน จึงคืนค่า bytes ได้ทันที:
@Override
public long sizeInBytes() {
return bytes;
}
คลาสนี้ไม่ต้องรู้จักโฟลเดอร์อื่น และไม่ต้องมีเมธอด add() หรือ remove()
#6.3 Composite: FolderNode
ส่วนที่เป็นหัวใจของแพตเทิร์นคือการเก็บสมาชิกด้วยชนิดข้อมูลร่วมกัน:
private final List<FsNode> children = new ArrayList<>();
และเรียกเมธอดของสมาชิกโดยไม่ตรวจว่าเป็นไฟล์หรือโฟลเดอร์:
for (FsNode child : children) {
total = Math.addExact(total, child.sizeInBytes());
}
ถ้า child เป็นไฟล์ Java จะเรียกการทำงานของ FileNode ถ้าเป็นโฟลเดอร์ Java จะเรียกการทำงานของ FolderNode ซึ่งเดินลูกของตัวเองต่อไป นี่คือการใช้ Polymorphism ร่วมกับ Recursion
Math.addExact() ทำให้การรวมค่าที่เกินขอบเขต long เกิด ArithmeticException แทนการล้นแล้วได้ค่าที่ผิดโดยเงียบ ๆ
#6.4 Client: showSummary()
static void showSummary(FsNode node) {
System.out.printf("%s = %d bytes%n",
node.getName(), node.sizeInBytes());
}
เมธอดนี้รับได้ทั้งไฟล์ โฟลเดอร์ย่อย และโฟลเดอร์ราก โดยไม่ใช้ instanceof หรือแปลงชนิดข้อมูล
ผู้ประกอบโครงสร้างใน main() ยังต้องรู้ว่าโฟลเดอร์มี add() แต่ส่วนที่อ่านข้อมูลรวมสามารถทำงานผ่าน FsNode ได้ทั้งหมด
#7. การคำนวณแบบ Recursive เกิดขึ้นอย่างไร
เมื่อเรียก project.sizeInBytes() การคำนวณเกิดตามสมาชิกที่พบ:
README.mdคืนค่า100srcรวมMain.javaและUtils.javaได้300 + 200 = 500docsรวมguide.mdได้400projectรวมผลลัพธ์ของลูกโดยตรง ได้100 + 500 + 400 = 1000
แต่ละโฟลเดอร์รู้เฉพาะลูกโดยตรงของตัวเอง ส่วนการเดินลงไปยังระดับลึกเกิดจากการเรียกเมธอดของโฟลเดอร์ลูก
การเรียกเมธอดเหมือนกันไม่ได้หมายความว่าการทำงานภายในเหมือนกัน: Leaf ทำงานกับตัวเอง ส่วน Composite ประสานการทำงานของสมาชิก
#8. เหตุใดต้องป้องกันวงจรและโหนดที่มีแม่หลายตัว
ตัวอย่างกำหนดให้ข้อมูลเป็นต้นไม้ จึงตรวจเงื่อนไขเมื่อเพิ่มลูก แทนที่จะรอให้การคำนวณพบปัญหา
#เพิ่มตัวเองเป็นลูก
project.add(project); // IllegalArgumentException
หากอนุญาต เมื่อเรียกหาขนาด โฟลเดอร์จะเรียกตัวเองซ้ำไม่สิ้นสุด
#เพิ่มบรรพบุรุษเป็นลูก
หลัง project.add(src) แล้ว คำสั่งนี้ต้องถูกปฏิเสธ:
src.add(project); // IllegalArgumentException
โค้ดจึงเดินจากโฟลเดอร์ปัจจุบันขึ้นไปตาม parent หากพบโหนดที่จะเพิ่ม แสดงว่าจะเกิดวงจร
#เพิ่มโหนดเดิมซ้ำหรือย้ายโดยไม่ถอดออกก่อน
docs.add(readme); // IllegalStateException: readme belongs to project
ถ้าต้องการย้ายไฟล์ ให้ถอดจากแม่เดิมก่อน:
project.remove(readme);
docs.add(readme);
เมื่อย้ายภายใน project ขนาดรวมของ project ยังเป็น 1000 ไบต์ แต่ขนาดของ docs เปลี่ยนจาก 400 เป็น 500 ไบต์
กติกาแม่ตัวเดียวเป็นการตัดสินใจของโดเมนนี้ Composite บางแบบอนุญาตให้หลายกลุ่มอ้างถึงสมาชิกเดียวกันได้ ซึ่งต้องระบุความหมายของการนับซ้ำและจัดการวงจรเพิ่มเติม
#9. ควรวาง add() และ remove() ไว้ที่ไหน
มีแนวทางหลักสองแบบ:
| แนวทาง | วางเมธอดจัดการลูกที่ใด | ข้อดี | สิ่งที่ต้องแลก |
|---|---|---|---|
| Transparent Composite | Component | Client เห็น API จัดการลูกเหมือนกันทุกโหนด | Leaf ต้องปฏิเสธหรือกำหนดพฤติกรรมสำหรับเมธอดที่ใช้ไม่ได้ |
| Safe Composite | Composite เท่านั้น | เพิ่มลูกให้ Leaf ไม่ได้ตั้งแต่ระดับชนิดข้อมูล | ผู้ประกอบโครงสร้างต้องรู้ว่าโหนดใดเป็น Composite |
บทความนี้ใช้ Safe Composite: มีเพียง FolderNode ที่มี add() และ remove() ส่วนการหาขนาดและแสดงรายการยังใช้ร่วมกันผ่าน FsNode
อย่าเพิ่มเมธอดลงใน Component เพียงเพื่อให้ทุกคลาสมีเมธอดจำนวนเท่ากัน ควรเลือกเฉพาะการทำงานที่มีความหมายร่วมกันจริง
#10. เวิร์กช็อป: ขยายตัวอย่างทีละขั้น
#กิจกรรมที่ 1: เพิ่มโฟลเดอร์ว่าง
เพิ่มก่อนคำสั่งแสดงผลใน main():
FolderNode assets = new FolderNode("assets");
project.add(assets);
showSummary(assets);
ผลที่ควรได้คือ assets = 0 bytes และขนาดรวมของ project ยังเท่ากับ 1000 ไบต์
#กิจกรรมที่ 2: เพิ่มระดับความลึก
ต่อจากกิจกรรมที่ 1:
FolderNode images = new FolderNode("images");
images.add(new FileNode("logo.png", 250));
assets.add(images);
showSummary(project);
ขนาดรวมควรเป็น 1250 ไบต์ โดยไม่ต้องแก้ showSummary() หรือโค้ดรวมขนาดเดิม
#กิจกรรมที่ 3: เพิ่มการนับจำนวนไฟล์
เพิ่มเมธอดนี้ใน FsNode:
public abstract long fileCount();
ใน FileNode ให้คืนค่า 1 ส่วน FolderNode รวมค่า fileCount() จากลูกทุกตัว โฟลเดอร์ว่างต้องคืนค่า 0
โจทย์นี้แสดงอีกด้านของแพตเทิร์น: การเพิ่มชนิด Leaf ใหม่อาจทำได้โดยไม่แก้ Client แต่การเพิ่ม operation ใหม่ลงใน Component ต้องปรับคลาสที่เกี่ยวข้องด้วย
#ค่าที่ใช้ตรวจงาน
| กรณี | ผลที่ควรได้ |
|---|---|
ตัวอย่างเริ่มต้น: project.sizeInBytes() |
1000 |
โฟลเดอร์ว่าง: assets.sizeInBytes() |
0 |
หลังเพิ่ม logo.png ขนาด 250 ไบต์ |
project เท่ากับ 1250 |
| จำนวนไฟล์ในตัวอย่างเริ่มต้น | 4 |
จำนวนไฟล์หลังเพิ่ม logo.png |
5 |
| เพิ่มโฟลเดอร์ให้เป็นลูกของตัวเอง | ปฏิเสธด้วย IllegalArgumentException |
| เพิ่มบรรพบุรุษเป็นลูก | ปฏิเสธด้วย IllegalArgumentException |
| เพิ่มโหนดที่มีแม่อยู่แล้ว | ปฏิเสธด้วย IllegalStateException |
#11. ข้อดีและข้อจำกัด
#ข้อดี
- Client เรียกการทำงานร่วมกันได้โดยไม่แยกชนิดไฟล์กับโฟลเดอร์
- สร้างโครงสร้างที่ซ้อนกันหลายระดับได้ด้วยกติกาเดียว
- ความรับผิดชอบในการรวมผลอยู่ใกล้กับกลุ่มที่เป็นเจ้าของสมาชิก
- เพิ่ม Leaf ชนิดใหม่ได้สะดวกเมื่อ operation ที่มีอยู่รองรับพฤติกรรมของชนิดนั้น
#ข้อจำกัดที่ควรออกแบบต่อเมื่อใช้งานจริง
- Operation ใหม่: การเพิ่มเมธอดใน Component อาจกระทบ Leaf และ Composite หลายคลาส หากเพิ่มบ่อยอาจพิจารณา Visitor ตามบริบท
- ต้นไม้ลึกมาก: การเรียก Recursive อาจทำให้ stack เต็ม ควรพิจารณาเดินโครงสร้างด้วย explicit stack
- การแก้ข้อมูลพร้อมกัน: ตัวอย่างไม่ได้ออกแบบให้ thread-safe ต้องกำหนดวิธีประสานการอ่านและการแก้ไขหากใช้หลายเธรด
- การแคชผลรวม: ช่วยลดการเดินซ้ำได้ แต่ต้องปรับหรือยกเลิกแคชเมื่อสมาชิกเปลี่ยน รวมถึงส่งผลขึ้นไปยังบรรพบุรุษ
- กฎของสมาชิก: หากบางกลุ่มรับสมาชิกได้เฉพาะบางชนิด ต้องออกแบบข้อจำกัดเพิ่ม การมี Component ร่วมกันไม่ได้บังคับกฎโดเมนให้เอง
#ต้นทุนการทำงานในตัวอย่าง
กำหนดให้ N คือจำนวนโหนดในต้นไม้ย่อยที่กำลังอ่าน และ H คือความสูงหรือจำนวนระดับตามเส้นทางที่เกี่ยวข้อง
| การทำงาน | ต้นทุน |
|---|---|
FileNode.sizeInBytes() |
เวลา O(1) |
FolderNode.sizeInBytes() |
เวลา O(N) และ call stack O(H) |
FolderNode.add() |
ตรวจบรรพบุรุษ O(H) และเพิ่มใน ArrayList แบบ amortized O(1) |
FolderNode.remove() |
เวลา O(k) เมื่อมีลูกโดยตรง k ตัว |
print() เยี่ยมชมทุกโหนดหนึ่งครั้ง แต่เวลาจริงยังขึ้นกับความยาวข้อความ ระยะเยื้อง และการเขียนลงปลายทางด้วย
#12. เปรียบเทียบกับแพตเทิร์นที่ใกล้เคียง
| แพตเทิร์น | จุดประสงค์หลัก | คำถามที่ช่วยแยก |
|---|---|---|
| Composite | รวมชิ้นส่วนและกลุ่มให้ทำงานผ่านสัญญาร่วมกัน | ต้องมีองค์ประกอบที่ซ้อนเป็นส่วนรวมหลายระดับหรือไม่? |
| Decorator | ห่อวัตถุเพื่อเพิ่มพฤติกรรม | ต้องเพิ่มความสามารถให้วัตถุผ่านตัวห่อหรือไม่? |
| Adapter | ทำให้ API ที่ต่างกันใช้งานร่วมกันได้ | ต้องแปลงอินเทอร์เฟซที่ไม่ตรงกันหรือไม่? |
| Visitor | แยก operation ออกจากโครงสร้างวัตถุ | มี operation ใหม่เพิ่มบ่อยบนชนิดโหนดที่ค่อนข้างคงที่หรือไม่? |
Composite และ Decorator อาจมีโครงสร้างคลาสบางส่วนคล้ายกัน แต่ควรแยกจากเจตนา: ตัวอย่างนี้รวมสมาชิกหลายตัวเพื่อเป็นส่วนรวม ส่วน Decorator เน้นการเพิ่มพฤติกรรมผ่านการห่อวัตถุ
#13. แนวคิดลักษณะเดียวกันใน Java API
เอกสาร Java AWT ระบุว่า java.awt.Container สืบทอดจาก java.awt.Component และเก็บ Component อื่นได้ การที่ Container เองก็เป็น Component ทำให้เกิดโครงสร้าง UI ที่ซ้อนกันหลายระดับได้
เราจึงใช้ความสัมพันธ์นี้เป็นตัวอย่างเปรียบเทียบแนวคิด Composite ได้: Component เป็นชนิดร่วม และ Container เป็นองค์ประกอบที่บรรจุสมาชิก โดย API จริงยังมีรายละเอียดด้าน layout, event และ lifecycle อีกมาก
ตัวอย่างในบทความเป็นโมเดลที่เขียนขึ้นเพื่อสาธิต ไม่ได้เรียกใช้ AWT
#14. คำถามทบทวน
- เพราะเหตุใด
FolderNodeจึงเก็บList<FsNode>แทนList<FileNode>? - เหตุใด
showSummary()จึงไม่ต้องตรวจชนิดข้อมูลด้วยinstanceof? - จะเกิดอะไรขึ้นถ้าโฟลเดอร์สามารถเก็บตัวเองเป็นลูกได้?
- Safe Composite และ Transparent Composite แลกความสะดวกกับความปลอดภัยของชนิดข้อมูลอย่างไร?
- หากต้องการเพิ่ม
fileCount()ต้องแก้ส่วนใดบ้าง และแตกต่างจากการเพิ่ม Leaf ใหม่อย่างไร?
#15. แหล่งอ้างอิงและอ่านเพิ่มเติม
- Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. บท Structural Patterns: Composite — แหล่งอ้างอิงพื้นฐานของแพตเทิร์น
- Oracle. Class Container — Java SE 21 API — ความสัมพันธ์ระหว่าง Container, Component และสมาชิกภายใน
- Oracle. Class Component — Java SE 21 API — API ขององค์ประกอบ UI ที่ใช้อธิบายแนวคิดเปรียบเทียบ