อะไรคือข้อเสียของการกำหนดคลาสเป็นคลาสย่อยของรายการของตัวเอง?


48

ในโครงการล่าสุดของฉันฉันกำหนดคลาสด้วยส่วนหัวต่อไปนี้:

public class Node extends ArrayList<Node> {
    ...
}

อย่างไรก็ตามหลังจากพูดคุยกับอาจารย์ประจำ CS ของฉันเขากล่าวว่าในชั้นเรียนจะ "น่ากลัวสำหรับความทรงจำ" และ "การปฏิบัติที่ไม่ดี" ฉันไม่ได้พบคนแรกที่เป็นจริงโดยเฉพาะและที่สองที่จะเป็นอัตนัย

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

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

มีเหตุผลที่ถูกต้องหรือไม่ทำไมฉันไม่ควรใช้รหัสนี้ในการพิจารณาการใช้งานของฉัน


¹หากฉันจำเป็นต้องอธิบายเรื่องนี้เพิ่มเติมฉันยินดีที่จะ; ฉันแค่พยายามทำให้คำถามนี้สั้นกระชับ



1
@gnat สิ่งนี้เกี่ยวข้องกับการกำหนดคลาสเป็นรายการของตัวเองอย่างเคร่งครัดแทนที่จะมีรายการที่มีรายการ ฉันคิดว่ามันแตกต่างจากสิ่งที่คล้ายกัน แต่ไม่เหมือนกันทั้งหมด คำถามที่หมายถึงสิ่งที่ตามเส้นของคำตอบที่โรเบิร์ตฮาร์วีย์
Addison Crump

16
รับช่วงจากบางสิ่งที่แปรสภาพด้วยตัวเองไม่ผิดปกติดังที่แสดงสำนวนCRTP ที่ใช้กันทั่วไปใน C ++ ในกรณีของคอนเทนเนอร์คำถามสำคัญคือการพิสูจน์เหตุผลที่คุณต้องใช้การสืบทอดแทนการแต่ง
Christophe

1
ฉันชอบความคิด ท้ายที่สุดแล้วแต่ละโหนดในทรีเป็นทรี(ย่อย) โดยปกติแล้ว Tree-Ness ของโหนดจะถูกซ่อนจากมุมมองชนิด (โหนดคลาสสิกไม่ใช่แบบต้นไม้ แต่เป็นวัตถุเดียว) และแสดงแทนในมุมมองข้อมูล (โหนดเดียวอนุญาตให้เข้าถึงสาขา) นี่คือวิธีอื่น ๆ เราจะไม่เห็นข้อมูลสาขามันอยู่ในประเภท น่าแปลกที่เค้าโครงวัตถุจริงใน C ++ น่าจะคล้ายกันมาก (การสืบทอดถูกนำไปใช้อย่างคล้ายคลึงกับข้อมูลที่มีอยู่) ซึ่งทำให้ฉันคิดว่าเรากำลังเผชิญกับสองวิธีในการแสดงสิ่งเดียวกัน
Peter - Reinstate Monica

1
บางทีฉันหายไปนิด แต่คุณต้องการจริงๆที่จะขยาย ArrayList<Node>เมื่อเทียบกับการดำเนินการ List<Node> ?
Matthias

คำตอบ:


107

ตรงไปตรงมาฉันไม่เห็นความต้องการมรดกที่นี่ มันไม่สมเหตุสมผล Node เป็น ArrayListของNode?

หากนี่เป็นเพียงโครงสร้างข้อมูลแบบเรียกซ้ำคุณจะเขียนดังนี้:

public class Node {
    public String item;
    public List<Node> children;
}

ซึ่งจะทำให้ความรู้สึก; node มีรายการ children หรือโหนดที่สืบทอด


34
มันไม่เหมือนกัน รายการของรายการad-infinitumนั้นไม่มีความหมายเว้นแต่ว่าคุณกำลังจัดเก็บบางอย่างนอกเหนือจากรายการใหม่ในรายการ นั่นคือสิ่งที่Item มีอยู่และItemดำเนินการอย่างเป็นอิสระจากรายการ
Robert Harvey

6
โอ้ฉันเห็นแล้ว ฉันได้เข้าหาNode เป็น ArrayListของNodeที่Node มี 0 ต่อ Nodeวัตถุมากมาย ฟังก์ชั่นของพวกเขาคือเดียวกันเป็นหลัก แต่แทนที่จะเป็นรายการของวัตถุเช่นนั้นจะมีรายชื่อของวัตถุเช่น การออกแบบดั้งเดิมสำหรับชั้นเรียนที่เขียนขึ้นนั้นเป็นความเชื่อที่ว่าสิ่งใดก็ตามNodeสามารถแสดงเป็นชุดย่อยNodeได้ซึ่งการดำเนินการทั้งสองทำได้ แต่สิ่งนี้ทำให้เกิดความสับสนน้อยลงอย่างแน่นอน +1
Addison Crump

