6. OOP and Encapsulation / 7. Composition and Aggregation / 8. Inheritance and Polymorphism

Cute_Security15·2024년 6월 16일
0

디자인패턴

목록 보기
2/2

Extreme C 5~8 장 중, 6장/7장/8장 내용을 초록

6. OOP and Encapsulation

oop 는 최신 oop language 들에만 해당되는 내용이고, old 한 언어, C 언어에겐 해당사항이
없다고 생각될수 있으나, 사실 oop 는 언어가 아닌, 사고하는 방식을 뜻한다.

먼저 oop 는 2가지 방식으로 구현될수 있고, 전자는 ruby, 후자엔 c++ 와 java 가 있다.

  • prototype-based 와 class-based
  • object 의 attribute 가 runtime 에 추가될수 있다면 prototype-based
  • object 의 attribute 가 compile time 에 고정되고, value 만 변한다면 class-based

c 언어는 oop 언어와 기계어 사이에 있는 언어라, oop 언어가 아니지만
몇가지 생각체계를 갖고 작성할 경우,
oop 스럽게 짤수 있다. (user 추가 type, UDT 를 'class' 처럼 작성)

그럼, oop 스럽게 짜야하는 이유가 무엇일까?

oop 스럽게 짠 다양한 c 기반 opensource 들이 있기 때문이다.
그들의 성공이 oop 스러운 c 가 내재한 강력함을 암시하고 있다.

c 를 oop 스럽게 짤땐

1) type 을 file 별로 관리한다. 구현(.c) 과 public interface(.h) 로 나눈다.

  • 구현에서 필요하진 않지만, 의존성 체크를 위해, public interface 를 include 한다.
  • public interface 에 공개되지 않은 attirubte 는 외부에서 접근할수 없다.

2) behavior 함수의 첫번째 인자로 object 의 opaque pointer 를 받는다.

attribute 와 behavior 를 묶는데 사용되는 도구를 oop 언어에서 제공받으면
좀더 편하게 작성하겠지만, 그 도구들이 없이도 c 에서도 가능하다.

c oop 작성에 있어서 backward compatibility 고려

public interface 에 attribute 를 공개하게 될땐, 항상 backward compatibility 를 고민해야 한다.
왜냐면 공개된 attribute 는 다른 구현들이 쓸수 있다는 뜻이므로, 변경이 어렵게 된다는 걸 뜻하기 때문이다.

7. Composition and Aggregation

type 사이는 관계를 to-have 와 to-be 로 구분할수 있다. 여기선 먼저 to-have 관계를 다루겠다.

  • to-have : 한 type 이 다른 type 의 opqaue pointer 를 가진 관계

to-have 관계는 composition 과 aggregation 둘로 구분이 될수 있다.
이 둘을 구분할땐 object 관점에서 보면 확연히 드러난다.

object 관점에서의 고민

한 object 가 다른 type 의 opaque pointer 를 가지고 있다는 것은,
다른 type object 의 life cycle 을 '어떻게 다룰지' 에 대한 고민으로 이어지게 된다.

  • opaque pointer 는 object 의 크기를 알수 없기 때문에,
    임의로 instantiate 하거나 free 하는게 "불가" 하고
    반드시 type 이 제공한 public interface 를 사용해야 한다.

composition 관계

먼저 object 가 다른 object 를 '소유' 한다고 보면, 그 관계는 composition 이 된다.
object 는 소유한 object 의 life cycle 을 관리할 "책임" 을 갖게 된다.

따라서 object 가 만들어질때, 자신이 소유한 object 까지 같이 만들고,
object 가 사라질땐, 그전에 자신이 소유한 object 까지 같이 제거한다.

composition 관계에서 빌드는 다음과 같은 순으로 진행될수 있다.

car 가 engine 을 소유하는 composition 관계를 예로 들면,
1) car 의 구현이 engine 의 public interface 를 사용
2) engine 의 public interface 는 engine 의 구현을 필요로 함
--> 결국 car 와 engine 의 구현이 최종 바이너리 링크 타임에 합쳐지게 됨

aggregation 관계

다음으로 object 가 다른 object 를 소유하지 않고, '일시적으로' 사용만 한다고 보면,
그 관계는 aggregation 이 된다.

