컴포지션이란 무엇인가?
컴포지션은 각기 다른것으로 큰 하나를 만들 수 있는 것을 말한다. 소프트웨어로 외발 자전거를 만든다고 할 때, 페달 객체와 타이어 객체를 조립해 구성하는 것도 컴포지션이라 할 수 있다.
오브젝티브 c 에서는 객체를 가리키는 인스턴스 변수로 컴포지션을 만들 수 있다.
참고
앞의 포스팅 Shape Object 프로그램에서 이미 컴포지션을 사용했다. Shape 클래스는 사각형과 색깔로 된 요소를 사용하고 있다. 그러나 엄밀히 따지면 객체를 사용하는 것만 컴포지션에 해당한다. int, float, enum, struct 와 같은 기본타임은 그냥 객체의 한부분이라고 생각하면 된다.
NSLog 수정
NSLog()는 객체를 출력하기 위해 %@ 형식 지정자를 사용한다. %@지정자를 처리할 때 객체의 출력을 위해서 파라미터에 객체를 요청한다. 기술적으로, NSLog()는 description 메시지를 객체에 보내고, 객체의 description 메소드는 NSString을 만들어 그 값을 반환한다. 그러면 NSLog()는 그 문자열을 출력하게 된다. 우리가 만들 클래스가 description 메소드를 제공함으로써 NSLog()에서 객체가 출력되는 내용을 수정했다.
참고
객체 배열을 관리하는 코코아 NSArray 클래스의 description 메소드의 경우는 배열이 가지고 있는 객체 수와 각 객체의 설명 등 배열 자체에 대한 정보를 반환한다. 객체의 대부분에 description 메시지를 보내면 객체가 가지고 있는 대략적인 정보를 얻을 수 있다.
자동차 만들기
각 자동차 객체는 엔진과 타이어를 가리키는 포인터를 위한 메모리를 할당한다. 엔진과 4개의 타이어 모두가 자동차에 들어가는 것이 아니고, 메모리 어딘가에 있는 다른 객체를 가리키는 포인터를 참조한다. 새로운 Car가 할당되면 이 포인터들(engine 과 tires 변수)은 그 자동차가 엔진과 타이어를 아직 가지고 있지 않았다는 것을 나타내는 nil(0의 값)로 초기화 된다. 자동차가 그냥 받침대 위에 얹혀 있는 모습을 상상하면 된다.
이제 init 메소드를 보자. init 메소드는 1개의 엔진과 4개의 바퀴를 가지고 있는 자동차 한대를 만든다. new를 사용해서 새 객체를 만들 때, 실제로 두 단계의 작업이 일어난다. 첫 번째는 그 객체가 할당되는데, 이 할당이라는 말은 인스턴스 변수를 담는 메모리 영역을 확보한다는 의미다. 그리고 두번째로 init 메소드가 자동으로 호출되면서 그 객체를 사용 가능한 상태로 만든다.
접근자 메소드
접근자 메소드를 사용해서 코드를 개선하고, 더 유연하게 만들어 볼 수 있다. (CarPart-Accessors)
경험 많은 프로그래머가 Car 클래스의 init 메소드를 본다면 왜 자동차가 자신의 타이어와 엔진을 직접 만들지? 라고 할 수도 있다.
다른 종류의 타이어나 엔진을 사용할 수 있다면 훨씬 좋은 프로그램이 될 것이다.
특정한 타이어나 엔진을 사용할 수 있도록 자동차에게 알려줄 수 있게 되면, 사용자는 자신만의 자동차를 만들기 위해 부품을 조립할 수 있게 된다.
접근자 메소드는 객체의 특정한 속성을 읽거나 변경한다. 예를 들어 ,Shapes-Object 에서 setFillColor 는 접근자 메소드다. 만을 Car 객체에 엔진을 바꾸기 위한 메소드를 추가한다면 그 메소드도 접근자 메소드가 될 것이다. 이런 종류의 접근자 메소드는 객체의 값을 설정하기 때문에 특별히 세터 메소드라고 한다.
게터는 속성값을 가져오기 위한 코드를 제공하는 메소드다. 게터 메소드는 메소드 이름에 get을 사용하지 않는다는 것을 주의하자. 세터 메소드는 메소드 앞에 set을 붙인다.
참고
다른 객체의 속성을 처리해야 하는 경우에는 반드시 제공된 접근자 메소드를 사용해야 한다. 객체의 값을 직접 변경하면 안된다. 예를 들어, main 함수가 엔진을 바꾸기 위해 , car->engine을 사용해서 car의 인스턴스 변수를 직접 바꾸는 일이 없어야 한다. 엔진을 바꾸기 위해서는 세터 메소드를 대신 이용해야 한다.
엔진 세팅
게터 메소드인 engine은 인스턴스 변수인 engine의 현재 값을 반환한다.
오브젝티브 c 에서 모든 객체의 상호작용은 포인터를 통해서 일어나기 때문에 engine 메소드는 Car가 가지고 있는 엔진 객체를 가리키는 포인터를 반환한다.
그와 비슷하게 세터 메소드인 setEngine: 은 인스턴스 변수인 엔진의 값을 새 엔진이 가리키는 포인터값으로 설정한다.
실제 엔진 자체는 복사되지 않고, 단지 엔진을 가리키는 포인터 값만 복사된다.
달리말하면 Car 객체의 setEngine 을 호출하고 나면 한개의 엔진만이 존재한다. 엔진을 복사해서 사용하는 것이 아니다.
접근자는 실제로 아래와 같이 사용할 수 있다.
자동차 변경사항 추적
Car는 이제 엔진과 타이어에 대한 접근자 메소드를 가지고 있기 때문에, init 메소드에서는 아무것도 만들 필요가 없다.
자동차를 만드는 코드는 엔진과 타이어들을 처리한다.
사실 Car에서 더 이상 필요하지 않기 때문에, init 메소드 전체를 없애 버릴 수 도 있다.
새 차를 갖는 사람들은 엔진과 타이어 없는 차를 갖게 되겠지만, 실제 세상에서는 이상할지 몰라도, 소프트웨어 세상에서는 자주 있는 일이다.
Car는 더 이상 엔진과 타이어를 만들지 않기 때문에, init 이 아닌 main 에서 만들도록 업데이트 되어야 한다.
main 메소드를 위와 같이 바꾸자.
CarParts 확장
다른 엔진을 만들고 싶으면 위와 같이 상속하면 된다.
Slant6은 엔진의 일종이기 때문에 Engin의 서브클래스가 된다.
상속은 수퍼클래스(Engine)가 가지고 있는 내용을 모두 서브 클래스가 상속한다는 것을 잊지 말자.
Car의 setEngine: 메소드가 Engine 타입으로 인수를 받아들이기 때문에 Engine을 상속 받은 Slant6은 안전하게 전달 될 수 있다.
Slant6은 새 메시지를 출력하기 위해 decription을 오버라이드 했기 때문에 (수퍼클래스의 description을 실행하지 않고) 상속 받은 description이 새 메소드로 대치된다.
AllWeatherRadial 이라는 새 타이어 클래스는 아래와 같이 구현한다.
마지막으로 새 엔진과 타이어를 사용할 수 있도록 main 함수를 수정한다.
컴포지션이냐 상속이냐
상속은 ~은 ~의 일종이다. (is a) 라는 관계를 설정한다. 삼각형은 도형의 일종이다. Slant6은 엔진의 일종이다. X는 Y의 일종이다. 라고 말할 수 있으면 상속을 사용할 수 있다.
컴포지션은 ~은 ~을 가진다. (has a) 라는 관계를 설정한다. 도형은 색을 가진다. 자동차는 엔진과 타이어를 가진다. X는 Y를 가진다 라고 할 수 있으면 컴포지션을 사용할 수 있다.
ios와 맥 os x 개발을 위한 오브젝티브 -c 2판 도서 참고
'아이폰 개발 > Objective C' 카테고리의 다른 글
메모리 관리 (0) | 2021.02.22 |
---|---|
상속 예제 (0) | 2021.02.01 |
객체지향 프로그래밍 예제 (0) | 2021.02.01 |
objective c 프로토콜 3 - 비공식 프로토콜 (0) | 2020.12.31 |
objective c 프로토콜 2 - 프로토콜의 채용, 상속 카테고리화 (0) | 2020.12.31 |