11
ฉันจะบอกว่าเอกลักษณ์ระหว่างโหนดและรายการมีแนวโน้มที่จะเกิดขึ้น "ตามธรรมชาติ" เมื่อคุณเขียนรายการที่เชื่อมโยงเดี่ยวใน C หรือ Lisp หรืออะไรก็ตาม: ในการออกแบบบางอย่างหัวโหนด "คือ" รายการในแง่ของการเป็นเพียง สิ่งที่เคยใช้เพื่อแสดงรายการ ดังนั้นฉันจึงไม่สามารถปัดเป่าความรู้สึกได้อย่างสมบูรณ์ว่าในบริบทบางอย่างอาจเป็นโหนด "คือ" (ไม่ใช่แค่ "มี") คอลเล็กชันของโหนดแม้ว่าฉันจะเห็นด้วยว่ามันรู้สึก EvilBadWrong ใน Java
Steve Jessop

12
@ nl-x ว่าหนึ่งไวยากรณ์สั้นกว่าไม่ได้หมายความว่าจะดีกว่า นอกจากนี้บังเอิญมันค่อนข้างยากที่จะเข้าถึงรายการในต้นไม้เช่นนั้น โดยปกติโครงสร้างจะไม่รู้จักกันในเวลารวบรวมดังนั้นคุณมักจะไม่ได้สำรวจต้นไม้ดังกล่าวด้วยค่าคงที่ ความลึกยังไม่ได้รับการแก้ไขดังนั้นคุณจึงไม่สามารถทำซ้ำได้มากกว่าค่าทั้งหมดโดยใช้ไวยากรณ์นั้น เมื่อคุณปรับเปลี่ยนรหัสเพื่อใช้ปัญหาการแวะผ่านต้นไม้แบบดั้งเดิมคุณจะต้องเขียนChildrenเพียงหนึ่งหรือสองครั้งและโดยทั่วไปแล้วควรเขียนมันออกมาในกรณีเช่นนี้เพื่อความชัดเจนของโค้ด
Servy


57

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

ตัวย่อ SOLID ให้การวิเคราะห์พฤติกรรมสำหรับการออกแบบเชิงวัตถุที่ดี ที่นี่I nterface Segregation Principle (ISP) และL iskov Substitution Pricinple (LSP) มีบางอย่างที่จะพูด

ISP บอกให้เรารักษาอินเตอร์เฟสให้เล็กที่สุดเท่าที่จะทำได้ แต่ด้วยการสืบทอดArrayListคุณจะได้รับวิธีการมากมาย มันมีความหมายกับget(), remove(), set()(แทนที่) หรือadd()(แทรก) โหนดเด็กที่ดัชนีโดยเฉพาะ? เป็นเรื่องที่สมเหตุสมผลensureCapacity()หรือไม่สำหรับรายการที่อยู่ภายใน? sort()โหนดหมายถึงอะไร? ผู้ใช้ในชั้นเรียนของคุณควรได้รับจริงsubList()หรือ เนื่องจากคุณไม่สามารถซ่อนเมธอดที่คุณไม่ต้องการได้ทางออกเดียวคือให้ ArrayList เป็นตัวแปรสมาชิกและส่งต่อเมธอดทั้งหมดที่คุณต้องการ:

private final ArrayList<Node> children = new ArrayList();
public void add(Node child) { children.add(child); }
public Iterator<Node> iterator() { return children.iterator(); }

หากคุณต้องการวิธีการทั้งหมดที่คุณเห็นในเอกสารเราสามารถไปยัง LSP LSP บอกเราว่าเราจะต้องสามารถใช้คลาสย่อยได้ในทุกที่ที่ผู้ปกครองต้องการ หากฟังก์ชั่นรับArrayListพารามิเตอร์เป็นและเราส่งผ่านNodeแทนไม่มีอะไรควรจะเปลี่ยน

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

แต่ LSP ทำงานได้ลึกกว่านี้มาก: เราต้องรักษาความเข้ากันได้กับทุกสิ่งที่สัญญาไว้ในเอกสารของคลาสแม่และอินเทอร์เฟซทั้งหมด ในคำตอบของพวกเขาลินน์พบกรณีหนึ่งกรณีที่Listอินเทอร์เฟซ (ซึ่งคุณสืบทอดมาArrayList) รับประกันว่าวิธีการequals()และhashCode()วิธีการทำงานอย่างไร สำหรับhashCode()คุณจะได้รับอัลกอริทึมเฉพาะที่ต้องดำเนินการอย่างแน่นอน สมมติว่าคุณเขียนสิ่งนี้Node:

