컴퓨터/정보처리기사 SW 공학

| 니앙팽이 - 객체지향(OOP) | 4-9 | 유용한 패턴들 - Subclass Sandbox, Event queue, Object Pool

객체지향

📕 4. 객체지향 디자인 패턴

유니티에서 사용하면 좋을 디자인 패턴만 명시한다.


📄 6. 그밖에 Unity에 유용한 패턴

1). ✨ Subclass Sandbox ✨ : Behavioral Patterns

상위 클래스가 제공하는 기능들을 통해서 하위 클래스에서 행동을 정의한다.

ⓐ 특징

# abstract & override #protected #subclass

  1. 실제 구현부, 메인이 되는 객체가 파생클래스다.

  2. 그럼 상위클래스가 제공하는것은 뭔가?

    • 상위 클래스는 하위 클래스가 필요로 하는 기능을 전부 제공할 수 있다.
    • 상위 클래스는 하위 클래스용 메소드인 Sandbox 메소드를 제공한다.
  3. sandbox 가 의미하는것.

    • 추상 원시 메소드.
    • protected로 만들어져 하위 클래스용이라는 걸 분명히 한다.
    • 하위 클래스에서 구현할때 아무 코드나 넣으면서 마음대로 구현할 수 있다.

ⓑ 왜 쓰는건가?

# decouping #재사용성

샌드박스 패턴은 이럴 때 좋다. :

  1. 클래스 하나를 잡고 많이 상속해야할때

    • 유사한 클래스들을 파생클래스로 구현해야할때
  2. 하위 클래스 한번 훑는데 어? 은근 기능 겹치는게 많음,
    겹치는 기능을 상위 클래스로 올려보내
    하위 클래스끼리 쉽게 공유하고 싶을때.
    그럼으로 상위클래스는 하위클래스가 수행해야할 동작을 전부 제공 한다.

  3. 외부 코드간 커플링 감소
    커플링을 상위 클래스에 몰아 놓아 하위 클래스가 필요로 하는 모든 기능을 상위 클래스에서 제공한다.

    • 하위 클래스는 상위 클래스와만 커플링 될 뿐, 그 외 외부 시스템에는 전혀 접근하지 않는다.
    • 즉, 상위 클래스 외 다른 잡다한 외부 코드를 끌어들일 필요가 없다는 얘기 오직 소통해도 상위 클래스로만 소통한다

ⓒ 주의 사항
컴포지트 패턴에서 지적한 상속의 문제

  1. 상위 클래스에 코드가 계속 쌓이는 경향이 있다. 특히 하위 클래스 샌드박스 패턴에서는 그럴 여지가 많다.

  2. '깨지기 쉬운 상위 클래스' 문제

ⓒ 구현

📂메서드를 직접 제공📂
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

namespace SubclassSandbox
{
    //Superclass
    public abstract class Superpower
    {        
        //Active 함수는 서브클래스에서 상속받아 사용될 Sandbox 매소드다.
        public abstract void Activate();

        //이 클래스들은 이제 서브클래스에서 지지고 볶을수 있는 매소드다
            //정말 상위클래스는 하위클래스에게 모든걸 주는구나 = 하위클래스는 오직 상위 클래스간 커플링이 있구나.
        protected void Move(float speed) { Debug.Log("Moving with speed " + speed); }

        protected void PlaySound(string coolSound) { Debug.Log("Playing sound " + coolSound);}

        protected void SpawnParticles() {/*...*/}
    }


    //Subclasses
    public class SkyLaunch : Superpower
    {
        //본격적으로 상속받은 샌드박스 메소드를 구체화 한다.
        public override void Activate()
        {
            //Add operations this class has to perform
            Move(10f);
            PlaySound("SkyLaunch");
            SpawnParticles();
        }
    }

