description |
---|
แนวคิดในการควบคุม object ให้ทำงานดั่งใจ 😈 |
เจ้าตัวนี้ผมขอตั้งชื่อเป็นภาษาไทยว่า ผู้ควบคุม ละกัน ซึ่งมันอยู่ในกลุ่มของ 🧱 Structural Patterns โดยเจ้าตัวนี้จะมาช่วยแก้ปัญหาเมื่อ เราต้องการจะควบคุมพฤติกรรม object ให้ได้ดั่งใจ พูดแล้วก็ งงๆ ไปดูโจทย์ของเรากันเลยดีกว่าจะได้เข้าใจได้เร็วขึ้น
{% hint style="info" %}
แนะนำให้อ่าน
บทความนี้เป็นส่วนหนึ่งของมหากาพย์ Design Patterns ที่จะมาเป็น guideline ในการแก้ปัญหาในการออกแบบซอฟต์แวร์โปรเจค หากใครสนใจอยากเข้าใจตั้งแต่ต้นว่ามันคืออะไร และเจ้า patterns ทั้ง 23 ตัวมีอะไรบ้าง ก็สามารถจิ้มตรงนี้เพื่อไปอ่านบทความหลักได้เบยครัช 👦 Design Patterns
{% endhint %}
{% hint style="warning" %}
หมายเหตุ
เนื้อหาของบทความนี้จะเน้นให้เข้าใจหลักการทำงานของ Design Patterns แต่ละตัว โดยภาพจากการ์ตูน One Piece มาใช้ประกอบ ซึ่งหลายๆอย่างนั้นมโนขึ้นมาเพื่อความสนุก และทำให้เข้าใจเนื้อหาได้ง่าย ลิขสิทธิ์ต่างๆอย่ามาจับผมนะผมโดนแมวน้ำครอบงำ + รู้เท่าไม่ถึงการ + ผมเป็นคนดี + ผมมีลูกมีเมียมีสามีที่ต้องดูแล 😭
{% endhint %}
สมมุติว่ามี Web API ตัวหนึ่งชื่อ พรหัก ที่สอนธรรมะแบบแปลกๆแก่ชาวโลกอยู่ตัวหนึ่ง ซึ่งรัฐบาลโลกเห็นว่าเป็นภัยต่อความมั่นคง พลโท.การ์ป จึงสั่งการแบนมันทิ้งอย่างฉับพลัน
แต่ถ้าแบนมันทิ้งไปดื้อๆ ก็อาจทำให้เหล่าประชาชนคนเหงาที่ต้องการที่พึ่งทางใจออกมาประท้วงได้ และบางธรรมะบรรยายก็เป็นเนื้อหาที่น่าสนใจ ดังนั้นพลโทการ์ปจึงสั่ง กระทรวง ผู้คุมกฏ จัดการควบคุมเนื้อหาแทน โดยมีเงื่อนไขว่า
- ผู้ใช้ทุกคนต้อง ใช้งาน API ได้เหมือนเดิมทุกอย่าง
- รัฐบาลโลกสามารถ ควบคุม เนื้อหาที่คิดว่าเป็นภัยต่อความมั่นคงได้
- รัฐบาลโลก กำหนดสิทธิในการเข้าถึง เนื้อหาต่างๆได้
- เมื่อเราแก้ไขอะไรก็ตามผู้ใช้ต้องไม่รู้ตัวถึงการเปลี่ยนแปลงใดๆเลย
เวลาที่ผู้ใช้จะเข้าเว็บอะไรก็ตาม มันจะต้องผ่าน ผู้คุมกฎ ก่อนเสมอ เพื่อให้ผู้คุมกฎไปเอาผลลัพท์กลับมาให้ดูนั่นเอง
🤔 ดังนั้นเราในฐาน Developer ที่ทำงานเป็นผู้คุมกฎ จะออกแบบยังไงถึงจะทำได้ครบทุกข้อตามที่รัฐบาลโลกสั่งมา ?
หากไม่เห็นผมเขียนบทความใหม่ๆแล้ว ก็ฝากไปประกันตัวผมด้วย หรือว่างๆก็แวะมาทักทายกันได้นะ (ผมชอบกินเนื้อย่าง) 😭
จากที่เล่ามาก็ดูเหมือนจะไม่มีไรยากชิมิ ในเมื่อทุกอย่างต้องผ่านเราอยู่แล้ว ดังนั้นเวลาที่ผู้ใช้ขอดูพระธรรมอะไรมา เราก็แค่ตรวจดูเนื้อหาในนั้นก่อนก็พอแล้วนิ
เข้าใช้งานตามปรกติได้
ใครขออะไรมาก็ทำให้เขาไป แต่ขอตรวจเนื้อหาก่อนที่จะส่งไปให้ก่อนนะ
ควบคุมเนื้อหาได้
ถ้าตรวจเนื้อหาแล้วมันอาจเป็นภัยต่อความมั่นคง เราก็เปลี่ยนเนื้อหาเป็นอย่างอื่นที่เราอยากให้เป็นไปซะก็จบละ
กำหนดสิทธิ์ในการเข้าถึง
ถ้าเนื้อหาไม่ได้เป็นภัยอะไร แต่เรารู้สึกว่าไม่เหมาะสมกับคนกลุ่มนี้ เราก็เปลี่ยนเป็นเนื้อหาที่เราคิดว่าเหมาะสมแทน
ก็ดูเหมือนจะได้เกือบครบหมดแล้วนะ ดังนั้นเขียนโค้ดนิดหน่อยละกัน ครืดๆ
โดยจากรูปด้านบนจะเห็นว่า เมื่อไหร่ก็ตามที่ clients ขอดูเว็บ เราในฐานะผู้คุมกฎก็จะไปเรียกใช้ ApiRequestHandler ไปดึงข้อมูลต่อให้ ดังนั้นโค้ดที่ใช้ในการตรวจสอบเงื่อนไขสิ่งต่างๆก็จะอยู่ภายในคลาส ApiRequestHandler นั่นเอง
ดังนั้นโค้ดก็น่าจะออกมาราวๆนี้
public class ApiRequestHandler
{
private พรหัก api;
public ApiRequestHandler(พรหัก api)
{
this.api = api;
}
public Content(string key)
{
var response = api.Content(key);
// ตรวจสอบหรือแก้ไข response เพื่อให้เป็นแบบที่เราต้องการ
return response;
}
}
อะเชจากโค้ดด้านบนก็สามารถตอบโจทย์ทุกอย่างได้หมดละ แต่มันจะเกิดอะไรขึ้น ถ้าเราอยากควบคุม api มากว่านี้ล่ะ? ... หรือ อยากเพิ่มเงื่อนไขเฉพาะเว็บนี้แต่เว็บอื่นไม่ต้องสนใจละ? ... หรือ ถ้า API ของพรหักเปลี่ยนล่ะ? ... โค้ดใน ApiRequestHandler ก็จะต้องถูกแก้ไขอะดิ 😨
เพราะโค้ดของเรามันมีหลายอย่างที่ไม่ตรงหลักในการออกแบบที่ดี เช่น SRP, OCP, DIP ยังไงล่ะ
{% hint style="danger" %}
Single-Responsibility Principle (SRP)
การออกแบบที่ละเมิดหลักในการออกแบบนี้จะทำให้ เวลาที่ Requirement เปลี่ยนมาทีนึง มันก็จมีโอกาสสูงมากที่การเปลี่ยนนั้นมันจะไปกระทบเจ้าสิ่งนั้น ทำให้เราต้องแก้ไขมัน ซึ่งผองเพื่อนอื่นๆที่มันดูแลอยู่นั้นไม่ได้เกี่ยวข้องเลยก็มีผลกระทบด้วยนั่นเอง ส่วนใครที่ลืมหรืออยากทบทวนเรื่อง SRP สามารถเข้าไปอ่านได้จากลิงค์นี้เบย Single-Responsibility Principle
{% endhint %}
{% hint style="danger" %}
Open & Close Principle (OCP)
การออกแบบที่ละเมิดหลักในการออกแบบนี้จะทำให้ทุกครั้งที่มีของใหม่ๆถูกเพิ่มเข้าไปปุ๊ป เราก็ต้องไปแก้โค้ดเดิมเสมอ สำหรับใครที่ลืมหลักในการออกแบบเรื่องนี้ไปแล้วให้กดอ่านได้จากตรงนี้ Open & Close Principle
{% endhint %}
{% hint style="danger" %}
Dependency-Inversion Principle (DIP)
การละเมิดกฎข้อนี้จะทำให้ module หลักต้องถูกแก้ไขบ่อยๆ เมื่อตัวที่ทำงานตัวเล็กตัวน้อยมีการเปลี่ยนแปลง แม้จะเปลี่ยนเพียงแค่เล็กน้อยก็ตาม Dependency-Inversion Principle
{% endhint %}
😢 เอาน่าไม่ต้องเสียใจไป เดี๋ยวเราลองวิเคราะห์ปัญหาแล้วลองแก้ไขมันไปทีละปมดูละกันนะ
โจทย์ในรอบนี้ความยากของมันคือ จะควบคุมสิ่งที่ควบคุมไม่ได้ยังไง ซึ่งเมื่อคุมได้ เงื่อนไขต่างๆก็ต้องดูแลแยกจากตัวที่ถูกควบคุมอันอื่นด้วยนั่นเอง ซึ่งเรื่องการควบคุมนี้ ดช.แมวน้ำ ได้เขียนอธิบายไว้ใน Adapter Pattern
แล้ว หากสนใจอยากดู Case Study ก็กดตรงนี้เพื่อนไปดูต่อได้่เลยครับ
เมื่อเรามีของที่เราควบคุมไม่ได้ เราต้องเปลี่ยนเป็นสิ่งที่เราควบคุมได้ก่อน เพราะไม่อย่างนั้นสนุกแน่ ซึ่งการที่เราจะไปควบคุมของอื่นๆมีหลายวิธีเลย ซึ่งวิธีง่ายสุดคือการทำ Wrapper Class โดยเราจะสร้างคลาสขึ้นมาครอบของที่ควบคุมไม่ได้เอาไว้ แล้วเราก็ทำงานกับคลาสตัวนั้นแทนที่จะทำงานกับของที่ควบคุมไม่ได้นั่นเอง ตามรูปด้านล่าง
Design Pattern ที่มีลักษณะเป็น Wrapper Class มีหลายตัวเลย เช่น Adapter, Decorator ลองไปศึกษาต่อได้
โดยปรกติ Wrapper Class จะมีหน้าที่เพียงแค่เรื่องเดียวคือควบคุมสิ่งที่มันดูแลอยู่ ดังนั้นเราก็จะมี Wrapper Class เอาไว้ดูแล API พรหัก โดยเฉพาะเลย ตามรูปด้านล่าง
แต่ทำแค่นี้ยังไม่จบเพราะเงื่อนไขในรอบนี้คือ คนใช้ต้องไม่รู้ถึงความแตกต่าง (ว่ากำลังใช้ Wrapper หรือ พรหัก) ดังนั้น เราต้องทำให้ Wrapper มีคุณสมบัติเป็นหนึ่งเดียวกับตัวที่มันควบคุมอยู่ โดยทำให้มันเป็นมาตรฐานเดียวกันตามรูปด้านล่าง (จริงๆมันทำได้หลายวิธีนะ แต่แมวน้ำขอยกไปอธิบายในหัวข้อล่างๆละกัน)
คราวนี้เวลาที่ (1) client ขอเข้าเว็บเมื่อไหร่ (2.1) เราก็จะเรียกใช้ตัว IWebApi มาทำงาน (2.2) ซึ่งตัว object จริงๆของมันคือ Wrapper (3) แต่ว่าเจ้า Wrapper มันไม่ได้ทำงานเอง มันจะคอยส่งไปให้ พรหัก API ต่างหาก (4) แล้วเมื่อได้ผลลัพท์กลับมา เจ้า Wrapper ก็จะคอยตรวจสอบ/แก้ไขผลลัพท์ให้เรียบร้อยก่อน (5) ค่อยส่งกลับไปให้ผู้ใช้ตามรูปนั่นเอง
🤠 หากเราอยากเพิ่ม/ลดเงื่อนไขต่างๆก็จะมีผลกระทบแค่กับคลาสเดียวนั่นคือ Wrapper นั่นเอง ซึ่งก็จะตรงกับกฎของ Single-Responsibility Principle ****เรียบร้อย
🤠 จากที่ว่ามาก็จะทำให้เราสามารถ เพิ่มตัวที่อยากควบคุมอื่นๆเพิ่มเข้ามาได้เรื่อยๆ โดยไม่มีผลกระทบกับโค้ดเดิม ซึ่งตรงกับกฎของ Open & Close Principle ****เรียบร้อย และการแก้ไขเล็กๆน้อยๆก็จะไม่มีผลกระทบกับ Module หลัก ซึ่งตรงกับกฎ Dependency-Inversion Principle ****เช่นกัลล์
🤠 แถมการทำแบบนี้ก็จะทำให้ Wrapper ของเราเป็นหนึ่งเดียวกันกับสิ่งที่มันต้องการควบคุม ซึ่งก็จะตรงกับกฎ Liskov Substitution Principle อีกด้วย
{% hint style="danger" %}
Liskov Substitution Principle (LSP)
การออกแบบที่ละเมิดหลักในการออกแบบนี้จะทำให้เราระแวงในการใช้ subclass เสมอ เพราะไม่แน่ใจว่า subclass ที่เอามาใช้ จะสามารถทำงานได้ 100% แบบคลาสแม่นั่นเอง สำหรับใครที่ลืมหลักในการออกแบบเรื่องนี้ไปแล้วให้กดอ่านได้จากตรงนี้ Liskov Substitution Principle (LSP)****
{% endhint %}
😳 อุ๊ตะ!! พึ่งเห็นว่าลืมเปลี่ยนชื่อ Wrapper Class ซะได้ ดังนั้นเราก็เปลี่ยนมันเป็น Proxy เก๋ๆล้อกับสิ่งที่มันควบคุมอยู่ตามรูปด้านล่างนั่นเอง
ยินดีด้วยในตอนนี้คุณได้ใช้สิ่งที่เรียกว่า Proxy Pattern เรียบร้อยแล้ว ไม่ว่าจะรู้ตัวหรือไม่ก็ตาม เย่ๆ 👏
มันคือ แนวคิดในการควบคุม object ให้ทำงานดั่งใจ นั่นเอง โดยมีแนวคิดง่ายๆว่า ถ้าคุมมันไม่ได้ งั้นก็ส่งคนของเราไปคุมมันอีกทีละกัน แล้วเราจะสั่งให้คนของเราทำอะไรก็ได้ละทีนี้
ก็อย่างที่บอกว่าใช้ควบคุมของต่างๆที่เราคุมไม่ได้ แต่นอกเหนือจากนั้นจริงๆประโยชน์มันมีหลายอย่างเลย โดยเขาแบ่งมันออกเป็นกลุมต่างๆตามนี้
หากของที่เราจะเรียกใช้งานมันเปลืองทรัพยากร เช่น ไฟล์รูปมันใหญ่มากเสียเวลาโหลด ดังนั้นเราก็สามารถใช้ Proxy ให้มันจำรูปที่เคยโหลดมาเก็บไว้เป็น memory cache แล้วถ้าเรียกใช้เมื่อไหร่ก็จะได้ไม่ต้องโหลดใหม่ก็ได้ (ของที่เป็นตระกูล caching data เราควรทำ expiration ให้มันด้วยเสมอ) หรือ การสร้าง object บางตัวค่อนค่างกินเวลา แบบ Database Connection เราก็สามารถทำ cache เป็น connection pool ไว้ก่อนก็ได้
หากของที่เราจะเรียกใช้สามารถทำงานจากภายใน Local ได้ เช่น ของบางอย่างสามารถทำงานแบบง่ายๆได้จากตัว client เลยก็สามารถเขียนเป็น Proxy ให้มันทำงานในนั้นให้จบซะ ส่วนไหนทำไม่ได้ค่อยส่งมาที่ Server ก็ได้ เพราะของบางอย่าง client บางประเภทไม่สามารถเอา logic ไปเขียนไว้ในนั้นได้ เช่น ของที่มี sensitive logic ที่ไม่อยากให้คนอื่นเห็น
หากของที่เราจะเรียกใช้จะต้องตรวจสอบสิทธิในการเข้าถึงก่อน เช่น เราตั้งเงื่อนไขว่าคนใช้งานต้องอายุเกิน 18 ปีขึ้นไป แต่ตัว API อีกฝั่งไม่ได้ตรวจเรื่องนี้ให้ เราก็สามารถตรวจเงื่อนไขก่อนที่จะเรียกใช้งานได้นั่นเอง
เจ้าตัวนี้จะคล้ายๆกับ Remote Proxy จนบางทีเขาก็มองว่ามันคืออันเดียวกัน ซึ่งหน้าที่ของมันคือดูว่าของที่จะตอบกลับไปจริงๆแล้วควรเป็นอะไรนั่นเอง
การออกแบบ Proxy นั้นสามารถทำได้หลายวิธีเลย และไม่จำเป็นต้องทำตามที่ ดช.แมวน้ำ บอกมานะ เพียงแค่คนส่วนใหญ่นิยมใช้แบบไหนป๋มก็เลยเอามาเขียนเจ๋ยๆ ซึ่งปรกติเราจะเห็นการออกแบบ Proxy 2 วิธีตามด้านล่าง
{% hint style="danger" %}
ข้อควรระวัง
แบบที่ 1 และ แบบที่ 2 มันมีทั้งข้อดีข้อเสียคนละแบบกัน ดังนั้นก่อนเอาไปใช้ก็ชั่งน้ำหนักให้ดีก่อนนะกั๊ฟ แต่โดยปรกติตัวเลือกที่ 1 จะเหมาะสมและใช้งานได้ง่ายที่สุดนั่นเอง
{% endhint %}
👍 ข้อดี - ของทุกอย่างถูกแยกขาดออกจากกัน ทำให้เราแก้ไขได้โดยไม่มีผลกระทบกับอีกตัว
👎 ข้อเสีย - เราก็ไม่สามารถเข้าถึงอีกฝั่งได้เยอะมากเท่าไหร่
👍 ข้อดี - เราสามารถใช้ความสามารถส่วนใหญ่ได้จากตัว base class เลย และ subclass ก็ได้อานิสงตามไปด้วย
👎 ข้อเสีย - ถ้าแก้ไขโค้ดที่ base class ก็จะส่งผลกระทบถึง sub class ด้วย
การนำ Proxy Pattern มาใช้งานนั้นจะช่วย ****ลดการผูกกันของโค้ดลง แถมยังสามารถควบคุมการทำงานอีกฝั่งได้ดั่งใจแม้ว่าเราจะไม่ได้เป็นคนเขียนอีกฝั่งก็ตาม ซึ่งส่วนใหญ่เราจะใช้ควบคุม 3rd party library และสุดท้ายโค้ดของเราถูกแยกหน้าที่ออกให้ดูแลเป็นของใครของมัน (Separation of Concerns)
เพิ่มความซับซ้อนโดยไม่จำเป็น เพราะการนำ Proxy ไปใช้ จะทำให้เราไม่สามารถทำงานกับ Source ได้ตรงๆ
{% hint style="danger" %}
ข้อควรระวัง
อย่านำ Proxy Pattern ไปใช้มั่วซั่ว เพราะมันทำให้โค้ดของเราซับซ้อนขึ้นเยอะเลยแทนที่เราจะเรียกใช้จาก Source ได้ตรงๆ เราจะต้องทำผ่าน Adapter อีกทีหนึ่ง ดังนั้นให้ชั่งน้ำหนักให้ดีเสียก่อนว่าปัญหาที่เราเจออยู่นั้น มันวุ่นวาย เทสยาก โค้ดมันผูกกันอยู่เยอะหรือเปล่า ถ้าชั่งน้ำหนักแล้ว + มีเหตุผลที่เพียงพอที่จะใช้ก็จงใช้ให้สบายใจไปเถิด
{% endhint %}
{% hint style="success" %} เกลียด ชอบ ถูกใจ อยากติดตาม อยากติชมแนะนำด่าทอ หรืออะไรก็แล้วแต่ (ห้ามมายืมเงิน) จิ้มลงมาที่เพจนี้ได้เลย Mr.Saladpuk และจะเป็นประคุณอันล้นพ้นถ้ากด Like + Follow + Share ให้ด้วยขอรับ น้ำตาจิไหล 🥺 {% endhint %}