ใช้คลาสเดี่ยวด้วยวิธีที่ดีที่สุด?


172

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

// Initialize arguments in constructor
MyClass myObject = new MyClass(arg1, arg2, arg3);
myObject.myMethod();

// Pass arguments to method
MyClass myObject = new MyClass();
myObject.myMethod(arg1, arg2, arg3);

// Pass arguments to static method
MyClass.myMethod(arg1, arg2, arg3);

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

คำตอบ:


264

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

ความแตกต่าง
สมมติว่าเรามีวิธี UtilityClass.SomeMethod ที่มีความสุขฉวัดเฉวียน ทันใดนั้นเราจำเป็นต้องเปลี่ยนฟังก์ชั่นเล็กน้อย ฟังก์ชั่นส่วนใหญ่เหมือนกัน แต่เราต้องเปลี่ยนสองส่วนอย่างไรก็ตาม หากไม่ใช่วิธีการคงที่เราสามารถสร้างคลาส derivate และเปลี่ยนเนื้อหาวิธีการได้ตามต้องการ เนื่องจากเป็นวิธีการคงที่เราจึงไม่สามารถทำได้ แน่นอนว่าถ้าเราต้องการเพิ่มฟังก์ชันการทำงานก่อนหรือหลังวิธีเก่าเราสามารถสร้างคลาสใหม่และเรียกคลาสเก่าที่อยู่ในนั้น - แต่นั่นก็แค่ขั้นต้น

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

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

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

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

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

เฉพาะ Sith เท่านั้นที่เกี่ยวข้องกับสัมบูรณ์
แน่นอนว่ามีข้อยกเว้นที่ไม่ชอบวิธีการคงที่ของฉัน คลาสยูทิลิตี True ที่ไม่ก่อให้เกิดความเสี่ยงใด ๆ ต่อการขยายตัวเป็นกรณีที่ยอดเยี่ยมสำหรับวิธีการคงที่ - System.Convert เป็นตัวอย่าง หากโครงการของคุณเป็นแบบครั้งเดียวโดยไม่มีข้อกำหนดสำหรับการบำรุงรักษาในอนาคตสถาปัตยกรรมโดยรวมนั้นไม่สำคัญมาก - คงที่หรือไม่คงที่ไม่สำคัญเลย - ความเร็วในการพัฒนาจะเป็นอย่างไร

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


เป็นเครื่องหมายบอกความแตกต่างความสามารถในการส่งผ่านวิธีการที่เป็น 'กลยุทธ์' พารามิเตอร์และความสามารถในการกำหนดค่า / ตั้งค่าตัวเลือกที่มีเหตุผลทั้งหมดที่จะใช้อินสแตนซ์ ตัวอย่างเช่น: ListDelimiter หรือ DelimiterParser สามารถกำหนดค่าเป็นตัวคั่นที่จะใช้ / ยอมรับไม่ว่าจะตัดช่องว่างจากโทเค็นการแยกวิเคราะห์ไม่ว่าจะวงเล็บรายการวิธีการรักษารายการว่าง / null ..
Thomas W

4
"ในขณะที่ระบบเติบโต ... " คุณ refactor?
Rodney Gitzel

3
@ user3667089 อาร์กิวเมนต์ทั่วไปที่จำเป็นสำหรับคลาสที่จะมีอยู่ในเชิงตรรกะเหล่านั้นที่ฉันจะผ่านในตัวสร้าง โดยทั่วไปจะใช้วิธีเหล่านี้ในวิธีส่วนใหญ่ / ทั้งหมด ข้อโต้แย้งเฉพาะวิธีฉันจะผ่านในวิธีการเฉพาะที่
Mark S. Rasmussen

1
สิ่งที่คุณทำกับคลาสแบบสแตติกจะกลับไปเป็น C ที่ไม่ใช่เชิงวัตถุ (แม้ว่าจัดการหน่วยความจำ) - ฟังก์ชันทั้งหมดอยู่ในพื้นที่ส่วนกลางไม่มีthisคุณต้องจัดการสถานะของโลกเป็นต้น ถ้าคุณชอบการเขียนโปรแกรมตามขั้นตอนให้ทำเช่นนี้ แต่ขอขอบคุณที่คุณสูญเสียผลประโยชน์เชิงโครงสร้างจำนวนมากของ OO
วิศวกร

