ฟังก์ชันตัวสร้างเทียบกับฟังก์ชันของโรงงาน


150

ใครสามารถอธิบายความแตกต่างระหว่างฟังก์ชั่นการสร้างและฟังก์ชั่นโรงงานใน Javascript

เมื่อใดจึงควรใช้อันใดอันหนึ่งแทนอันอื่น?

คำตอบ:


149

ความแตกต่างพื้นฐานคือฟังก์ชั่นคอนสตรัคเตอร์ใช้กับnewคำหลัก (ซึ่งทำให้ JavaScript สร้างวัตถุใหม่โดยอัตโนมัติตั้งค่าthisภายในฟังก์ชั่นให้กับวัตถุนั้นและส่งคืนวัตถุ):

var objFromConstructor = new ConstructorFunction();

ฟังก์ชั่นจากโรงงานเรียกว่าฟังก์ชั่น "ปกติ":

var objFromFactory = factoryFunction();

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

ในตัวอย่างง่าย ๆ ฟังก์ชันที่อ้างถึงข้างต้นอาจมีลักษณะดังนี้:

function ConstructorFunction() {
   this.someProp1 = "1";
   this.someProp2 = "2";
}
ConstructorFunction.prototype.someMethod = function() { /* whatever */ };

function factoryFunction() {
   var obj = {
      someProp1 : "1",
      someProp2 : "2",
      someMethod: function() { /* whatever */ }
   };
   // other code to manipulate obj in some way here
   return obj;
}

แน่นอนว่าคุณสามารถทำให้ฟังก์ชั่นของโรงงานมีความซับซ้อนได้มากกว่าตัวอย่างง่ายๆ

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


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

6
ฉันต้องการชี้ให้เห็นว่าตัวอย่างสำหรับทั้งสองกรณี (ฟังก์ชั่นคอนสตรัคเตอร์กับฟังก์ชั่นจากโรงงาน) ควรจะสอดคล้องกัน ตัวอย่างสำหรับฟังก์ชั่นจากโรงงานไม่รวมsomeMethodสำหรับวัตถุที่ส่งคืนโดยโรงงานและนั่นคือสิ่งที่มันได้รับหมอกเล็กน้อย ภายในฟังก์ชั่นจากโรงงานหากมีใครทำvar obj = { ... , someMethod: function() {}, ... }นั่นจะนำไปสู่วัตถุแต่ละชิ้นที่ถูกส่งคืนถือสำเนาที่แตกต่างกันsomeMethodซึ่งเป็นสิ่งที่เราอาจไม่ต้องการ นั่นคือที่ที่การใช้งานnewและprototypeภายในฟังก์ชั่นจากโรงงานจะช่วยได้
Bharat Khatri

3
ดังที่คุณได้กล่าวไปแล้วว่าบางคนพยายามที่จะใช้ฟังก์ชั่นจากโรงงานเพียงเพราะพวกเขาไม่ได้ตั้งใจที่จะปล่อยให้บั๊กที่ซึ่งผู้คนลืมที่จะใช้newกับฟังก์ชั่นคอนสตรัคเตอร์; ฉันคิดว่ามันเป็นสิ่งที่ใคร ๆ จะต้องดูวิธีเปลี่ยน constructors ด้วยตัวอย่างฟังก์ชั่นจากโรงงานและนั่นคือสิ่งที่ฉันคิดว่าจำเป็นต้องมีความสอดคล้องในตัวอย่าง อย่างไรก็ตามคำตอบคือให้ข้อมูลเพียงพอ นี่เป็นเพียงจุดที่ฉันต้องการยกระดับไม่ใช่ว่าฉันจะดึงคุณภาพของคำตอบลงไปในทางใดทางหนึ่ง
Bharat Khatri

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

1
@Federico - วิธีการของโรงงานไม่จำเป็นต้องส่งคืนวัตถุธรรมดา พวกเขาสามารถใช้newภายในหรือใช้Object.create()เพื่อสร้างวัตถุที่มีต้นแบบที่เฉพาะเจาะจง
nnnnnn

110

ประโยชน์ของการใช้คอนสตรัคเตอร์

  • หนังสือส่วนใหญ่สอนให้คุณใช้ตัวสร้างและ new

  • this หมายถึงวัตถุใหม่

  • บางคนชอบvar myFoo = new Foo();อ่านวิธี

ข้อเสีย

  • รายละเอียดของการสร้างอินสแตนซ์รับการรั่วไหลใน API การโทร (ผ่านnewข้อกำหนด) ดังนั้นผู้โทรทั้งหมดจะถูกผนวกเข้ากับการติดตั้ง Constructor หากคุณต้องการความยืดหยุ่นเพิ่มเติมของโรงงานคุณจะต้องปรับโครงสร้างผู้โทรทุกคน (ยอมรับกรณีพิเศษมากกว่ากฎ)

  • การลืมnewเป็นข้อผิดพลาดทั่วไปคุณควรพิจารณาเพิ่มการตรวจสอบสำเร็จรูปเพื่อให้แน่ใจว่าตัวสร้างถูกเรียกอย่างถูกต้อง ( if (!(this instanceof Foo)) { return new Foo() }) แก้ไข: ตั้งแต่ ES6 (ES2015) คุณไม่สามารถลืมnewด้วยclassนวกรรมิกมิฉะนั้นผู้สร้างจะโยนข้อผิดพลาด

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

