Study/TIL(Today I Learned)

24.08.22 CS, C++

에린_1 2024. 8. 22. 17:03
728x90

CS

상속과 합성

  • 프로그래밍을 할 때 가장 신경 써야 할 것 중 하나가 바로 코드 중복을 제거하여 재사용 함으로써 변경, 확장을 용이하게 만드는 것이다.
  • 상속과 합성은 객체지향 프로그램에서 가장 널리 사용되는 코드 재사용 기법이다.

상속(Inheritance) 합성(Composition)

부모 클래스와 자식 클래스 사이의 의존성은 컴파일 타임에 해결 두 객체 사이의 의존성은 런타임에 해결
is-a 관계 has-a 관계
부모 클래스의 구현에 의존 결합도가 높다 구현에 의존하지 않는다.
내부에 포함되는 객체의 구현이 아닌 인터페이스에 의존한다.  
클래스 사이의 정적인 관계 객체 사이의 동적인 관계
부모 클래스 안에 구현된 코드 자체를 물려 받아 재사용 포함되는 객체의 퍼블릭 인터페이스를 재사용

상속(Inheritance) 이란

  • 상속은 객체 지향 4가지 특징 중 하나로서 클래스 기반의 프로그래밍에서 가장 먼저 배우는 개념이다.
  • 클래스 상속을 통해 자식 클래스는 부모 클래스의 자원을 물려 받게 되며, 부모 클래스와 다른 부분만 추가하거나 재정의함으로써 기존 코드를 쉽게 확장할 수 있다. 그래서 상속 관계를 is-a 관계라고 표현하기도 한다.
  • 상속을 사용하는 경우는 명확한 is-a 관계에 있는 경우, 그리고 상위 클래스가 확장할 목적으로 설계되었고 문서화도 잘되어 있는 경우에 사용하면 좋다.
  • 그러나 상속을 제대로 활용하기 위해서는 부모 클래스의 내부 구현에 대해 상세하게 알아야 하기 때문에 자식 클래스와 부모 클래스 사이의 결합도가 높아질 수 밖에 없다. 또한 상속 관계는 컴파일 타임에 고정되기 때문에 코드를 실행하는 도중에 변경할 수 없다. 따라서 여러 기능을 조합해야 하는 설계에 상속을 이용하게 된다면 모든 조합별로 클래스를 하나하나 추가해주어야 한다. 이것을 클래스 폭발 문제라 한다.

합성(Composition) 이란

  • 합성은 또 다른 말로 조합이나 그냥 콩글리쉬로 컴포지션이라고 불린다.
  • 합성 기법은 기존 클래스를 상속을 통한 확장하는 대신에, 필드로 클래스의 인스턴스를 참조하게 만드는 설계이다. 예를들어 서로 관련없는 이질적인 클래스의 관계에서, 한 클래스가 다른 클래스의 기능을 사용하여 구현해야 한다면 합성의 방식을 사용한다고 보면 된다.
  • 이 방식을 포워딩(forwarding)이라고 하며 필드의 인스턴스를 참조해 사용하는 메소드를 포워딩 메소드(forwarding method)라고 부른다.
  • 그래서 클래스간의 합성 관계를 사용하는데 다른 말로 Has-A 관계라고도 한다. 객체 지향에서 다른 클래스를 활용하는 기본적인 방법이 바로 합성을 활용하는 것이다.
    • 합성을 이용할 때 곡 클래스 뿐만 아니라 추상 클래스, 인터페이스로도 가능하다.

