Saladpuk.com
🏆 เนื้อหาหลัก
🏆 เนื้อหาหลัก
  • 💖สลัดผัก
  • 📰มีอะไรใหม่บ้าง
    • 2020
      • 2020-11
      • 2020-10
      • 2020-09
      • 2020-08
      • 2020-03
      • 2020-02
      • 2020-01
    • 2019
      • 2019-12
      • 2019-11
      • 2019-10
      • 2019-09
      • 2019-08
  • 🤔อ่านเรื่องไรดี ?
  • มือใหม่หัดเขียนโค้ด
    • 👶เขียนโค้ดด้วยภาษา C#
      • เกิดมาไม่เคยเขียนโค้ดมาก่อนเบย
      • 👶พื้นฐาน
        • 1.โปรแกรมที่ต้องลง
        • 2.โครงสร้างของโค้ด
        • 3.ชนิดของข้อมูล
        • 4.การสร้างตัวแปร
        • 5.คำสั่งพื้นฐาน
        • 6.การแปลงข้อมูล
        • 7.การเปรียบเทียบค่า
        • 8.การตัดสินใจด้วย IF statements
        • 9.การตัดสินใจด้วย Switch statements
        • 10.การทำงานซ้ำๆด้วย While
        • 11.การทำงานซ้ำๆด้วย Do While
        • 12.การทำงานซ้ำๆด้วย For
        • 13.การแก้โจทย์จากรูป
        • 14.มารู้จักกับ Array กัน
      • 🧑ระดับกลาง
        • 15.Value type vs Reference type
        • 16.ลดงานซ้ำๆด้วย Method
        • 17.มารู้จักกับ Class & Field กัน
        • 18.มารู้จักกับ Constructor กันบ้าง
        • 19.มาเขียน Method ใน Class กัน
        • 20.มารู้จักกับ Property กัน
        • 21.ลองใช้คลาสแบบจริงจังบ้าง
        • 22.การสืบทอด Inheritance
        • 23.Polymorphism
        • 24.Abstract Class
        • 25.Interface
        • 26.Namespace
        • 27.Enum
        • 28.Exception handler
        • 29.ลงลึกกับ string
        • 30.StringBuilder เพื่อนคู่ string
      • 👨⏳ระดับสูง
        • Generic
        • Delegates
        • Action & Func
        • Lambda expression
        • LINQ
        • พระคัมภีร์การใช้คำสั่ง LINQ
      • 💡Tips
        • 💡C# version 8.0
        • 💡Boxing & Unboxing
    • 👶Algorithm
      • 👾Algorithm Big-O
      • 👽Algorithm P & NP
    • 👦OOP
      • 💖Abstraction
      • 💖Encapsulation
      • 🏆Abstraction & Encapsulation
      • 💖Inheritance
      • 💖Polymorphism
      • 🏆Inheritance & Polymorphism
      • 📝ลองเขียน OOP ดูดิ๊
      • 👑OOP + Power of Design
      • 🥰เทคนิคในการออกแบบ
    • 👶บทสรุปฐานข้อมูล
      • เก็บรูปในฐานข้อมูล
      • Database indexing
      • การลบข้อมูล
    • 👦Communication Patterns
    • 👦Design Patterns
      • 🤰Creational Patterns
        • 🏭Factory Method
        • 🏭Abstract Factory
        • ☝️ Singleton Pattern
        • 🏗️ Builder Pattern
        • 🎎Prototype Pattern
      • 🧱Structural Patterns
        • 🔌Adapter Pattern
        • 📪Proxy Pattern
  • Puzzle
    • 🧠Challenges
      • 🐴Google ม้า 25 ตัว
      • 🌉Amazon เสา 2 ต้น
      • 🥇ทองเก๊
      • 💊ยาต้านโควิด
      • 🎩CP หมวก 5 ใบ
      • 🧓Einstein's Riddle 01
  • พื้นฐานที่ควรต้องรู้
    • 🐳Docker
      • 📦Docker Containers
      • 🃏Docker Exercise 01
      • 🛠️ Docker Tools
      • 🗃️ Docker Registry
      • 🖼️ Container Image
      • 📢Docker Push
      • 🔄WSL
    • 👶Clean Code
      • 🧓Uncle Bob - Clean Code
      • 🧓Uncle Bob - Comments
      • 🧓Uncle Bob - Naming
      • 🧓Uncle Bob - Mindset
      • 🧓Uncle Bob - TDD
    • 👶Code Smells
    • 👶สิ่งที่คนเขียนโค้ดมักเข้าใจผิด
    • 👶AI พื้นฐาน
    • 👶Git พื้นฐาน
      • Git branching strategy
    • 👶Cloud พื้นฐาน
    • 👶UML พื้นฐาน
      • Activity Diagram
      • Class Diagram
      • Sequence Diagram
      • Use case Diagram
      • บทสรุปการใช้ UML
    • 👶Data Scientist
      • การเลือก Algorithms ให้ AI (1/5)
      • การเตรียมข้อมูลให้ AI (2/5)
      • หลักการตั้งคำถามให้ AI (3/5)
      • แฉความลับของ AI Model (4/5)
      • หัดเขียน AI จาก AI ของคนอื่น (5/5)
    • 👶DevOps พื้นฐาน
    • 👶Docker ขั้นพื้นฐาน
      • Image and Container
      • แชร์ Docker Image ที่สร้างไว้
    • 👶Microservices พื้นฐาน
      • Microservices ที่ดีมีลักษณะยังไง
      • Microservices Tips
      • จาก Monolith สู่ Microservices
    • 👶ความรู้พื้นฐานในการทำเว็บ
    • 👦Bottlenecks of Software
      • หัวใจที่สำคัญที่สุดของฐานข้อมูล
    • 👦Agile Methodology
      • Agile in a Nutshell
      • Software Development Life Cycle
      • Code Review
    • 👦Security พื้นฐาน
      • การเก็บรหัสผ่านที่ถูกต้อง
      • Security in actions
        • Hash function
      • Security Principles
      • 😎The Matrix 1
      • 😎The Matrix 2
      • HTTPS in a nutshell
    • 👦SOLID Design Principles
      • มารู้จักกับ SOLID กันดีกว่า
      • Single-Responsibility Principle
      • Open/Closed Principle
      • Liskov Substitution Principle
      • Interface Segregation Principle
      • Dependency-Inversion Principle
  • Cloud Computing
    • 👶Microsoft Azure 101
      • สมัคร Microsoft Azure
      • รู้จักกับ Resource Groups
      • สร้างเว็บตัวแรกกัน
      • สร้าง Virtual Machine กัน
      • ประเภทของคลาว์เซอร์วิส
      • มาสร้าง Logic App กัน
      • มาสร้าง Function App กัน
      • คลาว์คิดเงินยังไง ?
      • Cloud Native
      • Guideline for Cloud scaling
      • Auto Scaling
    • 👶Azure App Services
    • 👶App Service Plan
    • 👶Azure Storage
      • Blob storage
        • ลองสร้างที่เก็บไฟล์กันเลย
        • เข้าใจ Blob storage ให้มากขึ้น
        • ลองเขียนโค้ดอัพโหลดไฟล์กันบ้าง
        • สร้างเว็บจากที่ฝากไฟล์บนคลาว์
    • 👶Azure Bot Service
      • Bot เข้าใจเราได้ยังไงกันนะ
    • 👶Azure Cognitive Services
      • การสร้าง Cognitive Services
      • การ Login ด้วยใบหน้า
      • อ่านลายมือจากรูปเป็นตัวอักษร (OCR)
      • เขียน AI แยกของต่างๆทำยังไง?
      • เขียนแอพ ทายอายุ บอกเพศ ง่ายจิ๊ดเดียว
      • เขียนแอพให้ AI อธิบายรูปเป็นภาษาคน
    • 👶Machine Learning Studio
      • มาสร้าง AI ของแท้ตัวแรกของเรากัน
      • สร้าง AI ตัดสินใจอนุมัติบัตรเครดิต 💳
      • ลองเรียกใช้ AI ของเรากัน
    • 👶Azure Service Fabric
      • สร้าง Service Fabric กัน
    • 👶Blockchain
      • Blockchain ทำงานยังไง ?
      • Consensus Algorithm คืออะไร ?
      • สร้าง Blockchain ใช้เองกัน !
      • หัดเขียน Smart Contract กัน
    • 👶Power BI
    • 👶Azure Web App
      • เซิฟเวอร์บนคลาว์ ราคา? ต่าง?
    • 👶Azure DevOps
      • เล่น Azure DevOps กัน
      • เล่นกับ Repository
      • ลองทำ Continuous Integration (CI)
      • ลองทำ Continuous Delivery (CD)
      • เล่น Kanban Board
    • 🤠Cloud Playground
      • การป้องกันความลับหลุดตอนที่ 1
      • การป้องกันความลับหลุดตอนที่ 2
      • การป้องกันความลับหลุดตอนที่ 3
      • การป้องกันความลับหลุดตอนจบ
  • Software Testing
    • 👦Test-First Design
    • 👦Test-Driven Development
      • 1.มารู้จักกับ TDD กันดีกว่า
      • 2.Test cases เขาเขียนกันยังไงนะ
      • 3.เครื่องมือในการทดสอบ
      • 4.การใช้ Theory และ InlineData
      • 5.โค้ดที่ทดสอบได้
      • 6.Mantra of TDD
      • 7.Functional & None-Functional testing
      • 8.Manual vs Automation testing
      • 9.Automation Frameworks in .NET
      • 10.Mock Framework
      • 11.มาเรียนการใช้ Moq กันเถอะ
      • 12.สรุป
  • Web
    • 👦Web API
      • 1.Web API คืออะไร
      • 2.ติดตั้ง .NET Core SDK
      • 3.สร้าง Web API ตัวแรกกัน
      • 4.Verbs
      • 5.Swagger เพื่อคู่ API
      • 6.การใช้ Model
      • 7.เรียก Web API ผ่าน Postman
      • 8.มาจัดกลุ่ม API กัน (1/2)
      • 9.มาจัดกลุ่ม API กัน (2/2)
  • Software Design
    • 🤴Design Patterns
      • 🦈Creational patterns
        • Abstract Factory
        • Builder
        • Factory Method
        • Prototype
        • Singleton
      • 🦈Structural patterns
        • Adapter
        • Bridge
        • Decorator
        • Facade
        • Proxy
      • 🦈Behavioral patterns
        • Chain of Responsibility
        • Command
        • Iterator
        • Mediator
        • Memento
        • Observer
        • State
        • Strategy
        • Template Method
        • Visitor