1
สาเหตุส่วนใหญ่เกี่ยวข้องกับการจัดการรหัสที่ไม่ดีไม่ใช่จากการใช้วิธีการคงที่ ใน F # และภาษาการทำงานอื่น ๆ เราใช้วิธีการแบบสแตติก (ฟังก์ชั่นที่ไม่มีอินสแตนซ์) ในทุกที่และไม่มีปัญหาเหล่านี้ ที่กล่าวว่า F # ให้กระบวนทัศน์ที่ดีกว่าสำหรับการใช้ฟังก์ชั่น (ฟังก์ชั่นชั้นหนึ่งฟังก์ชั่นสามารถ curried และนำไปใช้บางส่วน) ซึ่งทำให้พวกเขาเป็นไปได้มากขึ้นกว่าใน C # เหตุผลที่ใหญ่กว่าในการใช้คลาสเพราะนั่นคือสิ่งที่ C # สร้างขึ้น โครงสร้างการพึ่งพาการฉีดทั้งหมดใน. NET Core จะหมุนรอบคลาสอินสแตนซ์ที่มี deps
Justin J Stark

89

ฉันชอบแบบคงที่ เนื่องจากคลาสไม่ได้แสดงถึงวัตถุจึงไม่สมเหตุสมผลที่จะสร้างอินสแตนซ์ของมัน

คลาสที่มีอยู่สำหรับวิธีการของพวกเขาควรจะคงที่


19
-1 "คลาสที่มีอยู่สำหรับวิธีการของพวกเขาควรจะคงที่"
Rookian

8
@Rookian ทำไมคุณไม่เห็นด้วยกับที่?
jjnguy

6
ตัวอย่างจะเป็นรูปแบบที่เก็บ ชั้นมีวิธีการเท่านั้น การทำตามวิธีการของคุณไม่อนุญาตให้ใช้อินเทอร์เฟซ => เพิ่มการแต่งงาน
Rookian

1
@ โกงในคนทั่วไปไม่ควรสร้างชั้นเรียนที่ใช้สำหรับวิธีการ ในตัวอย่างที่คุณให้วิธีการคงที่ไม่ใช่ความคิดที่ดี แต่สำหรับวิธีการยูทิลิตี้อย่างง่ายวิธีคงที่ดีที่สุด
jjnguy

9
ถูกต้องอย่างแน่นอน :) ด้วย conditon ของคุณ "สำหรับวิธีการอรรถประโยชน์ง่าย ๆ " ฉันเห็นด้วยกับคุณอย่างสมบูรณ์ แต่คุณได้ทำกฎทั่วไปในคำตอบของคุณซึ่งเป็น IMO ผิด
Rookian

18

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


16

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

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


6

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


5

ฉันไม่รู้จริง ๆ ว่าสถานการณ์อยู่ที่นี่ แต่ฉันจะดูว่ามันเป็นวิธีการในหนึ่งในชั้นเรียนที่ arg1, arg2 หรือ arg3 เป็นของ - ถ้าคุณสามารถพูดได้ว่าความหมายว่าหนึ่งในชั้นเรียนเหล่านั้นจะเป็นเจ้าของ วิธี.


4

ฉันขอแนะนำว่ามันตอบยากตามข้อมูลที่ให้ไว้

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

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

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


3

ชั้นเรียนของคุณสามารถทำให้คงที่ได้หรือไม่?

ถ้าเป็นเช่นนั้นฉันจะทำให้เป็นคลาส 'ยูทิลิตี้' ที่ฉันจะใส่คลาสฟังก์ชั่นทั้งหมดของฉันมา


2
ฉันค่อนข้างไม่เห็นด้วย ในโครงการที่ฉันเกี่ยวข้องชั้นสาธารณูปโภคของคุณอาจมีวิธีการที่ไม่เกี่ยวข้องหลายร้อยวิธี
Paul Tomblin

3
@ พอล: ตกลงกันโดยทั่วไป ฉันเกลียดที่จะเห็นชั้นเรียนที่มีวิธีการที่ไม่เกี่ยวข้องโดยสิ้นเชิง (หรือใช่หลายร้อย) หากต้องใช้วิธีการนี้อย่างน้อยก็แยกเป็นชุดเล็ก ๆ ที่เกี่ยวข้อง (EG, FooUtilities, BarUtilities, ฯลฯ )
John Rudy

9
หนึ่งระดับหนึ่งการตอบสนอง
Andreas Petersson

3

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


3

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

  • บริการ
    • กำหนดโดยI[ServiceName]Serviceอินเตอร์เฟส
    • ส่งออกและนำเข้าตามประเภทอินเตอร์เฟส
    • การนำไปใช้ครั้งเดียวนั้นจัดทำโดยแอปพลิเคชันโฮสต์และใช้ภายในและ / หรือโดยส่วนขยาย
    • วิธีการบนส่วนต่อประสานการให้บริการนั้นปลอดภัยสำหรับเธรด

