Abstract Class vs Traits vs Interface

2 년 전

1119명이 읽음

각각은 어떤 상황에서 쓰는게 좋은걸까요? 명확한 가이드라인이 있나요?

smartbos
2 년 전

Comments0

2 Answers

추상클래스(Abstract Class)는 일단은 "클래스"의 범주에 들어갑니다.

이게 무슨 말인고 하니, 객체지향의 클래스는 보통 "명사"를 담당하고, 매서드는 "동사"를 담당하지요. 즉, 인터페이스는 매서드들의 선언으로만 구성되어있기 때문에 "행동"을 담당하게 됩니다. 보통 그래서 인터페이스의 이름을 Readable이니, Writable과 같은 형용사 형태를 가지게 됩니다. "사람"이라는 클래스가 있다고 가정하면, 그 클래스에는 "걸을수있는", "말할수있는"과 같은 인터페이스가 선언되어있다고 볼 수 있는 것이죠.

interface 걸을수있는
{
    public function 걷다();
}

interface 말할수있는
{
    public function 말하다();
}

class 사람 implements 걸을수있는, 말할수있는
{
    public function 걷다()
    {
        echo "걸었다.";
    }

    public function 말하다()
    {
        echo "말했다.";
    }
}

그렇다면 추상클래스는 어떻게 생각해볼 수 있을까요? 추상클래스는 명확한 행동이 정의되지 않은 클래스라고 생각하면 되는데요, 위 예시에서 계속 이어가면 아마 이렇게 상상해볼 수 있을 것 같습니다. "사람"이라는 클래스를 상속받아서 "선생님"이라는 클래스와 "교수"라는 클래스를 만들고 싶습니다. 그런데 "선생님"과 "교수"둘다 "가르치다"라는 공통된 행동을 가질 수 있겠네요. 그렇다면 "가르칠수있는사람"이라는 추상클래스를 만들고, 그 추상클래스를 상속받는 "교수"와 "선생님"을 가질 수 있겠네요

abstract class 가르칠수있는사람 extends 사람
{
    abstract public function 가르치다();
}

class 선생님 extends 가르칠수있는사람
{
    public function 가르치다()
    {
        echo "학교에서 가르쳤다.";
    }
}

class 교수 extends 가르칠수있는사람
{
    public function 가르치다()
    {
        echo "대학에서 가르쳤다.";
    }
}

자, 그러면 이제 트레이트(Trait)는 무엇일까요. (인터페이스와 트레이트가 생긴 이유가 PHP라는 언어가 다중상속이 안되기 때문에 생긴 것인데요, 더 자세한 내용은 모던퍼그 2014년 2월, Trait in PHP 참고하세요.) 위에 예시에서 계속해서, "사람"말고, "강아지"라는 클래스를 추가하겠습니다. "강아지"는 "짖을수있고", "걸을수있겠네요".

interface 짖을수있는
{
    public function 짖다();
}

class 강아지 implement 짖을수있는, 걸을수있는
{
    public function 짖다()
    {
        echo "짖었다.";
    }

    public function 걷다()
    {
        echo "걸었다.";
    }
}

이렇게 하고 보니, "짖다"라는 행동이 겹치는 것(매서드가 중복)을 볼 수 있습니다. 그렇다고 "강아지"와 "사람"을 "걷다"를 통해서 클래스로 묶을 수도 없고.. (무..물론.. 포유류라는 상위 개념이 존재하긴 합니다만..) 이럴때 바로 트레이트를 사용하면 됩니다. 그러면 "사람"과 "강아지"는 다음과 같이 바뀔 수 있겠네요.

trait 걸을수있다
{
    public function 걷다()
    {
        echo "걸었다.";
    }
}

class 강아지 implement 짖을수있는, 걸을수있는
{
    use 걸을수있다;

    public function 짖다()
    {
        echo "짖었다.";
    }
}

class 사람 implements 걸을수있는, 말할수있는
{
    use 걸을수있다;

    public function 말하다()
    {
        echo "말했다.";
    }
}

트레이트는 위와 같은 방식으로 사용하기 위해 등장을 했는데, 이녀석의 특성상 인터페이스와 함께 사용되는 경우가 많습니다. 그래서인지 최근 PHP rfc에서는 트레이트에 인터페이스를 정의할 수 있는 내용에 대해서 토론하고 있습니다. (PHP RFC: Traits with interfaces)

2016.06.30 내용추가

뭐 누가 여기와서 다시 이 글을 보겠냐마는.. 트레이트를 조금 더 자세히 설명하고자 해당 내용을 추가합니다.

위 서비스가 운영이 잘되서 "고양이"를 추가해야합니다. 근데 "고양이"의 "걷다" 매서드를 기존의 "걸을수있다"에서 사용중인 매서드보다 더 좋게 바꿔보려고 합니다. 그래서 일단 이렇게 구현하기로 하였습니다.

class 고양이 implement 걸을수있는
{
    public function 걷다()
    {
        echo "네발로 걸었다.";
    }
}

그런데 이 "고양이"의 "걷다" 기능을 구현하다가 보니 왠지 "강아지"의 "걷다" 매서드도 같은 내용으로 구현하면 더 좋다고 생각이 들었습니다. 그래서 "네발로걸을수있다" 트레이트를 추가하기로 하고, 강아지에게도 해당 내용을 적용하기로 하였습니다.

trait 네발로걸을수있다
{
    public function 걷다()
    {
        echo "네발로 걸었다.";
    }
}

class 강아지 implement 짖을수있는, 걸을수있는
{
    use 네발로걸을수있다;

    public function 짖다()
    {
        echo "짖었다.";
    }
}

class 고양이 implement 걸을수있는
{
    use 네발로걸을수있다;
}

"강아지" 클래스는 트레이트 교체만으로 훨씬더 우수한 "네발로걸을수있다" 기능으로 교체가 됨을 알 수 있습니다. 그리고 매서드가 존재하고 안하고는 인터페이스가 보장을 해주니 쉽게 소스가 정리가 되었습니다. :-) 물론, 기존의 "걸을수있다" 트레이트는 "사람" 클래스가 존재하는한은 계속해서 유지시켜줘야합니다.

wan2land
2 년 전

Comments1

  • smartbos 2 년 전

    코드에 한글을 쓰니까 이해하기 더 좋네요! ㅎㅎㅎ