ชั้นเรียนในภาษา OOP และประเภท


9

ในทฤษฎีการเขียนโปรแกรมภาษาประเภทคือชุดของค่า เช่น type "int" คือชุดของค่าจำนวนเต็มทั้งหมด

ในภาษา OOP คลาสเป็นประเภทใช่หรือไม่

เมื่อชั้นเรียนมีสมาชิกมากกว่าหนึ่งคนเช่น

class myclass{
    int a; 
    double b;
}

เมื่อเราพูดถึงชั้นเรียนเราหมายถึง

  • " (a,b)อยู่ที่ไหนaint และbเป็นสองเท่า" หรือ
  • "{ (x,y)| xis int ใด ๆ , ydouble ใด ๆ }"?

ตัวอย่างmyclassหมายถึงอะไร

  • " (a,b)อยู่ที่ไหนaint และbเป็นสองเท่า" หรือ
  • วัตถุที่ใช้พื้นที่หน่วยความจำและที่ (ไม่จำเป็นต้องเช่นว่างเปล่า) ร้านค้า(x,y)ที่xใด int และyเป็นคู่ใด ๆ

2
คลาสเป็นประเภท "{(x, y) | x คือ int ใด ๆ , y คือ double ใด ๆ }" "จะถูกต้องเกือบยกเว้นสองสิ่ง: 1) คุณใช้ tuple ในขณะที่คลาสนั้นเป็นแนวคิดในการบันทึก - คุณอ้างถึง เขตข้อมูลตามชื่อไม่ใช่ตำแหน่งและ 2) ไม่ใช่ทุกระเบียนที่มีเขตข้อมูลaและbเป็นสมาชิกของประเภทนั้นดังที่ Killian Forth กล่าวถึง Myclass isomorphicกับระเบียนที่มีเขตข้อมูลaและbประเภทintและdouble- คุณสามารถบันทึกเช่นนั้นและเปลี่ยนเป็น เป็นตัวอย่างของmyclass.
Doval

1
ในภาษาที่พิมพ์อย่างรุนแรงคลาสจะเป็นประเภท ในภาษาที่มีการพิมพ์น้อยอาจเป็นหรือไม่เป็นประเภทก็ได้
shawnhcorey

1
ในทฤษฎีการเขียนโปรแกรมภาษาประเภทคือชุดของค่าหรือไม่ ฉันคิดว่าคุณต้องรับหนังสือเล่มอื่นหรืออาจารย์คนอื่นหรือทั้งสองอย่าง 'ตัวแปร' หรือ 'ค่าคงที่' มี 'ชนิด' และมักจะมี 'ค่า' มีประเภทค่าเป็นศูนย์สเกลาร์และประเภทค่ารวมที่ค่าของตัวแปรหรือค่าคงที่มีตัวแปรย่อย / ค่าคงที่ย่อย
user1703394

1
@ user1703394 ประเภทคือชุดของค่า ประเภทจำนวนเต็ม 32 บิตเป็นชุดของค่าที่แตกต่าง 2 ^ 32 หากนิพจน์ประเมินค่าเป็นประเภทนั้นคุณจะรู้ว่าค่านั้นอยู่ในชุดนั้น ตัวดำเนินการทางคณิตศาสตร์เป็นเพียงฟังก์ชันเหนือค่าของชุดนั้น
Doval

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

คำตอบ:


30

ทั้ง

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

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


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

7
@Tim typedef ไม่ได้สร้างประเภทมันเป็นนามแฝงชื่อที่ใช้ในการอ้างถึงประเภทที่มีอยู่
Doval

1
@DevSolar เขาพูดถึง C อย่างชัดเจนและนอกเหนือจาก C ++ ฉันไม่รู้ภาษาอื่นที่ใช้คำหลักนั้น
Doval

3
@Telastyn - ภาษาเหล่านั้นควรถูกฆ่าด้วยไฟ
Jon Story

4
@JonStory การพิมพ์ย่อยโครงสร้างมีประโยชน์ในระดับโมดูล การขาดมันคือสิ่งที่บังคับให้คุณเปลี่ยนทุกอย่างให้เป็นinterfaceJava และ C # หากคุณต้องการทำการทดสอบหน่วย คุณสามารถเขียนแผ่นสำเร็จรูปจำนวนหนึ่งได้เพื่อให้คุณสามารถเปลี่ยนชั้นเรียนที่โปรแกรมของคุณจะใช้แม้ว่าคุณจะไม่ได้ตั้งใจเปลี่ยนมันในรันไทม์ก็ตาม
Doval