따라서 일시적으로 사용되고 있는 object 의 life cycle 관리는 제3의 함수 (ex. main) 에게
주어지게 된다.

  • opaque pointer 를 가진 type 에게 life cycle 관리 "책임이 없다" 는게 주요 차이점

aggregation 관계에선 opaque pointer 가 NULL 일수도 있으니, 사용하는 쪽에선 주의가 필요

object 간의 관계는 '일시적으로' aggregation 이지만, type 측면에서 보면 '영구적으로' aggregation 관계가 된다.

  • 그리고 이는 composition 에도 동일하게 적용된다.
    한번 composition 관계인 type 은 영구적으로 composition 관계이다.

8. Inheritance and Polymorphism

inheritance 는 to-be 관계로, is-a 로 표현할수 있는 두 type 을 뜻한다.

inheritance 는 또 "extension relationship" 으로 불리기도 하는데,
이는 type 에 새로운 attribute 를 '추가' 하는것만 가능하기 때문이다.

c 에선 2가지 방식으로 Inheritance 구현이 가능하다.

1) parent type 의 public interface 를 사용하지 않는 방법

  • inherited attribute + parent type 의 behavior 를 재사용 가능

2) public interface 를 사용하지 않는 방법

  • inherited attirbute 에만 접근가능, parent type 의 behavior 는 재사용 불가

parent type 의 public interface 를 사용하지 않는 방법

먼저 첫번째 방법은 child type 내에 parent type 'variable' 을 넣는게 핵심이다.

문제는 parent type 이 incomplete data type, 즉 정의를 모르므로, type 크기를 알수 없어
메모리 할당이 불가하다.

  • public interface 에는 type 정의가 없음

따라서, parent type 크기를 알려주기 위해, chlid type 구현에서 include 할수 있는
private header 를 작성하게 된다.

또한, 이때 parent type variable 은 반드시 첫번째 field 여야 한다.

그래야 두 opaque type 포인터 (parent, child) 가 동일 메모리 주소를 가리키게 되고,
덕분에 parent type 의 behavior 를 child 에서도 재사용이 가능하게 된다.

parent type 의 public interface 를 사용하는 방법

두번째 방법은 public interface 를 사용하는 방법이다.
이 경우, parent type 수정시 child type 을 신경쓰지 않아도 되는 장점이 있으나,
(child 는 parent type 정의를 전혀 모르므로)

대신 pointer upcast 가 불가하므로, parent type 의 behavior 를 재사용하는게 불가하고,
별도의 interface 를 정의해서, parent public interface 의 wrapper 처럼 이용하게 된다.

두 방법 모두 child object 가 parent object 의 life cycle 을 관리하고 있으므로,
근본적으로 (intrinsically) composition 관계가 된다.

비교

두 방법을 비교해보면,

첫번째 방법은, child 가 1명의 parent 만 가질수 있다는 단점이 있으나,
대신 parent 의 behavior 를 그대로 사용하고 싶을때 쓰면 용이하다는 장점이 있고,

두번쨰 방법은, parent behavior 를 쓰려면 child 쪽에 별도의 wrapping 함수들을
만들어야 한다는 단점이 있으나,
대신 child 가 n명의 parent 를 가질수 있고, parent 수정에 child 가 독립적이며,
parent 가 child 의 첫번째 field 여야 한다는 제약도 없다는 장점이 있다.

Polymorphism

Polymorphism 은 기존 type 에 새로운 동작(morph) 를 추가하고 싶을때 사용하게 된다.

  • ex) Animal type 의 새로운 동작을 정의하는 Cat, Duck

따라서 subtype 을 작성하게 되는데, subtype 의 동작을 parent type 에게 넘기기 위해,
parent type 내부에는 function pointer 를 담을 placeholder 를 정의하게 된다.

child object 은 초기화 시, parent object 도 초기화 하는데,
이 때 function pointer 를 자신의 함수로 "override" 하게 된다.

따라서 c에서 Polymorphism 을 쓰려면, upcast 가 가능한 첫번째 interitance 사용이 필요하다.

  • child type pointer 주소와 parent type pointer 주소가 다르면,
    기존 object (Animal) 에 새로운 동작을 넣는게 불가

parent type 은 placeholder 에 있는 function pointer 를 호출하는 역활만 수행하게 된다.

profile
관심분야 : Filesystem, Data structure, user/kernel IPC

0개의 댓글