คอนสตรัคเตอร์ทำลายหลักการเปิด / ปิด

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

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

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

ประโยชน์ของการใช้โรงงาน

  • รหัสน้อย - ไม่จำเป็นต้องมีสำเร็จรูป

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

  • คุณไม่จำเป็นต้องแปลงจากโรงงานเป็นคอนสตรัคเตอร์ดังนั้นการปรับโครงสร้างจะไม่เป็นปัญหาอีกต่อไป

  • newไม่มีความคลุมเครือเกี่ยวกับการใช้ อย่า (มันจะทำให้thisพฤติกรรมไม่ดีดูจุดต่อไป)

  • thisพฤติกรรมที่มันปกติ - เพื่อให้คุณสามารถใช้มันในการเข้าถึงวัตถุแม่ (ตัวอย่างเช่นภายในplayer.create(), thisหมายถึงplayer. เช่นเดียวกับวิธีการอุทธรณ์อื่น ๆ จะcallและapplyยังมอบหมายthisเป็นไปตามคาดหากคุณเก็บต้นแบบในวัตถุแม่ที่. สามารถเป็นวิธีที่ดีในการสลับการทำงานแบบไดนามิกและเปิดใช้งานความหลากหลายที่หลากหลายสำหรับการสร้างอินสแตนซ์วัตถุของคุณ

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

  • บางคนชอบวิธีvar myFoo = foo();หรือvar myFoo = foo.create();อ่าน

ข้อเสีย

  • newไม่ทำงานตามที่คาดไว้ (ดูด้านบน) วิธีแก้ปัญหา: อย่าใช้มัน

  • thisไม่ได้อ้างถึงออบเจ็กต์ใหม่ (แทนหากตัวสร้างถูกเรียกด้วยเครื่องหมายจุดหรือเครื่องหมายวงเล็บเหลี่ยมเช่น foo.bar () - thisหมายถึงfoo- เหมือนกับวิธี JavaScript อื่น ๆ - ดูประโยชน์)


2
คุณหมายถึงอะไรที่ผู้สร้างทำให้ผู้โทรเข้าคู่กับการใช้งานของพวกเขาอย่างแน่นหนา? เท่าที่มีการโต้แย้งการก่อสร้างพวกนั้นจะต้องถูกส่งผ่านไปยังโรงงานเพื่อใช้มันและเรียกใช้ตัวสร้างที่เหมาะสมภายใน
Bharat Khatri

4
เกี่ยวกับการละเมิดการเปิด / ปิด: ไม่ได้ทั้งหมดนี้เกี่ยวกับการฉีดพึ่งพา? ถ้า A ต้องการ B, A อาจจะเรียก A ใหม่ B () หรือ A เรียก BFactory.create () ทั้งคู่แนะนำการมีเพศสัมพันธ์ หากในอีกทางหนึ่งคุณให้ A เป็นตัวอย่างของ B ในรูทองค์ประกอบ A ไม่จำเป็นต้องรู้อะไรเลยเกี่ยวกับวิธีการสร้างอินสแตนซ์ B ฉันรู้สึกว่าทั้งผู้สร้างและโรงงานมีประโยชน์ คอนสตรัคเตอร์ใช้สำหรับการสร้างอินสแตนซ์ที่เรียบง่ายโรงงานสำหรับอินสแตนซ์ที่ซับซ้อนมากขึ้น แต่ในทั้งสองกรณีการฉีดการพึ่งพาของคุณนั้นฉลาด
Stefan Billiet

1
DI นั้นดีสำหรับสถานะการฉีด: การกำหนดค่า, วัตถุโดเมนและอื่น ๆ มันเกินความจริงสำหรับทุกอย่าง
Eric Elliott

1
เอะอะคือการที่ต้องnewละเมิดหลักการเปิด / ปิด ดูmedium.com/javascript-scene/…สำหรับการสนทนาที่ใหญ่กว่าความคิดเห็นเหล่านี้อนุญาต
Eric Elliott

3
เนื่องจากฟังก์ชั่นใด ๆ สามารถส่งคืนออบเจ็กต์ใหม่ใน JavaScript และมีฟังก์ชั่นจำนวนมากทำโดยไม่มีnewคำหลักฉันไม่เชื่อว่าnewคำหลักนั้นมีความสามารถในการอ่านเพิ่มเติม IMO ดูเหมือนว่าโง่ที่จะกระโดดผ่านห่วงเพื่อให้ผู้โทรพิมพ์มากขึ้น
Eric Elliott

39

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


5

ตัวอย่างฟังก์ชันตัวสร้าง

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");
  • newสร้างต้นแบบของวัตถุUser.prototypeและเรียกใช้Userกับวัตถุที่สร้างขึ้นเป็นthisค่าของมัน

  • new ถือว่านิพจน์อาร์กิวเมนต์สำหรับตัวถูกดำเนินการเป็นตัวเลือก:

         let user = new User;

    จะทำให้newการโทรUserโดยไม่มีข้อโต้แย้ง

  • newส่งคืนวัตถุที่สร้างขึ้นเว้นแต่ว่าตัวสร้างส่งกลับค่าวัตถุซึ่งจะถูกส่งกลับแทน นี่คือกรณีขอบซึ่งส่วนใหญ่สามารถละเว้น

ข้อดีและข้อเสีย

วัตถุที่สร้างโดยฟังก์ชั่นตัวสร้างสืบทอดคุณสมบัติจากคุณสมบัติของตัวสร้างprototypeและส่งคืนค่าจริงโดยใช้ตัวinstanceOfดำเนินการบนฟังก์ชันตัวสร้าง

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

ฟังก์ชันตัวสร้างสามารถขยายได้โดยใช้extendsคำสำคัญ

ฟังก์ชันตัวสร้างไม่สามารถส่งคืนnullเป็นค่าความผิดพลาดได้ newเพราะมันไม่ได้เป็นชนิดข้อมูลวัตถุนั้นจะถูกปฏิเสธโดย

ตัวอย่างฟังก์ชั่นจากโรงงาน

function User(name, age) {
  return {
    name,
    age,
  }
};

let user = User("Tom", 23);

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

ข้อดีและข้อเสีย

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

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

ในกรณีง่าย ๆ ฟังก์ชั่นของโรงงานสามารถทำได้ง่ายในโครงสร้างและความหมาย

วัตถุกลับไม่ได้รับมรดกโดยทั่วไปจากฟังก์ชั่นของโรงงานprototypeทรัพย์สินและผลตอบแทนจากfalseinstanceOf factoryFunction

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


1
นี่เป็นคำตอบที่ล่าช้าที่โพสต์เพื่อตอบคำถามนี้ในหัวข้อเดียวกัน
traktor53

ไม่ใช่แค่ "null" แต่ "new" จะไม่สนใจประเภทข้อมูลใด ๆ ที่ส่งคืนโดยฟังก์ชันตัวสร้าง
วิษณุ

2

โรงงานจะดีกว่าเสมอ เมื่อใช้ภาษาที่มุ่งวัตถุแล้ว

  1. ตัดสินใจเกี่ยวกับสัญญา (วิธีการและสิ่งที่พวกเขาจะทำ)
  2. สร้างส่วนต่อประสานที่เปิดเผยวิธีการเหล่านั้น (ใน javascript คุณไม่มีส่วนต่อประสานดังนั้นคุณจำเป็นต้องหาวิธีตรวจสอบการใช้งาน)
  3. สร้างโรงงานที่ส่งคืนการใช้งานของแต่ละอินเตอร์เฟสที่ต้องการ

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

โรงงาน - ดีกว่าสิ่งอื่นใดทั้งหมด - โครงร่างสปริงสร้างขึ้นโดยสมบูรณ์ตามแนวความคิดนี้


โรงงานจะแก้ไขปัญหานี้ได้อย่างไรเมื่อต้องเปลี่ยนทุกบรรทัด
รหัสชื่อ Jack

0

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

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

น่าสังเกตว่าตัวสร้าง Javascript อาจเป็นโรงงานที่กำหนดเองได้โดยส่งคืนสิ่งอื่นนอกเหนือจากนี้หรือไม่ได้กำหนดไว้ ดังนั้นใน js คุณจะได้รับทั้งสองสิ่งที่ดีที่สุดในโลก - API ที่ค้นพบได้และการรวมวัตถุ / แคช


5
ในจาวาสคริปต์ค่าใช้จ่ายในการก่อสร้างสูงกว่าต้นทุนการใช้โรงงานเนื่องจากฟังก์ชั่นใด ๆ ใน JS สามารถส่งคืนวัตถุใหม่ได้ คอนสตรัคเตอร์เพิ่มความซับซ้อนโดย: การร้องขอnew, การเปลี่ยนแปลงพฤติกรรมของthis, การปรับเปลี่ยนค่าส่งคืน, การเชื่อมต่อการอ้างอิงต้นแบบ, การเปิดใช้งานinstanceof(ซึ่งอยู่และไม่ควรใช้เพื่อจุดประสงค์นี้) เห็นได้ชัดว่าทั้งหมดคือ "คุณสมบัติ" ในทางปฏิบัติมันจะทำลายคุณภาพของรหัสของคุณ
Eric Elliott

0

สำหรับความแตกต่าง Eric Elliott ชี้แจงได้ดีมาก

แต่สำหรับคำถามที่สอง:

เมื่อใดจึงควรใช้อันใดอันหนึ่งแทนอันอื่น?

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

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