คลาสยูทิลิตี้ที่ไม่มีอะไรนอกจากสมาชิกแบบคงที่เป็นรูปแบบการต่อต้านใน C ++ หรือไม่?


39

คำถามที่ฉันควรใส่ฟังก์ชั่นที่ไม่เกี่ยวข้องกับคลาสได้ก่อให้เกิดการถกเถียงกันว่ามันสมเหตุสมผลหรือไม่ใน C ++ ที่จะรวมฟังก์ชั่นยูทิลิตี้ในชั้นเรียน

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

สิ่งต่าง ๆ จะแตกต่างกันหรือไม่หากการรวบรวมฟังก์ชั่นยูทิลิตี้ต้องการข้อมูลที่ใช้ร่วมกันเช่นแคชที่สามารถเก็บไว้ในเขตข้อมูลส่วนตัวคงที่


ฟังดูคล้ายกับ antipattern "การสลายตัวที่ใช้งานได้"
281377

7
คำตอบสั้น ๆ : คุณเพียงแค่ไม่ต้องเรียนเพื่อห่อฟังก์ชันเหล่านี้ ฟังก์ชั่นฟรีเหมาะสำหรับงานของคุณมากกว่าการบีบอัดมันลงในโครงสร้างหลอกแบบ OO ซึ่งเป็นวิธีแก้ปัญหาที่คุณต้องการเฉพาะในภาษา "หมดจด -OO"
คริสพูดว่า Reinstate Monica

คำตอบ:


39

ฉันเดาที่จะตอบว่าเราควรเปรียบเทียบความตั้งใจของทั้งคลาสและเนมสเปซ ตามที่ Wikipedia:

ชั้น

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

namespace

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

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

อาจมีเหตุผลทางเทคนิคเช่นกัน แต่ในขณะที่บางคนมาจาก Java และพยายามที่จะเข้าใจภาษาหลายกระบวนทัศน์นั่นคือ C ++ คำตอบที่ชัดเจนที่สุดสำหรับฉันก็คือ: เพราะเราไม่ต้องการ OO เพื่อให้บรรลุสิ่งนี้


1
+1 สำหรับ "เราไม่ต้องการ OO เพื่อให้บรรลุเป้าหมายนี้"
Ixrec

ฉันเห็นด้วยกับสิ่งนี้ในฐานะที่ไม่เป็นแฟนของ OO แต่ฉันเดาว่ามีบางสถานการณ์ที่การใช้คลาสแบบคงที่ทั้งหมดเป็นทางออกเดียวโดยยกตัวอย่างการแทนที่เนมสเปซเนื่องจากคุณไม่สามารถประกาศเนมสเปซซ้อนภายใน ห้องเรียน.
Wael Boutglay

26

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


3
คำตอบอื่น ๆ ได้คัดสรรในบรรทัดสุดท้ายของ OP Namespaces เป็นขอบเขตที่มีชื่อค่อนข้างมากดังนั้นหากคุณต้องการการควบคุมการเข้าถึงคลาสเป็นเครื่องมือเดียวที่มีให้
cmannett85

2
@ cbamber85 เป็นธรรมฉันวางบรรทัดนั้นหลังจากอ่านคำตอบสองคำแรกเท่านั้น
PersonalNexus

@ ส่วนบุคคล Nexus พอยุติธรรมฉันไม่ได้ตระหนักถึง!
cmannett85

10
@ cbamber85: นั่นไม่จริงทั้งหมด คุณจะได้รับการห่อหุ้มที่ดียิ่งขึ้นโดยการใส่ฟังก์ชั่น "ส่วนตัว" ในไฟล์การนำไปใช้ดังนั้นผู้ใช้จะไม่ได้เห็นแม้แต่การประกาศส่วนตัว
UncleBens

2
+1 เทมเพลตเป็นจุดที่เนมสเปซยังขาดคุณสมบัติ
Chris บอกว่า Reinstate Monica

13

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

มีปัญหาคล้ายกันในการเล่นที่นี่: การใช้คลาสคงที่เพื่อโฮสต์ฟังก์ชันยูทิลิตี้ใน C ++ นั้นเป็น idiome ต่างประเทศถึง C ++