Powered by GitBook
On this page
  • Chain of Responsibility
  • 🎯 เป้าหมายของ pattern นี้
  • ✌ หลักการแบบสั้นๆ
  • 😢 ปัญหา
  • 😄 วิธีแก้ไข
  • 📌 โครงสร้างของ pattern นี้
  • 🛠 ตัวอย่างการนำไปใช้งาน
  • 👍 ข้อดี
  • 👎 ข้อเสีย
  • ‍‍📝 Code ตัวอย่าง

Was this helpful?

Export as PDF
  1. Software Design
  2. Design Patterns
  3. Behavioral patterns

Chain of Responsibility

PreviousBehavioral patternsNextCommand

Last updated 5 years ago

Was this helpful?

Chain of Responsibility

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

🎯 เป้าหมายของ pattern นี้

ป้องกันการเกิด coupling จากตัวส่ง request กับ ตัวจัดการ request

✌ หลักการแบบสั้นๆ

  1. แตกตัวจัดการ request ออกเป็น class หลายๆตัว (handler)

  2. นำตัวจัดการที่แตกออกมาแล้ว มาเรียกต่อกันเป็นทอดๆตามลำดับ (chain)

  3. request จะถูกส่งให้กับ handler ตัวแรก แล้วไปยัง handler ตัวถัดไปเรื่อยๆ ตราบใดที่ยังไม่มี handler ไหนมาจัดการมันได้