public class Node extends ArrayList<Node> {
  public final int value;

  public Node(int value, Node... children) {
    this.value = Value;
    for (Node child : children)
      add(child);
  }

  ...

}

สิ่งนี้ต้องการให้สิ่งที่valueไม่สามารถมีส่วนร่วมhashCode()และไม่สามารถมีอิทธิพลequals()ได้ Listอินเตอร์เฟซ - ซึ่งคุณสัญญาว่าจะให้เกียรติโดยการสืบทอดจากมัน - ต้องการnew Node(0).equals(new Node(123))ที่จะเป็นจริง


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

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

ตัวอย่างหนึ่งคือปัญหาการสร้างแบบจำลองพนักงาน - หัวหน้า เรามีหลายรายการPersonแต่ละแห่งมีชื่อและที่อยู่ Employeeเป็นและมีPerson ยังเป็น ดังนั้นฉันควรสร้างคลาสที่สืบทอดและ? ตอนนี้ฉันมีปัญหา: เจ้านายก็เป็นพนักงานและก็มีหัวหน้าอีกคนหนึ่ง ดังนั้นจึงดูเหมือนว่าควรขยาย แต่เป็นแต่ไม่ได้เป็น? กราฟการสืบทอดประเภทใดจะแตกลงที่นี่BossBossPersonPersonBossEmployeeBossEmployeeCompanyOwnerBossEmployee

OOP ไม่เกี่ยวกับลำดับชั้นมรดกและกลับมาใช้ของชั้นเรียนที่มีอยู่มันเป็นเรื่องเกี่ยวgeneralizing พฤติกรรม OOP เป็นเรื่องเกี่ยวกับ“ ฉันมีวัตถุมากมายและต้องการให้พวกเขาทำสิ่งใดสิ่งหนึ่ง - และฉันไม่สนใจเลย” นั่นคืออินเทอร์เฟซสำหรับ หากคุณใช้Iterableอินเทอร์เฟซสำหรับของคุณNodeเพราะคุณต้องการทำให้ iterable นั่นเป็นเรื่องที่สมบูรณ์แบบ หากคุณติดตั้งCollectionส่วนต่อประสานเพราะคุณต้องการเพิ่ม / ลบโหนดลูกเป็นต้นนั่นก็ถือว่าใช้ได้ แต่สืบทอดมาจากคลาสอื่นเพราะมันเกิดขึ้นเพื่อให้คุณทั้งหมดที่ไม่ได้หรืออย่างน้อยไม่เว้นแต่คุณจะได้ให้มันคิดอย่างรอบคอบตามที่อธิบายไว้ข้างต้น


10
ฉันได้พิมพ์ 4 ย่อหน้าสุดท้ายของคุณชื่อพวกเขาเป็นเกี่ยวกับ OOP และมรดกและทำตามพวกเขาบนผนังในที่ทำงาน เพื่อนร่วมงานของฉันบางคนต้องเห็นว่าอย่างที่ฉันรู้ว่าพวกเขาจะขอบคุณวิธีที่คุณใส่ไว้ :) ขอบคุณ
YSC

17

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


14

การเพิ่มสิ่งที่กล่าวมามีเหตุผลที่ค่อนข้างเฉพาะจาวาเพื่อหลีกเลี่ยงโครงสร้างประเภทนี้

สัญญาของequalsวิธีการสำหรับรายการต้องมีรายการที่จะพิจารณาเท่ากับวัตถุอื่น

และถ้าหากวัตถุที่ระบุยังเป็นรายการทั้งสองรายการมีขนาดเดียวกันและคู่ที่สอดคล้องกันทั้งหมดขององค์ประกอบในสองรายการมีความเท่าเทียมกัน

ที่มา: https://docs.oracle.com/javase/7/docs/api/java/util/List.html#equals(java.lang.Object)

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


1
นี่เป็นเหตุผลที่ดีในการลบสิ่งนี้จากรหัสของฉันนอกเหนือจากการปฏิบัติที่ไม่ดี : P
Addison Crump

3

เกี่ยวกับหน่วยความจำ:

ฉันจะบอกว่านี่เป็นเรื่องของลัทธิพอใจนิยม ตัวสร้างเริ่มต้นของArrayListมีลักษณะเช่นนี้:

public ArrayList(int initialCapacity) {
     super();

     if (initialCapacity < 0)
         throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);

     this.elementData = new Object[initialCapacity];
 }

public ArrayList() {
     this(10);
}

แหล่ง คอนสตรัคนี้ยังใช้ใน Oracle-JDK

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

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

แก้ไข: คำชี้แจงเกี่ยวกับความคิดเห็น (@amon ให้แม่นยำ) คำตอบในส่วนนี้ไม่ได้เกี่ยวข้องกับปัญหาการสืบทอด การเปรียบเทียบการใช้หน่วยความจำนั้นทำขึ้นกับรายการที่เชื่อมโยงเดี่ยว ๆ และด้วยการใช้หน่วยความจำที่ดีที่สุด (ในการปรับใช้จริงปัจจัยอาจเปลี่ยนแปลงเล็กน้อย

เกี่ยวกับ "การปฏิบัติที่ไม่เหมาะสม":

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


1
10 เท่าสูงกว่าเมื่อเทียบกับสิ่งที่ ? ถ้าฉันมี ArrayList ที่สร้างขึ้นเป็นค่าเริ่มต้นเป็นเขตข้อมูลอินสแตนซ์ (แทนที่จะสืบทอดจากมัน) ฉันใช้หน่วยความจำเพิ่มขึ้นเล็กน้อยเนื่องจากฉันต้องการเก็บตัวชี้ไปยัง ArrayList เพิ่มเติมในวัตถุของฉัน แม้ว่าเราจะเปรียบเทียบแอปเปิ้ลกับส้มและเปรียบเทียบการสืบทอดจาก ArrayList เพื่อไม่ใช้ ArrayList ใด ๆ โปรดทราบว่าใน Java เรามักจะจัดการกับตัวชี้ไปยังวัตถุไม่เคยวัตถุตามมูลค่าตาม C ++ จะทำ สมมติว่าการnew Object[0]ใช้ทั้งหมด 4 คำ, a new Object[10]จะใช้เพียง 14 คำของหน่วยความจำ
amon

@amon ฉันไม่ได้ระบุไว้ชัดเจน แม้ว่าบริบทจะเพียงพอที่จะเห็นว่าฉันเปรียบเทียบกับ LinkedList และมันเรียกว่าการอ้างอิงไม่ใช่ตัวชี้ btw และจุดที่มีหน่วยความจำไม่ได้เกี่ยวกับว่าจะใช้คลาสผ่านการรับมรดกหรือไม่ แต่เพียงความจริงที่ว่า (เกือบ) ArrayLists ที่ไม่ได้ใช้นั้นกำลังรอบเอวค่อนข้างน้อย
พอล

-2

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


13
ฉันจะไม่ downvote นี้ แต่นี่คือเหตุผลที่ฉันจริงๆเกลียดหนังสือ GoF มันเป็นต้นไม้แห่ง Bleedin! ทำไมเราต้องคิดค้นคำว่า "รูปแบบผสม" เพื่ออธิบายต้นไม้
David Arno

3
@DavidArno ฉันเชื่อว่ามันเป็นลักษณะของ polymorphic-node ทั้งหมดที่กำหนดว่าเป็น "รูปแบบผสม" การใช้โครงสร้างข้อมูลต้นไม้เป็นเพียงส่วนหนึ่งของมัน
JAB

2
@ RobertHarvey ฉันไม่เห็นด้วย (ทั้งความคิดเห็นของคุณต่อคำตอบของคุณและที่นี่) public class Node extends ArrayList<Node> { public string Item; }เป็นรุ่นที่สืบทอดของคำตอบของคุณ ของคุณนั้นง่ายกว่าที่จะให้เหตุผล แต่ทั้งคู่ก็บรรลุบางสิ่ง: มันนิยามต้นไม้ที่ไม่ใช่แบบไบนารี
David Arno

2
@DavidArno: ฉันไม่เคยบอกว่ามันไม่ได้ผลฉันแค่บอกว่ามันอาจจะไม่เหมาะ
Robert Harvey

1
@ DavidArno ไม่ได้เกี่ยวกับความรู้สึกและรสนิยมที่นี่ แต่มันเกี่ยวกับข้อเท็จจริง ต้นไม้ทำจากโหนดและทุกโหนดมีลูกที่มีศักยภาพ โหนดที่กำหนดเป็นโหนดลีฟเพียงช่วงเวลาที่กำหนด ในคอมโพสิตโหนดใบเป็นใบไม้โดยการสร้างและลักษณะของมันจะไม่เปลี่ยนแปลง
Christophe
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.