6

ประเภทไม่ได้ตั้งค่า

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

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

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

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

โดยทั่วไปจะได้รับอนุญาตให้ทับซ้อนกันโดยที่ไม่ได้อยู่ในชุดอื่น นี่เป็นเรื่องแปลกในทฤษฎีประเภทแม้ว่าบางภาษาจะสนับสนุนในรูปแบบของการสืบทอดหลาย ๆ ภาษาอื่นเช่น Java อนุญาตเฉพาะรูปแบบที่ จำกัด นี้หรือไม่อนุญาตทั้งหมด

มีประเภทที่ว่างเปล่าอยู่ (เรียกว่าประเภทด้านล่าง ) แต่ภาษาส่วนใหญ่ไม่รองรับหรือไม่ถือว่าเป็นประเภทที่มีระดับ "ประเภทที่มีประเภทอื่น ๆ ทั้งหมด" ก็มีอยู่ (เรียกว่าประเภทด้านบน ) และได้รับการสนับสนุนอย่างกว้างขวางซึ่งแตกต่างจากทฤษฎีเซต

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


ความคิดของ "subtyping" นั้นค่อนข้างแตกต่างจากแนวคิดของ "subset" หากXเป็นประเภทย่อยYหมายถึงเราสามารถทดแทนอินสแตนซ์ของYสำหรับอินสแตนซ์ของXและโปรแกรมจะยังคง "ทำงาน" ในบางกรณี นี่เป็นพฤติกรรมมากกว่าโครงสร้างแม้ว่าบางภาษา (เช่น Go, Rust, arguably C) ได้เลือกแบบหลังเพื่อความสะดวกไม่ว่าจะเป็นโปรแกรมเมอร์หรือการใช้ภาษา


ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
วิศวกรโลก

4

ประเภทข้อมูลเกี่ยวกับพีชคณิตเป็นวิธีการอภิปรายนี้

มีสามวิธีพื้นฐานที่คุณสามารถรวมประเภทได้:

  • สินค้า นั่นคือสิ่งที่คุณคิดโดยทั่วไป:

    struct IntXDouble{
      int a; 
      double b;
    }
    

    เป็นประเภทผลิตภัณฑ์ ค่าที่มีทั้งหมดรวมกันได้ (เช่น tuples) ของหนึ่งและเป็นหนึ่งในint doubleหากคุณพิจารณาประเภทของตัวเลขตามที่กำหนดแล้วความสำคัญของประเภทผลิตภัณฑ์นั้นในความเป็นจริงแล้วผลิตภัณฑ์ของความสำคัญของเขตข้อมูล

  • รวม ในภาษาเชิงโพรซีเดอร์นี่เป็นเรื่องที่น่าอึดอัดใจที่จะแสดงโดยตรง (คลาสสิกมันทำกับสหภาพที่ติดแท็ก ) ดังนั้นเพื่อความเข้าใจที่ดีขึ้นนี่คือผลรวมประเภทใน Haskell:

    data IntOrDouble = AnInt Int
                     | ADouble Double
    

    ค่าของประเภทนี้มีทั้งแบบฟอร์มAnInt 345หรือADouble 4.23แต่มีตัวเลขเดียวเท่านั้นที่เกี่ยวข้อง (ซึ่งแตกต่างจากประเภทผลิตภัณฑ์ที่แต่ละค่ามีตัวเลขสองตัว) ดังนั้นความสำคัญ: คุณต้องระบุIntค่าทั้งหมดก่อนโดยแต่ละค่าจะต้องรวมกับตัวAnIntสร้าง พลัสทุกค่าแต่ละรวมกับDouble ADoubleดังนั้นประเภทผลรวม

  • ยกกำลัง1 ฉันจะไม่พูดถึงรายละเอียดที่นี่เพราะไม่มีการโต้ตอบ OO ที่ชัดเจนเลย