😢 ปัญหา

วันนี้เรามาทำงานกับลาซาด้า เว็บขายของ online ของประเทศอุกันด้า ซึ่งใครที่จะสั่งซื้อสินค้าได้จะต้องผ่านการ login ให้เรียบร้อยเสียก่อน (authentication) และ ต้องมีสิทธิ์ (authorization) ในการสั่งซื้อด้วย ซึ่งถ้ามีอย่างใดอย่างหนึ่งไม่ถูก ก็จะไม่สามารถสั่งซื้อได้เลย ตามรูป

หลังจากผ่านไปเดือนกว่าๆ เราก็ได้เพิ่มความสามารถอื่นๆเข้าไปในระบบตามนี้

  • เมื่อส่งคำสั่งซื้อมันจะต้องไม่ส่ง raw data ไปให้กับเซิฟเวอร์ เนื่องจากมันไม่ปลอดภัย ดังนั้นเลยให้จุดนี้ต้องเพิ่มขั้นตอนในการตรวจสอบคำสั่งซื้อเพิ่มขึ้น

  • เพื่อป้องกันการทำ brute force password ทำให้เราต้องเพิ่มการตรวจสอบ IP ที่ login ล้มเหลวหลายๆครั้งด้วย

  • เพื่อความรวดเร็ว บางทีเราก็จะส่ง cach data กลับไป (สำหรับบาง request ก็ไม่สามารถใช้ cach ได้อยู่ดีนะจ๊ะ)