    //Subclasses
    public class GroundDive : Superpower
    {
        //Has to have its own version of Activate()
        public override void Activate()
        {
            //Add operations this class has to perform
            Move(15f);
            PlaySound("GroundDive");
            SpawnParticles();
        }
    }
    public class GameController : MonoBehaviour
    {
        List<Superpower> superPowers = new List<Superpower>();
        //스킬들을 저장하는 리스트

        void Start()
        {
            superPowers.Add(new SkyLaunch());
            superPowers.Add(new GroundDive());
        }

        void Update()
        {
            if (Input.GetKey(KeyCode.Space))
            {
                for (int i = 0; i < superPowers.Count; i++) { superPowers[i].Activate(); }
            }
        }
    }
}
📂컴포넌트 객체를 통해서 제공📂
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

namespace SubclassSandbox
{
    public class SoundPlayer {
        protected void playSound(SoundId sound, double volume) { /*...*/ }
        protected void stopSound(SoundId sound) { /*...*/ }
        protected void setVolume(SoundId sound) { /*...*/ }
    };

    //Superclass
    public abstract class Superpower
    {        
        private SoundPlayer soundPlayer;

        //Active 함수는 서브클래스에서 상속받아 사용될 Sandbox 매소드다.
        public abstract void Activate();

        //이 클래스들은 이제 서브클래스에서 지지고 볶을수 있는 매소드다
            //정말 상위클래스는 하위클래스에게 모든걸 주는구나 = 하위클래스는 오직 상위 클래스간 커플링이 있구나.
        protected void Move(float speed) { Debug.Log("Moving with speed " + speed); }

        protected SoundPlayer GetSoundPlayer() {return this.soundPlayer;}
        protected void SpawnParticles() {/*...*/}
    }


    //Subclasses
    public class SkyLaunch : Superpower
    {
        //본격적으로 상속받은 샌드박스 메소드를 구체화 한다.
        public override void Activate()
        {
            //Add operations this class has to perform
            Move(10f);
            PlaySound("SkyLaunch");
            SpawnParticles();
        }
    }

    //Subclasses
    public class GroundDive : Superpower
    {
        //Has to have its own version of Activate()
        public override void Activate()
        {
            //Add operations this class has to perform
            Move(15f);
            PlaySound("GroundDive");
            SpawnParticles();
        }
    }
    public class GameController : MonoBehaviour
    {
        List<Superpower> superPowers = new List<Superpower>();
        //스킬들을 저장하는 리스트

        void Start()
        {
            superPowers.Add(new SkyLaunch());
            superPowers.Add(new GroundDive());
        }

        void Update()
        {
            //Activate each superpower each update
            for (int i = 0; i < superPowers.Count; i++) { superPowers[i].Activate(); }
        }
    }
}
📂서브클래스 샌드박스로 몬스터 구현📂

public class Monster {
    public Monster(){}

    protected int hp, damage, movespeed;
    protected Vector3 = new Vector3(0,0,0);
    protected abstract void attack();
    protected void walk(string animantion, int speed) {
        updateAnimation(animantion);
        setSpeed(speed);
    };

}

public class Ogre : Monster{
    public Ogre() {
        this.damage = 10;
        this.hp = 100;
        this.movespeed = 5;
    }
    public override void attack() {
        /*느리지만 존나 아픈 몽둥이 떄리맥이고~ */
    }
}
public class Skeleton : Monster{
    public Skeleton() {
        this.damage = 5;
        this.hp = 10;
        this.movespeed = 10;
    }
    public override void attack() {
        /* 와 샌즈! 블라스트!*/
    }
}
public class Ghoul : Monster{
    public Ghoul() {
        this.damage = 5;
        this.hp = 30;
        this.movespeed = 1;
    }
    public override void attack() {
        /*으어*/
    }
}

멈추는 방법을 도입하여 각 몬스터에게 좀 더 유연하게 움직일 수 있도록 합시다.