상속의 문제점

  1. 결합도가 높아진다.
    • 결합도는 하나의 모듈이 다른 모듈에 대해 얼마나 많은 지식을 갖고 있는지를 나타내는 의존 정도를 뜻한다.
    • 객체 지향 프로그래밍에서는 결합도는 낮을수록, 응집도는 높을수록 좋다. 그래서 추상화에 의존함으로써 다른 객체에 대한 결합도는 최소화하고 응집도를 최대화하여 변경 가능성을 최소화 할 수 있다.
    • 하지만 상속을 하게 되면 부모 클래스와 자식 클래스의 관계가 컴파일 시점에 관계가 결정되어 결합도가 당연히 높아질 수 밖에 없다.
    • 컴파일 시점에 결정되는 관계는 유연성을 상당히 떨어뜨리고, 실행 시점에 객체의 종류를 변경하는 것이 불가능하여 유기적인 다형성 및 객체지향 기술을 사용할 수 없다.
  2. 불필요한 기능 상속
    • 부모 클래스에 메소드를 추가했을 때, 자식 클래스에는 적합하지 않는 메소드가 상속되는 문제이다. 물론 메소드를 구현하고 빈칸으로 놔두거나, 클래스를 분리하고 분리하여 해결은 할 수 있지만 결국 복잡해질 뿐이다.
      • 물론 이는 인터페이스로 따로 implements하면서 해결할 수 도 있다.
  3. 부모 클래스의 결함이 그대로 넘어온다.
    • 만일 상위 클래스에 결함이 있다고 하면, 이를 상속을 하게 되면 부모 클래스의 결함도 자식 클래스에게 넘어오게 된다.
    • 결국 자식 클래스에서 아무리 구조적으로 잘 설계하였다 하더라도 애초에 부모 클래스에서 결함이 있기 때문에 자식 클래스도 문제가 터지게 된다.
  4. 부모 클래스와 자식 클래스의 동시 수정 문제
    • 부모 클래스와 자식 클래스 사이의 개념적인 결합으로 인해, 부모 클래스를 변경할 때 자식 클래스도 함꼐 변경해야 하는 문제를 말한다.
  5. 메서드 오버라이딩의 오동작
    • 자식 클래스가 부모 클래스의 메서드를 오버라이딩할 때 자식 클래스가 부모 클래스의 메서드 호출 방법에 영향을 받는 문제이다.
    • 부모의 public 메소드는 외부에서 사용하도록 노출한 메소드이다. 그런데 상속을 하게 된다면, 자식 클래스에서도 부모 클래스의 public 메소드를 이용할때 의도하지 않는 동작을 수반할 수 있게 될 수 있다.
    • 이는 캡슐화를 위반하였다고 하기도 한다.
  6. 불필요한 인터페이스 상속 문제
  7. 클래스 폭발
    • 상속을 남용하게 되면, 새롭게 만든 클래스에 하나의 기존의 기능을 연결하기 위해 상속을 하게 될것이고, 또 다시 새롭게 만든 클래스에 기능을 연결하기 위해 상속을 하고, 이렇게 필요 이상으로 많은 수의 클래스를 추가해야 하는 경우를 가리켜 클래스 폭발(class explosion) 문제 도는 조합의 폭발(combinational explosion) 문제라고 부른다.
    • 클래스 폭발 문제는 자식 클래스가 부모 클래스의 구현과 강하게 결합되도록 강요하는 상속의 근본적인 한계 떄문에 발생한다.
    • 상속 관계는 컴파일 타임에 결정되고 고정되기 떄문에 코드를 실행하는 도중에는 변경할 수 없다. 그래서 부모와 자식 클래스의 구현이 강하게 결합된 상속 관계 상태에서, 다양한 조합이 필요한 상황이 오면 결국 유일한 해결 방법은 조합의 수 만큼 새로운 클래스를 추가해 상속하는 것 뿐이다.
    • 클래스 폭발 문제는 새로운 기능을 추가할 떄 뿐만 아니라 기능을 수정할 떄에도 동일하게 발생한다.