Brute force - คือการที่ hacker ใช้ในการสุ่มรหัสผ่านของเราแบบง่ายที่สุด คือสุ่มแบบไล่ทุกอย่างไปเรื่อยๆเลย เช่น PIN 4 หลัก เขาก็จะไล่ใส่ตั้งแต่ 0000 ไปจนถึง 9999 ทำให้สุดท้ายเขาก็จะรู้รหัส PIN เรา ดังนั้นโดยระบบปรกติเขาเลยจะ lock บัญชีที่ใส่หรัสผ่านผิดบ่อยๆ

Cach data - คือข้อมูลที่เก็บไว้ชั่วคราว โดยปรกติจะเป็นข้อมูลที่ไม่มีการเปลี่ยนแปลง (static) เนื่องจากเป็นข้อมูลที่ระบบมันจำไว้ เลยไม่ต้องผ่านการคำนวณอะไรมากมาย ดังนั้นเวลาทำงานกับข้อมูลพวกนี้จะเร็วม๊วก ทำให้วิธีนี้เป็นที่นิยมใช้บ่อยๆ เช่น หน้าเว็บไหนที่มีคนเข้าบ่อยๆ และหน้านั้นไม่ค่อยเปลี่ยน คนทำเว็บเขาก็จะเก็บหน้านี้เป็น cach ไว้ แล้วพอมีคนมาเปิดหน้าเว็บนั้นๆ ระบบก็จะส่งหน้าเว็บนั้นไปให้ได้เลย ทำให้เว็บค่อนข้างเร็ว

จากที่เราเพิ่มความสามารถตามที่ไล่มา ทำให้ขั้นตอนในการตรวจสอบการสั่งซื้อเพิ่มขึ้น ตามรูป

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

และอีกจุดที่อาจจะมองตกไปคือ ทุกเงื่อนไขที่เพิ่มเข้าไป มันจะยิ่งเพิ่มการผูกติดกัน (coupling) เข้าไปเรื่อยๆอีกด้วย