ดังที่คุณกล่าวถึงในคำถามของคุณการใช้คลาสแบบคงที่สำหรับฟังก์ชั่นยูทิลิตี้ใน C # นั้นเป็นเรื่องจำเป็น: ฟังก์ชั่นแบบยืนฟรีนั้นไม่ใช่ตัวเลือกใน C # ภาษาที่จำเป็นในการพัฒนารูปแบบสำหรับการอนุญาตให้โปรแกรมเมอร์กำหนดฟังก์ชั่นที่ยืนอยู่ด้วยวิธีอื่น - กล่าวคือภายในคลาสยูทิลิตี้แบบคงที่ นี่เป็นกลอุบายที่ใช้คำต่อคำของ Java: ตัวอย่างเช่นjava.lang.MathและSystem.Math of .NET เกือบจะเป็นแบบ isomorphic

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

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


6
+1 ยกเว้นการแนะนำซิงเกิล พวกเขาไม่เป็นที่ต้องการใน C ++! ฟังก์ชั่นอาจอยู่ในชั้นเรียนหากพวกเขาต้องการแบ่งปันสถานะ แต่คลาสไม่ควรเป็นแบบซิงเกิลเว้นแต่ว่าพวกเขาต้องการจริงๆ และพวกเขามักจะไม่
Maxpm

@ Maxpm อย่าเข้าใจฉันผิด Singleton เป็นที่ต้องการเฉพาะกับตัวแปรสแตติกทั่วโลกเท่านั้น นอกเหนือจากนั้นไม่มีอะไรที่ "ชอบ" เกี่ยวกับเรื่องนั้น
dasblinkenlight

2
มีบทความดีดีนี้คือการแก้ปัญหาของคุณไม่ทราบคุณไม่เคยมีตั้งแต่ปี 1995: Singletons สรุปมันบอกว่าการเชื่อมต่ออินสแตนซ์ที่รู้จักกันดีในชั้นเรียนของตัวเองโดยไม่จำเป็น จำกัด ความยืดหยุ่นของคุณและทำให้คุณไม่มีอะไร เวลาส่วนใหญ่มีเพียงชั้นเรียนและมีอินสแตนซ์ที่ใช้ร่วมกันอยู่ที่ไหนสักแห่ง แต่อย่าทำให้มันเป็นโสด
Jan Hudec

ฉันไม่แน่ใจว่า "สำนวนต่างประเทศ" เป็นคำที่ถูกต้อง มันเป็นสำนวนที่พบบ่อยมาก ผมคิดว่าค่อนข้างไม่กี่ทีมเขียนโปรแกรมใช้ E2 ของ Stroustrup ...
นิคไคลีย์

10

บางทีคุณต้องถามว่าทำไมคุณถึงต้องการคลาสที่ไม่เปลี่ยนแปลง?

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

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

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


10
แน่นอน - ฉันจะบอกว่า "ชั้นเรียนที่มีสมาชิกคงที่" ใน Java เป็นจริงแฮ็คที่จะได้รับข้อ จำกัด ของ Java
Charles Salvia

@CharlesSalvia: ฉันเป็นคนสุภาพ :) ฉันมีความรู้สึกไม่ดีเหมือนกันเกี่ยวกับ main () เป็นส่วนหนึ่งของคลาส java ด้วย แต่ฉันเข้าใจว่าทำไมพวกเขาถึงทำเช่นนั้น
gbjbaanb

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

5

อย่างน้อยบนพื้นผิววิธีการคงที่ในชั้นเรียนดูเหมือนจะแยกไม่ออกจากฟังก์ชั่นฟรีใน namespace

กล่าวอีกนัยหนึ่งการมีคลาสแทนเนมสเปซไม่มีข้อดี

ทำไมจึงเป็นความพึงพอใจของคนหลัง

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

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

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


5

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

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

สิ่งต่าง ๆ จะแตกต่างกันหรือไม่หากการรวบรวมฟังก์ชั่นยูทิลิตี้ต้องการข้อมูลที่ใช้ร่วมกันเช่นแคชที่สามารถเก็บไว้ในเขตข้อมูลส่วนตัวคงที่

ไม่ได้จริงๆ จุดประสงค์ดั้งเดิมของstaticใน C (และ C ++ ต่อมา) คือการนำเสนอที่เก็บข้อมูลถาวรที่สามารถใช้งานได้ในทุกระดับของขอบเขต:

int counter() {
    static int value = 0;
    value++;
}

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

static int value = 0;

int up() { value++; }
int down() { value--; }

