본문 바로가기
IT 개인 공부/Java

[Java] 상속(Inheritance) vs 위임(Delegation)

by Libi 2021. 8. 22.
반응형

객체지향 프로그래밍에서 서로 연관된 객체들의 관계를 표현할 때 상속(Inheritance) 위임(Delegation) 관계로 많이 표현한다.

디자인 패턴을 공부해보면 이들을 활용함으로써 보다 객체지향적인 설계가 가능해지는 장점이 있다.

하지만 이들은 각각 장단점이 존재하며 사용해야 하는 상황이 다르기 때문에 무분별하게 남용하게 되면 클래스 간의 결합도가 강해 지거나 의존성이 높아지는 등 오히려 설계에 어려움이 생기게 될 수도 있다.

따라서 이번 기회에 이들의 장단점과 차이점, 언제 사용해야 하는지에 대해서 알아보도록 하자.

 

 

상속(Inheritance)

 

상속은 is a 관계로 extends 키워드를 통해 부모의 속성들을 자식이 물려받아서 사용할 수 있도록 해주는 관계이다.

부모의 속성을 물려받기 때문에 자식은 부모의 코드를 작성하지 않고 사용함으로써 구현하기가 수월해진다.

간단한 예시로 Animal 클래스와 이를 부모로 가지는 Dog 클래스를 구현하였다.

class Animal {

    public void eat() {
        System.out.println("밥먹기");
    }

    public void move() {
        System.out.println("움직이기");
    }
}

class Dog extends Animal {}

 

Dog 클래스에 아무것도 작성하지 않았지만 Animal 클래스의 속성들을 물려받음으로써 사용할 수 있게 된다.

Dog dog = new Dog();
dog.move();
dog.eat();

 

코드를 작성하지 않아도 된다는 점이 엄청난 메리트가 있지만 이로 인해 부모와 강한 결합도를 가지게 됨으로써 변화에 대응하기가 어려워진다.

간단한 예로 부모의 속성을 변경하게 되면 이를 그대로 사용하는 자식에게 영향이 미치기 때문에 곤란한 상황이 발생할 수 있게 된다.

만약 Animal 클래스의 move() 메서드를 제거해야 한다는 요구사항을 받았다고 하자. dog는 Animal 클래스의 move() 메서드를 그대로 사용하기 때문에 dog 객체를 사용하고 있는 곳은 모두 수정해야 하는 불가피한 상황이 발생하게 된다.

즉, 부모 클래스의 변화에 따라 자식 클래스가 변경될 가능성이 존재하기 때문에 OCP의 원칙을 깨뜨리게 된다.

 

그리고 부모의 속성들을 모두 사용하지 않는 경우에도 상속을 사용해버리면 코드가 쓸데없이 복잡해지고 유지 보수하기가 어렵게 된다.

이처럼 변화가 자주 일어나는 상황에는 상속보다는 위임을 사용해야 한다.

 

 

 

위임(Delegation)

 

위임은 has a 관계로 클래스 내에서 위임 관계에 있는 클래스의 인스턴스를 가지고 있는 상태이다.

상속이 클래스 사이의 관계라면 위임은 인스턴스 사이의 관계이다.

위임이라는 말 그 자체로 어떤 행위를 위임 관계에 있는 객체에게 넘겨서 처리하는 것을 의미한다. 스프링에서 Controller 계층이 Repository 계층을 사용하는 관계를 생각하면 쉽다.

public class MyController {

    //has a 관계
    MyRepository myRepository;

    public void save() {
        //myRepository에게 save 권한을 위임에서 처리
        myRepository.save();
    }
}

 

상속 관계는 정적인 관계로 컴파일 시간에 모든 관계가 정해지고 변경될 수 없지만 위임 관계는 동적인 관계로 런타임 시간 동안 관계가 변경될 수 있다.

간단한 예시로 save()를 수행할 때 처음에는 myRepository를 MemoryRepository로 쓰다가 어느 순간에 JpaRepository로 교체하여 사용하게 된다.

public class MyController {

    MyRepository myRepository;

    public void save() {
        //MemoryRepository에게 save 권한을 위임
        myRepository = new MemoryRepository();
        myRepository.save();
       
        //JpaRepository에게 save 권한을 위임
        myRepository = new JpaRepository에게();
        myRepository.save();
    }
}

 

이러한 동작이 가능한 이유는 MyRepository를 인터페이스로 구현하여 객체지향의 꽃인 다형성을 활용할 수 있기 때문이다.

 

또한, 다형성을 활용함으로써 두 클래스 간의 결합도가 낮아진다.

왜냐하면 myRepository의 구현체를 변경하여도 MyService 입장에서는 실제 구현체가 아닌 그들이 구현하는 인터페이스를 바라보기 때문에 적절한 구현체를 갈아 끼우기만 하면 되기 때문이다.

물론, 결합도가 낮아진다 하여도 갈아 끼우기 위해 코드를 조금 수정해야 하는 점에서 OCP 원칙을 깨뜨린다.

하지만 이는 나중에 스프링 프레임워크를 사용하게 되면 의존성 주입을 개발자가 아닌 프레임워크가 해줌으로써 OCP 원칙마저 지킬 수 있게 되는 장점이 있다.

 

상속과 위임의 특징과 장단점에 대해서 알아봤다. 그렇다면 어떤 식으로 설계해서 이들을 사용해야 할까?

정답은 없겠지만 내가 공부하면서 느낀 것은 정말로 두 클래스의 관계가 상속 관계로 확고하고 변경 가능성이 낮거나, 먼 미래의 변화, 유지보수 등을 고려해도 효율적이라면 상속을 사용하고, 그게 아니라면 위임을 사용하는 게 맞는 것 같다.

왜냐하면 객체 간의 결합도가 높다는 것은 코드가 복잡해지고 변화나 유지보수를 대응하기에 어렵다는 단점을 가지며 상속 관계를 위임 관계로 변경하여 처리할 수 있기 때문이다.

 

반응형

댓글