จากที่นั่งเมากาวมาจะเห็นว่า ทุกๆครั้งที่เพิ่มอะไรลงไปมันก็จะทำให้ระบบยากต่อการทำความเข้าใจ และการบำรุงรักษาก็จะยากตามไปด้วย (ยิ่งถ้าไม่ทำ Refactor code นะ ยิ่งเป็นบาปหนักกว่าเดิม)

จากปัญหาที่ว่ามา เราจะจัดการยังไงดี เพื่อไม่ให้มันเป็นสปาเก็ตตี้แบบนี้ดี??

😄 วิธีแก้ไข

ขณะที่กำลังปวดหัวอยู่นั้นเอง ก็ได้เดินไปเจอกับเทะสึไบซังที่กำลังเดินบิณฑบาตอยู่แถวนั้นอยู่พอดี เขาก็เลยบอกว่า ใช้ Chain of responsibility สิโยม!!

ขณะที่เรายืน งงๆ แล้วสูดหายใจเอาฝุ่น 2.5 เข้าปอดอยู่ เทะสึไปซังก็พูดต่อว่า

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

ยังไม่หมดเท่านั้นนะโยม หลังจากที่โยมแตกเป็น Handler เป็นหลายๆตัวละ ก็ให้เอา handler พวกนั้นมาเรียกใช้งานต่อๆกันเป็นทอดๆนะ (Chain) โดยที่เจ้า handler แต่ละตัวจะต้องรู้ด้วยว่า handler ตัวที่ต่อจากมันคือตัวไหน

ซึ่งพอโยมทำแบบนี้เวลาที่มีคำสั่งซื้อเข้ามา คำสั่งซื้อก็จะถูกโยนไปหา handler ที่มันต่อๆกันอยู่ แล้วมันก็จะถูกประมวลผลโดย handler แต่ละตัวไงละโยม

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

เดี๋ยวอาตามาจะเขียนภาพให้ดูนะ เห็นโยมยืนสูดฝุ่น 2.5 ไปเยอะจนน่าจะมึนละ ... ได้ภาพประมาณนี้เลยโยม

ฟังอาตามาอธิบายดีๆนะโยมนะ (เริ่มรู้สึกหงุดหงิดเจ้าพระรูปนี้แล้วแฮะ)

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

แต่ไหนๆก็ไหนๆแล้วนะโยม อาตตามาจะบอกความลับให้ฟัง โดยทั่วไปแล้วเวลาเขาเอา chain of responsibility pattern นี้ไปใช้นะ เขาจะทำกลับด้านกันนิดหน่อยคือ ถ้ามี request เข้า มาแล้ว handler นั้นๆสามารถทำงานกับ request นั้นได้จนจบแล้วละก็ มันจะไม่ส่ง request ไปให้กับ handler ตัวถัดไปเลย นะจุ๊

สุดท้ายอาตตามาจะบอกให้เอาบุญ โดยปรกติแล้ว handler ทั้งหลาย ควรจะต้องมาจาก interface เดียวกันนะจุ๊ ส่วน concrete handler ก็ดูแลแค่ method execute ของตัวเองก็พอ มันจะได้ไม่เกิด coupling ขึ้นยังไงละ

📌 โครงสร้างของ pattern นี้

อธิบาย 1.Handler - interface กลางของ handler ตัวมันจะมี method สำหรับจัดการ request 2.Base Handler - (มีหรือไม่มีตัวนี้ก็ได้) base class ของ handler โดยจะมีโค้ดที่ใช้บ่อยๆอยู่ในนี้ 3.Concrete Handlers - ตัว handler สำหรับทำงานที่แท้จริง เมื่อมันได้รับ request โดยตัวมันสามารถเลือกได้ต่อว่าจะส่งต่อ หรือ หยุดส่ง request ให้ handler ที่ต่อจากมัน และโดยปรกติข้อมูลที่จำเป็นในการทำงานของมันจะถูกส่งเข้ามาผ่าน constructor 4.Client - สร้าง chain ของ handler ทั้งหลาย หรือเรียกใช้ chain ที่มีอยู่แล้ว