public class Movement {
    void walk(string animation, int speed) {
        updateAnimation(animation);
        setSpeed(speed);
    }
    void run(string animation) {
        updateAnimation(animation);
        setSpeed(speed);
    }
    void stop(string animation) {
        updateAnimation(animation);
        setSpeed(speed);
    }
}

public class Monster {
    public Monster(){}

    protected int hp, damage, movespeed;
    protected Vector3 = new Vector3(0,0,0);
    protected Movement movement;
    abstract void attack();
    protected void movement(string animation, int speed)
    {
        if(speed == movespeed*2) { movement.run(animation,speed); }
        else if(speed == movespeed) { movement.walk(animation,speed); }
        else { movement.stop(animation, speed); }
    }
    protected Movement GetMovement(return this.movement;)
}

public class Ogre : Monster{
    public Ogre() { this.damage = 10; this.hp = 100; this.movespeed = 5; }
    public override void attack() {
        /*느리지만 존나 아픈 몽둥이 떄리맥이고~ */
    }
}
public class Skeleton : Monster{
    public Skeleton() { this.damage = 5; this.hp = 10; this.movespeed = 10; }
    public override void attack() {
        /* 와 샌즈! 블라스트!*/
    }
}
public class Ghoul : Monster{
    public Ghoul() { this.damage = 5; this.hp = 30; this.movespeed = 1;}
    public override void attack() {
        /*으어*/
    }
}

public class Movement
{
    public void walk(string animation, int speed) { }
    public void run(string animation) { }
    public void stop(string animation) { }
}

public abstract class Monster
{
    public Monster(Movement movement) { 
        this.movement = movement;
    }

    public abstract void Attack();

    protected int hp, damage, movespeed;
    protected int x, y, z;
    protected Movement movement;
    protected void Move(string animation, int speed)
    {
        if (speed == movespeed * 2) { movement.run(animation); }
        else if (speed == movespeed) { movement.walk(animation, speed); }
        else { movement.stop(animation); }
    }
    protected Movement GetMovement() { return this.movement; }
}

public abstract class ThingsThatFart : Monster
{
    public ThingsThatFart(Movement movement) : base(movement) { }
    public abstract void Fart();
}

public class Ogre : ThingsThatFart
{
    public Ogre(Movement movement) : base(movement) { this.damage = 10; this.hp = 100; this.movespeed = 5; }
    public override void Attack()
    {
        /*느리지만 존나 아픈 몽둥이 떄리맥이고~ */
    }
    public override void Fart()
    {
        /*푸드&*^&ㄲ*(@!*(!&*!#)(*%&(!)#%&#&득!*/
    }
}

public class Orc : ThingsThatFart
{
    public Orc(Movement movement) : base(movement) { this.damage = 10; this.hp = 100; this.movespeed = 5; }
    public override void Attack()
    {
        /*느리지만 존나 아픈 몽둥이 떄리맥이고~ */
    }
    public override void Fart()
    {
        /*푸득*/
    }
}
public class Skeleton : Monster
{
    public Skeleton(Movement movement) : base(movement) { this.damage = 5; this.hp = 10; this.movespeed = 10; }
    public override void Attack()
    {
        /* 와 샌즈! 블라스트!*/
    }
}
public class Ghoul : Monster
{
    public Ghoul(Movement movement) : base(movement) { this.damage = 5; this.hp = 30; this.movespeed = 1; }
    public override void Attack()
    {
        /*으어*/
    }
}

참고

  1. 샌드박스
  2. 서브클래스 샌드박스
  3. 서브클래스 샌드박스
  4. 서브클래스 샌드박스
  5. 서브클래스 샌드박스

2). Event queue : Decoupling Patterns

  • Decouple when a message or event is sent from when it is processed.

3). ✨ Object Pool ✨ : Optimization Patterns

  • Improve performance and memory use by reusing objects from a fixed pool instead of allocating and freeing them individually.
  • 이 패턴은 매우 인기가 있어 Unity는 ObjectPool으로 사용가능하다.