แล้วคลาสล่ะล่ะ? ฉันจงใจใช้คำหลักstructมากกว่าสำหรับclass IntXDoubleสิ่งที่เป็นคลาสที่เป็นประเภทไม่ได้โดดเด่นด้วยเขตข้อมูลของพวกเขาเป็นเพียงรายละเอียดการใช้งาน ปัจจัยที่สำคัญค่อนข้างมีค่าที่แตกต่างสิ่งที่ชั้นสามารถมี

สิ่งที่เกี่ยวข้องคืออะไรค่าของคลาสสามารถเป็นค่าของคลาสย่อยใด ๆ ก็ได้ ! ดังนั้นระดับเป็นจริงชนิดรวมมากกว่าประเภทผลิตภัณฑ์: ถ้าAและBทั้งสองจะได้รับจากการmyClassที่myClassจะเป็นหลักเป็นผลรวมของและA Bโดยไม่คำนึงถึงการใช้งานจริง


1 นี่คือฟังก์ชั่น (ในแง่คณิตศาสตร์! ); ประเภทฟังก์ชั่นจะถูกแสดงโดยชี้แจงInt -> Double DoubleIntไปไม่ดีถ้าภาษาของคุณไม่มีฟังก์ชั่นที่เหมาะสม ...


2
ขออภัยฉันคิดว่านี่เป็นคำตอบที่แย่มาก ฟังก์ชั่นทำมีอะนาล็อกที่ชัดเจน OO คือวิธีการ (และวิธีการเดียวอินเตอร์เฟซชนิด) คำจำกัดความพื้นฐานของวัตถุคือมันมีทั้งสถานะ (เขตข้อมูล / สมาชิกข้อมูล) และพฤติกรรม (วิธีการ / ฟังก์ชั่นสมาชิก); คำตอบของคุณจะไม่สนใจหลัง
ruakh

@ruakh: ไม่ แน่นอนคุณสามารถใช้ฟังก์ชั่นใน OO แต่โดยทั่วไปแล้ววิธีการไม่ได้ทำงาน ( เพราะพวกเขาปรับเปลี่ยนสถานะ ฯลฯ ) หรือ "ฟังก์ชั่น" ในฟังก์ชั่นภาษาขั้นตอนสำหรับเรื่องนั้น ที่จริงแล้วอินเทอร์เฟซแบบวิธีเดียวคงมาใกล้กับฟังก์ชัน / เอ็กซ์โปเนนเชียลชนิด แต่ฉันหวังว่าจะหลีกเลี่ยงการอภิปรายเนื่องจากมันไม่มีความเกี่ยวข้องสำหรับคำถามนี้
leftaroundabout

... ที่สำคัญกว่า anwer ฉันไม่พิจารณาพฤติกรรม พฤติกรรมที่แท้จริงมักเป็นเหตุผลว่าทำไมคุณใช้การสืบทอดและการรวมกันของพฤติกรรมที่เป็นไปได้ที่แตกต่างกันได้อย่างแม่นยำจับภาพรวมประเภทของชั้นเรียน OO
leftaroundabout

@ruakh วิธีการไม่สามารถไม่มีวัตถุ อะนาล็อกที่ใกล้เคียงที่สุดคือstaticวิธีการยกเว้นพวกเขายังคงไม่ได้เป็นค่าชั้นหนึ่ง สิ่งที่เกี่ยวกับภาษา OO ส่วนใหญ่คือพวกมันใช้วัตถุเป็นหน่วยการสร้างที่เล็กที่สุดดังนั้นหากคุณต้องการอะไรที่เล็กกว่าคุณต้องปลอมมันด้วยวัตถุและคุณยังต้องลากฟังก์ชันความหมายจำนวนมากไปด้วย เช่นไม่เหมาะสมที่จะเปรียบเทียบฟังก์ชั่นเพื่อความเท่าเทียมกัน แต่คุณยังสามารถเปรียบเทียบสองฟังก์ชั่นมารยาทได้
Doval

@Doval 1) คุณสามารถส่งเมธอดรอบ ๆ AFAIK ได้ดังนั้นจึงเป็นค่าที่ดีที่สุด 2) มันสมเหตุสมผลที่จะเปรียบเทียบฟังก์ชั่นเพื่อความเท่าเทียมกันคน JS ทำมันตลอดเวลา
Den

2