🛠 ตัวอย่างการนำไปใช้งาน

สมมุติว่าเราต้องการเขียน lib ตัวนึงที่เอาไว้เขียน log ไฟล์ โดยมี log ทั้งหมด 3 แบบคือ Info, Debug และ Error

โดยรอบนี้เราจะใช้ Chain of responsibility มาช่วย แต่จะใช้ในรูปแบบที่ หากมี handler ไหนจัดการไปแล้ว มันจะไม่ส่งต่อให้ handler ตัวถัดไปนะ ไปลองดูโค้ดตัวอย่างกันเบย

👍 ข้อดี

  • สามารถจัดการ request โดยเป็นลำดับๆได้

  • สามารถเพิ่มขั้นตอนใหม่ๆเข้าไปโดยไม่มีผลกระทบกับระบบ

👎 ข้อเสีย

  • บาง request อาจจะไม่ถูก handler ตัวไหนเอาไปทำงานเลย ถ้าเราไม่เขียนการจัดการส่วนนี้ดีๆ

‍‍📝 Code ตัวอย่าง

using System;

// Request Models
class LogInfo
{
    public int Level { get; set; }
    public string Message { get; set; }

    public LogInfo(int level, string message)
    {
        Level = level;
        Message = message;
    }
}

// Handler Interface
interface ILogger
{
    ILogger HigherLogger { get; }
    void SetNextLogger(ILogger logger);
    void WriteLog(LogInfo log);
}

// Base Handler
abstract class LoggerBase : ILogger
{
    private int logLevel;

    public ILogger HigherLogger { get; protected set; }

    public LoggerBase(int logLevel)
        => this.logLevel = logLevel;

    public void SetNextLogger(ILogger logger)
        => HigherLogger = logger;

    public void WriteLog(LogInfo log)
    {
        if(logLevel >= log.Level)
            WriteMessage(log.Message);
        else
            HigherLogger?.WriteLog(log);
    }

    protected abstract void WriteMessage(string msg);
}

// Concrete Handlers
class InfoLogger : LoggerBase
{
    public InfoLogger(int logLevel)
        : base(logLevel){ }

    protected override void WriteMessage(string msg)
        => Console.WriteLine($"[INFO] {msg}");
}
class DebugLogger : LoggerBase
{
    public DebugLogger(int logLevel)
        : base(logLevel){ }

    protected override void WriteMessage(string msg)
        => Console.WriteLine($"[DEBUG] {msg}");
}
class ErrorLogger : LoggerBase
{
    public ErrorLogger(int logLevel)
        : base(logLevel){ }

    protected override void WriteMessage(string msg)
        => Console.WriteLine($"[ERROR] {msg}");
}

// Client
class Program
{
    static void Main(string[] args)
    {
        const int INFO = 1;
        var infoLogger = new InfoLogger(INFO);

        const int DEBUG = 2;
        var debugLogger = new DebugLogger(DEBUG);

        const int ERROR = 3;
        var errorLogger = new ErrorLogger(ERROR);

        infoLogger.SetNextLogger(debugLogger);
        debugLogger.SetNextLogger(errorLogger);

        var chainedLogger = infoLogger;
        chainedLogger.WriteLog(new LogInfo(INFO,"Hello world XD"));
        chainedLogger.WriteLog(new LogInfo(DEBUG, "Houston we have a problem."));
        chainedLogger.WriteLog(new LogInfo(ERROR, "Who the cat is Huston?"));
    }
}

Output

[INFO] Hello world XD
[DEBUG] Houston we have a problem.
[ERROR] Who the cat is Huston?

🤴
🦈
img
img
img
img
img