เป็นตัวอย่างที่วางแผนไว้:

public interface ISettingsService
{
    string ReadSetting(string name);

    void WriteSetting(string name, string value);
}

[Export]
public class ObjectRequiringSettings
{
    [Import]
    private ISettingsService SettingsService
    {
        get;
        set;
    }

    private void Foo()
    {
        if (SettingsService.ReadSetting("PerformFooAction") == bool.TrueString)
        {
            // whatever
        }
    }
}

2

ฉันจะทำทุกอย่างในตัวสร้าง ชอบมาก:

new MyClass(arg1, arg2, arg3);// the constructor does everything.

หรือ

MyClass my_object(arg1, arg2, arg3);

ถ้าคุณต้องการส่งคืนสิ่งใดนอกจากวัตถุประเภท MyClass
Bill the Lizard

13
ฉันคิดว่ามันเป็นการปฏิบัติที่ไม่ดีที่จะวางตรรกะการดำเนินการในตัวสร้าง การพัฒนาที่ดีเป็นเรื่องเกี่ยวกับความหมาย ความหมายคอนสตรัคเตอร์มีอยู่เพื่อสร้างวัตถุไม่ใช่เพื่อทำงานอื่น ๆ
mstrobl

การเรียกเก็บเงินถ้าคุณต้องการค่าตอบแทนส่งผ่านการอ้างอิงถึง vvalue ret: MyClass myObject (arg1, arg2, arg3, retvalue); mstrobl ถ้าไม่ต้องการวัตถุทำไมต้องสร้างมัน และเคล็ดลับนี้สามารถช่วยคุณได้จริงในบางกรณี

หากวัตถุไม่มีสถานะเช่นไม่มี vars การใส่วัตถุไว้จะไม่ทำให้เกิดการจัดสรรใด ๆ - ไม่ว่าจะเป็น heap หรือ stack คุณสามารถนึกถึงวัตถุใน C ++ เป็นเพียงการเรียกใช้ฟังก์ชัน C โดยมีพารามิเตอร์ตัวแรกซ่อนอยู่ซึ่งชี้ไปที่ struct
mstrobl

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

0

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

คุณควรใส่ใจกับสถานะของระบบ


0

คุณอาจหลีกเลี่ยงสถานการณ์ทั้งหมดเข้าด้วยกัน พยายามที่จะ refactor arg1.myMethod1(arg2, arg3)เพื่อที่คุณจะได้รับ สลับ arg1 ด้วย arg2 หรือ arg3 อย่างใดอย่างหนึ่งหากเหมาะสมกว่า

หากคุณไม่สามารถควบคุมคลาสของ arg1 ให้ตกแต่ง:

class Arg1Decorator
    private final T1 arg1;
    public Arg1Decorator(T1 arg1) {
        this.arg1 = arg1;
    }
    public T myMethod(T2 arg2, T3 arg3) {
        ...
    }
 }

 arg1d = new Arg1Decorator(arg1)
 arg1d.myMethod(arg2, arg3)

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


0

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


0

ขึ้นอยู่กับว่าคุณต้องการทำอะไรหรือทำและส่งคืนสิ่งที่คุณทำได้:

public abstract class DoSomethingClass<T>
{
    protected abstract void doSomething(T arg1, T arg2, T arg3);
}

public abstract class ReturnSomethingClass<T, V>
{
    public T value;
    protected abstract void returnSomething(V arg1, V arg2, V arg3);
}

public class DoSomethingInt extends DoSomethingClass<Integer>
{
    public DoSomethingInt(int arg1, int arg2, int arg3)
    {
        doSomething(arg1, arg2, arg3);
    }

    @Override
    protected void doSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        // ...
    }
}

public class ReturnSomethingString extends ReturnSomethingClass<String, Integer>
{
    public ReturnSomethingString(int arg1, int arg2, int arg3)
    {
        returnSomething(arg1, arg2, arg3);
    }

    @Override
    protected void returnSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        String retValue;
        // ...
        value = retValue;
    }
}

public class MainClass
{
    static void main(String[] args)
    {
        int a = 3, b = 4, c = 5;

        Object dummy = new DoSomethingInt(a,b,c);  // doSomething was called, dummy is still around though
        String myReturn = (new ReturnSomethingString(a,b,c)).value; // returnSomething was called and immediately destroyed
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.