การมีเพศสัมพันธ์ ปฏิบัติที่ดีที่สุด


11

ติดตามจากกระทู้นี้ฉันเริ่ม

รูปแบบซิงเกิล

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

แล้วอะไรคือข้อต่อหลวม ๆ และการมีเพศสัมพันธ์อย่างหนัก? ในโครงการปัจจุบันของฉัน (และโครงการแรก) ฉันกำลังทำงานในโครงการ ac # winforms ซึ่งส่วน GUI สร้างวัตถุและ subcribes กับกิจกรรมของพวกเขาเมื่อพวกเขาถูกทริกเกอร์ GUI สร้างวัตถุอื่น (ในตัวอย่างนี้ datagridview (คลาส) ที่ฉันได้สร้างที่ล้อมรอบ DataGridview มาตรฐานและเพิ่มฟังก์ชั่นเพิ่มเติม) และแนบไปกับ GUI นี่คือการแต่งงานกันหรือไม่ดี?

ฉันไม่ต้องการรับนิสัยที่ไม่ดีและเริ่มเขียนโค้ดไม่ดีดังนั้นฉันจะขอบคุณคำตอบของคุณ

คำตอบ:


11

ในการทำให้โค้ดของคุณมีการรวมตัวกันอย่างหลวม ๆ นี่เป็นสิ่งง่าย ๆ ที่ควรจดจำ:

ส่วนที่ 1:

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

ส่วนตัวผมอ้างถึงนี้ (ในโลกเล็ก ๆ น้อย ๆ ของตัวเอง) create it or use itในฐานะ คลาสควรสร้างวัตถุหรือใช้วัตถุที่ไม่ควรทำทั้งสองอย่าง

ส่วนที่ 2:

วิธีการแยกความกังวลออก
ในฐานะที่เป็นจุดเริ่มต้นมีสองเทคนิคง่าย ๆ :

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

ฉีดพึ่งพา :

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

class Tokenizer
{
    public:
        Tokenizer(std::istream& s)
            : stream(s)
        {}
        std::string nextToken() { std::string token; stream >> token;return token;}
    private:
        std::istream& stream;
};

ที่นี่เราส่งกระแสข้อมูลไปยัง Tokenizer tokenizer ไม่ทราบประเภทของกระแสข้อมูลตราบใดที่ใช้อินเทอร์เฟซของ std :: istream

รูปแบบตัวค้นหาบริการ :

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

class Application
{
     public:
         Application(Persister& p)
             : persistor(p)
         {}

         void save()
         {
             std::auto_ptr<SaveDialog> saveDialog = persistor.getSaveDialog();
             saveDialog.DoSaveAction();
         }

         void load()
         {
             std::auto_ptr<LoadDialog> loadDialog = persistor.getLoadDialog();
             loadDialog.DoLoadAction();
         }
    private:
        Persister& persistor;
};

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

สิ่งนี้มีประโยชน์เมื่อต้องการpotentiallyวัตถุที่ไม่ซ้ำกันในแต่ละครั้งที่คุณสร้างอินสแตนซ์ของการกระทำ

โดยส่วนตัวแล้วฉันพบว่าสิ่งนี้มีประโยชน์อย่างยิ่งในการเขียนการทดสอบหน่วย

หมายเหตุของรูปแบบ:

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

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


3
@Darren Young: ขอบคุณที่ยอมรับมัน แต่คำถามของคุณมีอายุเพียงสามชั่วโมง ฉันจะกลับมาอีกในหนึ่งวันหรือมากกว่านั้นเพื่อยืนยันว่าคนอื่นไม่ได้ให้คำตอบที่ดีกว่า
Martin York

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

@Darren Young: เช่นเดียวกับการเขียนโปรแกรมทั้งหมดบรรทัดเป็นสีเทาและเบลอไม่ชัดเจน มันยากที่จะให้การตอบสนองที่แน่นอนโดยไม่ต้องอ่านรหัสของคุณ แต่เมื่อฉันพูดถึงmanaging the dataฉันหมายถึงตัวแปร (ไม่ใช่ข้อมูลจริง) ดังนั้นสิ่งต่างๆเช่นพอยน์เตอร์จำเป็นต้องได้รับการจัดการเพื่อไม่ให้รั่วไหล แต่ข้อมูลสามารถถูกฉีดหรือวิธีการดึงข้อมูลสามารถถูกทำให้เป็นนามธรรม (เพื่อให้คลาสของคุณสามารถนำกลับมาใช้ใหม่ได้ด้วยวิธีการดึงข้อมูลที่แตกต่างกัน) ฉันขอโทษฉันไม่สามารถแม่นยำมากขึ้น
Martin York

1
@Darren Young: ตามที่ระบุไว้โดย @StuperUser ในคำตอบของเขา อย่าไปลงน้ำ (AKA minutae of loose coupling(รักคำว่า minutae)) ความลับของการเขียนโปรแกรมคือการเรียนรู้เมื่อใช้เทคนิค การใช้มากเกินไปอาจทำให้รหัสยุ่งเหยิง
Martin York