สมาชิกคลาสสแตติกส่วนตัวใน C ++ ทำหน้าที่จุดประสงค์เดียวกัน แต่ถูก จำกัด ขอบเขตของคลาส เทคนิคที่ใช้ในcounter()ตัวอย่างข้างต้นยังใช้งานได้ในวิธีการ C ++ และเป็นสิ่งที่ฉันขอแนะนำให้ทำหากตัวแปรไม่จำเป็นต้องปรากฏให้เห็นในชั้นเรียนทั้งหมด


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

หากพวกเขาแบ่งปันข้อมูลพวกเขาอาจจะดีกว่าในชั้นเรียนที่ไม่มีวิธีการคงที่?
Nick Keighley

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

1

หากฟังก์ชั่นไม่มีการรักษาสถานะและเป็น reentrant ดูเหมือนจะไม่ได้มีจุดมากในการผลักมันในชั้นเรียน (ยกเว้นกรณีที่ถูกบังคับโดยภาษา) หากฟังก์ชันรักษาสถานะบางอย่าง (เช่นสามารถทำเธรดที่ปลอดภัยได้โดยการใช้ mutex แบบคงที่) ดังนั้นเมธอดคงที่ในคลาสจะเหมาะสม


1

มากของวาทกรรมในหัวข้อที่นี่ทำให้รู้สึกว่ามีบางสิ่งที่พื้นฐานมากเกี่ยวกับ C ++ ที่ทำให้namespacesและclassES / structs แตกต่างกันมาก

คลาสแบบสแตติก (คลาสที่สมาชิกทั้งหมดเป็นแบบสแตติกและคลาสจะไม่มีอินสแตนซ์) เป็นวัตถุเอง มันไม่ได้เป็นเพียงแค่การnamespaceบรรจุฟังก์ชั่น

แม่แบบการเขียนโปรแกรมเมตาช่วยให้เราสามารถใช้คลาสคงที่เป็นวัตถุเวลารวบรวม

พิจารณาสิ่งนี้:

template<typename allocator_type> class allocator
{
public:
    inline static void* allocate(size_t size)
    {
        return allocator_type::template allocate(size);
    }
    inline static void release(void* p)
    {
        allocator_type::template release(p);
    }
};

ในการใช้งานเราต้องการฟังก์ชั่นที่บรรจุอยู่ในคลาส เนมสเปซจะไม่ทำงานที่นี่ พิจารณา:

class mallocator
{
    inline static void* allocate(size_t size)
    {
        return std::malloc(size);
    }
    inline static void release(void* p)
    {
        return std::free(p);
    }
};

ตอนนี้ที่จะใช้มัน:

using my_allocator = allocator<mallocator>;

void* p = my_allocator::allocate(1024);
...
my_allocator::release(p);

ตราบใดที่ตัวจัดสรรใหม่แสดงallocateและreleaseฟังก์ชันที่เข้ากันได้การสลับไปยังตัวจัดสรรใหม่นั้นเป็นเรื่องง่าย

สิ่งนี้ไม่สามารถทำได้ด้วยเนมสเปซ

คุณต้องการฟังก์ชั่นเพื่อเป็นส่วนหนึ่งของชั้นเรียนหรือไม่? เลขที่

การใช้คลาส anti-pattern คงที่ใช่หรือไม่? มันขึ้นอยู่กับบริบท

สิ่งต่าง ๆ จะแตกต่างกันหรือไม่หากการรวบรวมฟังก์ชั่นยูทิลิตี้ต้องการข้อมูลที่ใช้ร่วมกันเช่นแคชที่สามารถเก็บไว้ในเขตข้อมูลส่วนตัวคงที่

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


การใช้คำหลักแบบอินไลน์ซ้ำซ้อนที่นี่เนื่องจากวิธีการที่กำหนดไว้ในคำจำกัดความของชั้นเรียนมีการ inline โดยนัย ดูen.cppreference.com/w/cpp/language/inline
เทรเวอร์

1

ดังที่ John Carmack โด่งดังกล่าวว่า

"บางครั้งการใช้งานที่สวยงามเป็นเพียงฟังก์ชั่นไม่ใช่วิธีไม่ใช่คลาสไม่ใช่เฟรมเวิร์กเพียงแค่ฟังก์ชั่น"

ฉันคิดว่ามันน่าจะสรุปได้ ทำไมคุณถึงทำให้ชั้นเรียนมีพลังถ้าไม่ใช่ชั้นเรียนอย่างชัดเจน? C ++ มีความหรูหราที่คุณสามารถใช้ฟังก์ชั่นได้; ใน Java ทุกอย่างเป็นวิธี จากนั้นคุณต้องมีคลาสยูทิลิตี้และอีกมากมาย

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