/ OOD

[365 วันแห่งโปรแกรม #day54] Composite pattern

วันที่ห้าสิบสี่ของ ‪#‎365วันแห่งโปรแกรม วันนี้เราจะคุยกันเรื่อง Composite pattern


Composite pattern

Composite pattern นี่ก็ตรงตัวเลยครับ คือเป็นแพทเทิร์นที่มี object นึงถือ reference ของ object อื่นไว้ จุดประสงค์ขอฃแพทเทิร์นนี้คือการสร้าง structure แบบ tree ครับ ตัวอย่างที่เห็นได้ชัดคือเรื่องไฟล์และโฟลเดอร์ ในโฟลเดอร์หนึ่งๆ อาจจะมีไฟล์หรือโฟลเดอร์ย่อยเข้าไปอีกก็ได้ เห็นเป็นโครงสร้างแบบ tree ได้ชัดเจน

Class Diagram

Composite pattern in UML

จาก Diagram จะพบว่าแพทเทิร์นนี้มีส่วนประกอบสามอย่างคือ Component abstraction ที่เป็นโครงหลักของ component ทั้งหมด ส่วน Leaf คือ component ต่างๆ ที่ derived มาจาก Component abstraction และส่วนสุดท้ายคือ Composite ส่วนนี้จะเหมือนกับ Leaf เลย แต่จะมีสมาชิกภายในเป็น object ที่ derived มาจาก abstraction เดียวกันนี้ด้วย (ดูจากเส้นความสัมพันธ์แบบ Has a ที่ Composite ชี้ไป Component abstraction) ส่วนนี้เมื่ออยู่ใน tree เราจะเรียกมันว่า branch เพราะเราสามารถใส่ component อื่นลงไปต่อได้

  • Component
    • เป็น abstraction ของ component ทั้งหมดในแพทเทิร์นนี้
    • กำหนด interface ของ object ทั้งหมด
    • (optional) กำหนด interface สำหรับเรียก parent ของ component นั้น
  • Leaf
    • เป็น leaf ใน tree
    • implement ทุกอย่างที่ abstraction บอกไว้
  • Composite
    • ส่วนที่เป็น Composite Component ของแพทเทิร์นนี้ (เนื่องจากมี children ได้)
    • กำหนด method ที่ใช้ในการทำงานกับ children
    • implement ทุกอย่างที่ abstraction บอกไว้ โดยทั่วไปจะเป็นการ delegate ไปให้ children อีกที

*ในบางตำราบอกไว้ว่า Composite และ Leaf ไม่จำเป็นต้องมี Parent abstraction ร่วมกันก็ได้

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

โดยทั่วไปเราใช้แพทเทิร์นนี้เวลาต้องการโครงสร้างแบบ tree เพื่อให้ Client เอาไปใช้ได้ง่ายโดยอ้างถึง root แค่ตัวเดียว แนวทางที่ใช้บ่อยจะมี 2 แบบคือ

  1. ทำเพื่อใช้ในการ represent โครงสร้างแบบ tree เช่น File/Folder, XML Document, JSON, และอื่นๆ เพื่อให้ Client สามารถใช้งานได้ง่าย และลดความผิดพลาด

  2. ใช้เมื่อมีความจำเป็นที่จะต้องทำอะไรบางอย่างกลับ object ทั้งกลุ่ม เช่น ในเกมทุกเวลา 1/x เราต้องวาด object ใหม่ วิธีการวาดปกติก็คือการวนวาดไปเรื่อย ซึ่งมีความซับซ้อนอยู่พอสมควร ดังนั้นเราจึงใช้ Composite pattern เข้ามาครอบ แล้วสั่งวาดจาก Composite Component แล้วให้ Composite Component สั่งวาด Children อีกที

ตัวอย่างการใช้งาน

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

interface DrawableComponent{
    public void Draw(Renderer renderer);
}

class DrawableCircle : DrawableComponent{
    //some code
    public double Radius{get;set;}
    public override void Draw(Renderer renderer){
        //draw here
    }
}

class DrawableTexture : DrawableComponent{
    //some code
    public Texture Texture{get;set;}
    public override void Draw(Renderer renderer){
        //draw here
    }
}

class  MoveableCharacter : DrawableTexture {
    //some code
    public override void Draw(Renderer renderer){
        //draw here
    }
}

จากโค้ดจะเห็นว่าเรามี object ที่จะวาดลง screen อยู่ 3 แบบ คือ DrawableCircle, DrawableTexture, และ MoveableCharacter ถ้าเรามีคลาสเกมเวาลาจะวาดทุกอย่างลงฉากก็จะต้องวนแบบนี้

class Game: GameAbstraction{
    List<DrawableComponent> drawableComponents = new List<DrawableComponent>();
    public override void init(){
        drawableComponents.Add(new DrawableCircle());
        drawableComponents.Add(new DrawableTexture());
        drawableComponents.Add(new MoveableCharacter());
    }
    //some code
    public override void Draw(Renderer renderer){
        foreach(var object in drawableComponents){
            object.Draw(renderer);
        }
    }
}

ก็ไม่ได้มีอะไรยากใช่ไหมครับ แต่พอเกมเราใหญ่ขึ้นไปวิธีการวาดก็อาจจะซับซ้อนขึ้นเรื่อยๆ โค้ดก็จะบวกขึ้นเรื่อยๆ การ maintain ก็จะยากขึ้นเรื่อยๆ ดังนั้นเราจะย้ายส่วนนี้เข้าไปอยู่ใน Composite Component กัน

class DrawableComposite: DrawableComponent{
    //some code
    List<DrawableComponent> drawableComponents = new List<DrawableComponent>();
    public override void Draw(Renderer renderer){
        foreach(var object in drawableComponents){
            object.Draw(renderer);
        }
    }
}

สมมติว่าผมสร้าง DrawableComposite ที่เป็น Composite Component สำหรับใช้จัดการ DrawableComponent ทุกรูปแบบไว้ ต่อไปเวลาจะวาดผมก็แค่สั่งให้ DrawableComposite นั่นวาด แค่นั้นเลยครับ

class Game: GameAbstraction{
    DrawableComposite  drawableComposite = new DrawableComposite ;
    public override void init(){
        drawableComposite.Add(new DrawableCircle());
        drawableComposite.Add(new DrawableTexture());
        drawableComposite.Add(new MoveableCharacter());
    }
    //some code
    public override void Draw(Renderer renderer){
        drawableComposite.Draw(renderer);
    }
}

จะเห็นว่าโค้ดใน Game ก็จพสะอาดขึ้นนิดนึงละครับ แล้วถ้าเกมเราใหญ่ขึ้นล่ะ? ถ้ามีวิธีวาดหลายแบบ? ก็ง่ายๆ เลยครับ สร้าง Composite Component ใหม่สำหรับการวาด object บางกลุ่ม แล้วก็ยัดใส่ไปใน DrawableComposite เดิม ดังนั้นใน Game เราก็ไม่ต้องแก้ส่วนวาดเลยครับ

class MoveableCharacterComposite: DrawableComponent{
    //some code
    List<MoveableCharacter> drawableComponents = new List<MoveableCharacter>();
    public override void Draw(Renderer renderer){
        foreach(var object in drawableComponents){
            //MoveableCharacter specific code
        }
    }
}

เวลาใช้ก็เช่น

class Game: GameAbstraction{
    DrawableComposite  drawableComposite = new DrawableComposite ;
    public override void init(){
        drawableComposite.Add(new DrawableCircle());
        drawableComposite.Add(new DrawableTexture());
        
        MoveableCharacterComposite moveableCharacterComposite = new MoveableCharacterComposite();
        moveableCharacterComposite.Add(new MoveableCharacter());
        moveableCharacterComposite.Add(new MoveableCharacter());
        moveableCharacterComposite.Add(new MoveableCharacter());
        drawableComposite.Add(moveableCharacterComposite);
    }
    //some code
    public override void Draw(Renderer renderer){
        drawableComposite.Draw(renderer);
    }
}

เห็นมั้ยครับว่าไม่ยากเลยเราก็แค่โยน object ที่ต้องการวาดอีกแบบใส่เข้าไปใน composite ที่เหมาะสม แล้วก็โยน composite ใหม่ใส่เข้าไปในของเก่าอีกที เวลาวาดก็ทำเหมือนเดิมเลย ไม่ต้องแก้อะไร

Game objects

References

Composite pattern - WikiPedia

Composite Design Pattern - SourceMaking

Composite Pattern - OODesign

Composite

Design Patterns - Composite Pattern - TutorialsPoint

A look at the Composite design pattern - JavaWorld

Design Patterns: The Composite Pattern - ReefPoints

Composite Pattern -AVAJAVA

Composite Design Pattern - LePUS

#‎day54 #365วันแห่งโปรแกรม ‪#‎โครงการ365วันแห่ง‬...