@ มาร์ติน - ขอบคุณสำหรับคำแนะนำ ฉันคิดว่านั่นคือสิ่งที่ฉันกำลังดิ้นรนในปัจจุบัน ..... ฉันมักจะกังวลเกี่ยวกับสถาปัตยกรรมของรหัสของฉันและพยายามเรียนรู้ภาษาเฉพาะที่ฉันใช้ด้วย C # ฉันคิดว่ามันจะมาพร้อมกับประสบการณ์และความปรารถนาของฉันในการเรียนรู้สิ่งนี้ ฉันขอขอบคุณความคิดเห็นของคุณ
Darren Young

6

ฉันเป็นนักพัฒนา ASP.NET จึงไม่ทราบมากเกี่ยวกับการเชื่อมต่อ WinForms แต่รู้เพียงเล็กน้อยจากเว็บแอป N-Tier สมมติว่าสถาปัตยกรรมแอปพลิเคชัน 3 ระดับของ UI, โดเมน, Data Access Layer (DAL)

การแต่งงานกันแบบหลวม ๆ เป็นเรื่องเกี่ยวกับ abstractions

เนื่องจาก @MKO ระบุว่าคุณสามารถแทนที่แอสเซมบลีด้วยชุดอื่น (เช่นโครงการ UI ใหม่ที่ใช้โครงการโดเมนของคุณ DAL ใหม่ที่บันทึกลงในสเปรดชีตแทนที่จะเป็นฐานข้อมูล) ดังนั้นจึงมีข้อต่อหลวม หากโดเมนและ DAL ของคุณขึ้นอยู่กับโครงการที่ต่อเนื่องกันไปการเชื่อมต่ออาจจะหลวม

ด้านหนึ่งของบางสิ่งบางอย่างที่กำลังถูกรวมเข้าด้วยกันอย่างหลวม ๆ คือคุณสามารถแทนที่วัตถุด้วยอีกวัตถุหนึ่งที่ใช้อินเทอร์เฟซเดียวกันได้หรือไม่ มันไม่ได้ขึ้นอยู่กับวัตถุจริง แต่คำอธิบายที่เป็นนามธรรมของสิ่งที่มันทำ (ส่วนต่อประสาน)
การเชื่อมต่อที่หลวมอินเตอร์เฟสและ Dependency Injectors (DI) และ Inversion of Control (IoC) มีประโยชน์สำหรับการแยกส่วนของการออกแบบเพื่อความสามารถในการทดสอบ

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

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

อย่าปล่อยให้TOOจมอยู่ใน minutae ของการแต่งงานกันแบบหลวม ๆ หากไม่มีโอกาสที่จะนำสิ่งต่าง ๆ กลับมาใช้ใหม่ (เช่นโครงการโดเมนที่มีมากกว่าหนึ่ง UI) และอายุการใช้งานของผลิตภัณฑ์มีน้อย ของลำดับความสำคัญ (มันจะถูกนำมาพิจารณาด้วย) แต่มันจะยังคงเป็นความรับผิดชอบของสถาปนิก / ผู้นำด้านเทคโนโลยีที่จะตรวจสอบรหัสของคุณ


3

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


3

มาดูหลักการ5 ข้อของโซลิด การปฏิบัติตาม SRP นั้น ISP และกรมทรัพย์สินทางปัญญาจะมีเพศสัมพันธ์ที่ลดลงอย่างมีนัยสำคัญโดยกรมทรัพย์สินทางปัญญาเป็นเครื่องมือที่ทรงพลังที่สุด มันเป็นหลักการพื้นฐานใต้ที่กล่าวมาแล้วDI

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

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

ในความเป็นจริงฉันพบคำถามเกี่ยวกับ stackoverflowที่ฉันแสดงให้เห็นถึงการประยุกต์ใช้ SOLID ในปัญหาที่เป็นรูปธรรม อาจเป็นการอ่านที่น่าสนใจ


1

ตามที่ Wikipedia:

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

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

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

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

  • การจัดเก็บข้อมูลและตรรกะการดึงข้อมูล
  • ตรรกะทางธุรกิจ
  • ต้องใช้ลอจิกเพื่อเรียกใช้ UI

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

หากคุณจริงจังเกี่ยวกับการออกแบบระบบที่เชื่อมโยงกันอย่างหลวม ๆ ให้อ่านหลักการของ SOLID และรูปแบบการออกแบบ

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

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

ทั้งหมดที่ดีที่สุด


1

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

เคล็ดลับที่ดีคือการวางคลาสในไลบรารี / แอสเซมบลีที่แตกต่างกันและให้พวกเขาพึ่งพาไลบรารีอื่นน้อยที่สุดเท่าที่จะเป็นไปได้


4
ถุงยางอนามัยบางคำศัพท์ทางไอทีที่เป็นนามธรรมหรือฉันเพียงแค่ไม่ได้รับการลงโทษ?
Darren Young

3
มันเป็นอีกชื่อหนึ่งสำหรับภาชนะฉีดพึ่งพา
Mchl

2
ยังเป็นวิธีที่ดีในการป้องกันการแพร่กระจายของไวรัส เรากำลังพูดถึงสิ่งเดียวกันหรือไม่?
sova

1
การมีเพศสัมพันธ์อย่างหนักมักจะทำในแบ็กเอนด์
Homde

1
การตั้งค่าพาดหัวเป็นการจัดรูปแบบที่ไม่เหมาะสมอย่างไม่เหมาะสม
Homde

0

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

init, เปิด, ปิด

กับ

setTheFoo, setBar, initX, getConnection, ปิด

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

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

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