합성을 사용해야 하는 이유

  • 합성은 구현에 의존하지 않는 점에서 상속과 다르다.
  • 합성을 이용하면 객체의 내부는 공개되지 않고 인터페이스를 통해 코드를 재사용하기 때문에, 구현에 대한 의존성을 인터페이스에 대한 의존성으로 변경하여 결합도를 낮출 수 있기 때문이다.
  • 상속과 합성은 재사용 기법으로 많이 쓰이는 방법이지만, 대체로 상속을 최소화하고 합성을 사용하기를 권한다.
  • 상속은 자식 클래스 정의에 부모 클래스의 이름을 덧붙이는 것(extends)만으로 코드를 재사용할 수 있으며 쉽게 확장할 수 있다. 그러나 상속을 제대로 활용하기 위해서는 부모 클래스의 내부 구현에 대해 상세히 알아야 하기 때문에 자식과 부모 사이의 결합도가 높아질 수 밖에 없다.
  • 결과적으로 상속은 코드를 재사용하기 쉬운 방법이기 하지만 결합도가 높아지는 치명적인 단점이 있다.
  • 또한 상속 관계는 클래스 사이의 정적인 관계인데 비해 합성 관계는 객체 사이의 동적인 관계이다. 코드 작성 시점에 결정된 상속 관계는 변경이 불가능하지만, 합성 관계는 실행 시점에 동적으로 변경할 수 있기 때문이다. 그래서 합성을 사용하고 인터페이스 타입을 사용한다면 런타임시에 외부에서 필요한 전략에 따라 교체하며 사용할 수있으므로 좀 더 유연한 설계를 할 수 있다.

참조

💠 상속을 자제하고 합성(Composition)을 이용하자

C++

iostream

  • C++에서 표준 입출력에 필요한 것들을 포함하고 있다.
    • ex) std::cout이나 std::endl
  • C언어에서의 stdio.h와 비슷하다고 생각하면된다.

namespace(이름 공간)

  • cout 앞에 붙어 있는 std의 정체는 C++ 표준 라이브러리의 모든 함수, 객체등이 정의된 이름 공간(namespace) 입니다.
  • 이름 공간은 말 그대로 어떤 정의된 객체에 대해 어디 소속인지 지정해주는 것과 동일하다. 코드의 크기가 늘어남에 따라, 혹은 다른 사람들이 쓴 코드를 가져다 쓰는 경우가 많아지면서 중복된 이름을 가진 함수들이 많아졌다. 따라서 C++에서 이를 구분하기 위해, 같은 이름이라도, 소속된 이름 공간이 다르면 다른것으로 취급한다.
std::cout
  • 위의 경우 std 라는 이름 공간에 정의되어 있는 cout 을 의미한다. 만약에 std:: 없이 그냥 cout 이라고 한다면 컴파일러가 cout 을 찾지 못한다.
  • using namespace std; 와 같이 어떠한 이름 공간을 사용하겠다라고 선언하는 것은 권장되지 않는다. 왜냐하면 std에 이름이 겹치는 함수를 만들면 오류가 발생하기 때문이다. 권장하는 방식은 using namespace std; 같은 것은 사용하지 않고, std:: 를 직접 앞에 붙여서 std의 이름공간의 함수이다를 명시해주는 것이 좋다. 또한, 여러분이 작성하는 코ㄷ는 여러분 만의 이름 공간에 넣어서 혹시 모를 이름 충돌로 부터 보호하는 것이 좋다.
  • cout 은 ostream 클래스의 객체로 표준 출력을 담당하고 있다.

이름 없는 이름 공간

  • C++에서는 이름 공간에 굳이 이름을 설정하지 않아도 된다. 이 경우 해당 이름 공간에 정의된 것들은 해당 파일 안에서만 접근할 수 있다. 이 경우 마치 static 키워드를 사용한 것과 같은 효과를 낸다.
728x90

'Study > TIL(Today I Learned)' 카테고리의 다른 글

24.08.26 CS, C++  (0) 2024.08.26
24.08.23 CS  (0) 2024.08.23
24.08.21 CS  (0) 2024.08.21
24.08.20 CS, 디자인 패턴  (0) 2024.08.20
24.08.19 네트워크  (0) 2024.08.19