• 2021. 10. 10.

    by. 문익점

    반응형

    oop의 구성요소 중에 상속이 있습니다. 상속은 클래스를 정의 할 때 부모 클래스의 정의 된 필드와 메소드들을 말 그대로 이어 받아 정의 할 수 있는 것 입니다. 하지만 이 상속은 오히려 변경의 유연함을 해치므로써 코드의 유지보수에 문제를 야기하게 됩니다.

    코드의 유지보수에 문제가 생기는 것은 치명적인 단점입니다. 그래서 상속 보단 조립을 사용해라! 라는 말이 있습니다. 아래는 그에 대한 설명입니다.

    상속의 사용

    상속을 통하여 하위 클래스를 정의하는 것은 상위 클래스의 기능을 오버라이딩(재정의)하거나 재사용하는 방식으로 사용됩니다.

    간단한 예시 입니다. BankAccount는 Asset에서 확장되고 있고 SavingAccount는 BankAccount에서 확장되며 정의 되고 있습니다. 즉 상속은 하위 클래스를 만들 때 상위 클래스의 정의 된 필드, 메소드에서 메소드를 재사용하거나 재정의하고 필드를 추가하며 정의하게 됩니다.

    상속의 문제점

    상위 클래스가 하의 클래스들에게 의존되어 변경이 어렵다.

    어떤 클래스를 상속받는다는 것은 해당 클래스에 의존한다는 뜻입니다. 즉 의존하는 클래스의 코드가 변경되면 영향을 받을 수 있습니다. 이 말은 상위 클래스의 변경 여파가 상속의 계층도를 따라 전파 되어 모든 하위 클래스에 전파가 되는 것입니다. 결국 상위 클래스의 변경이 어렵게 되는 것입니다.\

    클래스가 너무 많아진다.

    무엇가 새로운 조합이 생길 때마다 하위 클래스를 생성해야 합니다. 즉 위 그림처러 BankAccount의 하위 클래스로 다양한 종류의 Account들(Saving, Checking)이 생성되어 있습니다. 만약 새로운 기능이 추가된 개념의 Account가 생긴다면 계속해서 계층도가 생성되며 클래스가 너무 많아지는 현상이 생깁니다.

    상속을 오용할 수 있다.

    다음은 유저목록 클래스이다. 이 클래스는 기능을 직접 구현하지 않고 Array 클래스가 제공하는 기능을 상속 받아서 사용하고 결정했습니다.

    class UserList extends Array {
      private maxSize: number;
      private currentSize: number;
    
      constructor(maxSize: number) {
        super();
        this.maxSize = maxSize;
      }
    
      public put(user: User) {
        if (!this.canContain()) {
          throw new NotEnoughSpaceException();
        }
        super.push(user);
        this.currentSize += 1;
      }
    
      public extract(user: User) {
        super.splice(super.indexOf(user), 1);
        this.currentSize -= 1;
      }
    
      public canContain = () => this.maxSize >= this.currentSize;
    }

    이 유저목록에 유저를 등록하는 방법은 다음과 같습니다. 유저목록에 유저를 추가하는 올바른 방법입니다.

    let userList = new UserList(5);
    if(userList.canContain()) {
        userList.put(user)
    }

    이제 이 클래스를 정의한 개발자가 아닌 다른 개발자가 사용 한다는 관점에서 보겠습니다. IDE나 에디터에서 일반적으로 이 클래스를 사용 하려고 객체를 생성하고 참조하게 되면 클래스가 가진 메소드들을 볼 수 있습니다.

    상속 받았던 Array가 가지고 있는 메소드들을 볼 수가 있습니다. 이 개발자는 결국 유저 목록에 유저를 추가할 수 있는 메소드가 무엇인지 알 수가 없게 됩니다.

    상속은 IS-A 관계가 성립할 때에만 사용해야 하는데, 유저 목록은 Array가 아닙니다. 유저 목록은 유저 객체를 보관하는 역할을 가진 것이지 어떠한 요소를 보관하는 역할을 가진 것이 아닙니다. 이는 상속의 오용입니다.

    그래서 상속보단 조립(Composition)

    조립은 기능을 재사용하고 싶은 클래스가 있으면 그 클래스의 객체를 필드에 선언 하거나 필요한 시점에 생성하여 사용 하는 것 입니다. 즉 하나의 객체가 다른 객체의 기능을 사용하기 위해 필드로 가져와서 사용된다는 의미에서 조립이라고 볼 수 있습니다.

    https://koseungbin.gitbook.io/wiki/books/undefined/part-1./undefined-3

    위 방식을 사용하게 된다면 앞서 제시한 상속을 사용 할 때 발생 할 때 문제점 3가지를 해결 할 수 있습니다. 앞선 그림 처럼 Storage는 CompressorStroage, EncryptorStrage를 정의 하기 위해 상속을 사용하는 것이 아니라 Storage 클래스에서 Compressor, Encryptor를 사용 될 때 가져오거나 필드에 가져와서 조립해 사용하면 된다.

    즉 이러한 방식들은 상위 클래스가 하의 클래스들에게 의존되어 변경이 어렵다, 클래스가 너무 많아진다, 상속을 오용할 수 있다와 같은 문제점들을 해결하며 OOP를 사용 할 수 있게 됩니다.

    반응형