ขออภัยที่ฉันไม่รู้เกี่ยวกับทฤษฎี "ดิบ" ฉันสามารถให้แนวทางปฏิบัติได้เท่านั้น ฉันหวังว่านี่เป็นที่ยอมรับของโปรแกรมเมอร์ ฉันไม่คุ้นเคยกับมารยาทที่นี่


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

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

    int areacode;
    int number;

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

การสร้างอินสแตนซ์อาจปฏิเสธตัวเลขติดลบใด ๆ บางทีการสร้างอาจจะทำให้ปกติของโหนดในบางวิธีหรือแม้กระทั่งการตรวจสอบจำนวนทั้งหมด คุณจึงจะจบลงมากใกล้กับคุณ"(a,b) where a is an int and b is a double"เพราะมันไม่แน่นอนใด ๆสอง int เก็บไว้ในชั้นเรียนที่

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

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


มีการออกแบบ patters เพื่อให้แน่ใจว่าการเปลี่ยนแปลงของการเป็นตัวแทนภายในสามารถทำได้โดยไม่ต้องลูกค้าได้ตระหนักถึงมัน (เช่น pimpl idiom ใน C ++ ซึ่งซ่อนสมาชิกข้อมูลใด ๆ ที่อยู่เบื้องหลังตัวชี้ทึบแสง )


1
It is neither the type of the data members, nor the range of their possible values that defines the class, it's the methods that are defined for it.สมาชิกข้อมูลไม่ได้กำหนดคลาสเฉพาะเมื่อคุณซ่อนพวกเขา อาจเป็นกรณีที่พบบ่อยที่สุด แต่ก็ไม่สามารถพูดได้ว่าเป็นเรื่องจริงสำหรับทุกชั้น ถ้าแม้แต่ฟิลด์เดียวเป็นแบบสาธารณะก็สำคัญพอ ๆ กับวิธีการของมัน
Doval

1
นอกจากว่าคุณกำลังเขียนโปรแกรมใน Java ที่คุณไม่มีทางเลือกในเรื่องนี้และแม้แต่การบันทึกมารยาทไร้มารยาทของคุณก็ต้องเป็นclasses (การทำเครื่องหมายพวกเขาfinalจะช่วยให้ได้จุดข้าม แต่ก็ยัง) คุณยังคงมีปัญหากับprotectedสมาชิกซึ่งสามารถสืบทอดและเป็นส่วนหนึ่งของ API ที่สองสำหรับผู้พัฒนาของคลาสย่อย
Doval

1
@Doval: ฉันเข้าใจว่านี่เป็นคำถามที่ "ทฤษฎี" ซึ่งเป็นเหตุผลที่ฉันอยู่ที่ชัดเจนของปัญหาภาษาที่แท้จริงที่เป็นไปได้ (เช่นเดียวกับฉันอยู่ที่ชัดเจนของชวาและprotectedเป็นไปได้ในทางปฏิบัติ ;-).)
DevSolar

3
ปัญหาคือว่าclassเป็นโครงสร้างที่ขึ้นอยู่กับภาษา เท่าที่ฉันรู้ไม่มีสิ่งเช่นclassทฤษฎีแบบ
Doval

1
@Doval: ไม่ว่าหมายถึงว่าทฤษฎีประเภทต่อ seไม่ได้นำไปใช้กับการเรียนที่พวกเขาจะสร้างออกจากขอบเขตของทฤษฎีที่?
DevSolar

2
  • ประเภทคือคำอธิบายของหมวดหมู่ / ช่วงของค่าโครงสร้างสารหรือสิ่งที่มีคุณ OOPwise มันคล้ายกับ "อินเตอร์เฟส" (ในความหมายภาษาไม่เชื่อเรื่องพระเจ้า. ความรู้สึกเฉพาะภาษาไม่มาก. ใน Java เช่นintเป็นชนิดแต่มีความสัมพันธ์กับผู้ไม่มีinterface. สาธารณะ / รายละเอียดข้อมูลที่มีการป้องกันเป็นอย่างดีไม่ได้เป็นส่วนหนึ่งของการinterface, แต่เป็นส่วนหนึ่งของ "อินเตอร์เฟส" หรือประเภท )

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

  • ระดับเป็นสำนึกของประเภทที่ เป็นแม่แบบที่กำหนดโครงสร้างภายในพฤติกรรมที่แนบมา ฯลฯ

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