우리는 다형성이 어떤 의미인지, 그리고 사용했을 때 어떤 장점과 단점이 있는지에 대해 알아보았습니다.
이제는 이것을 어떻게 활용이 되는지 알아보도록 하겠습니다.
우리는 Mage라는 마법사 캐릭터를 만들 예정입니다.
그리고 이 마법사는 Fire, Ice, Light라는 마법공격을 사용하도록 할 것입니다. 우선 이 과정을 다형성을 활용하지 않고 진행해 볼까요?
클래스 다이어그램으로 보겠습니다.
각 마법 클래스 별로 casting()이라는 메서드가 있고, 이것을 Mage 가 객체화 해서 사용하는 구조라는 걸 알 수 있습니다.
이전보다 코드량이 많아졌지만, 어려워하지 마세요 그냥 우리는 핵심만 보면 됩니다.
위 코드에서 알 수 있는 내용은 마법사가 마법을 쓰기 위해서는 사용하는 마법을 객체화해야 하며, 이것을 담기 위한 형태의 변수가 필요하다는 것입니다.
그리고 만약 사용하고 싶은 마법이 늘어난다면? 그 형태의 개수만큼의 변수를 추가해 줘야 합니다.
상당히 귀찮겠죠?
이번엔 다형성을 활용해 보겠습니다.
Mage 가 사용하는 마법은 모두 특정 주문(Spell)을 외워야 사용이 가능하도록 합니다.
클래스 다이어그램으로 보겠습니다.
Fire와 Ice와 Light는 Spell을 상속받고 있군요.
그리고 Spell의 casting() 메서드를 각각 오버라이드(override) 하고 있습니다. 부모의 특정 메서드를 자식들이 동일하게 들고 있으면 이건 오버라이드(override)라고 했었죠?
Spell을 상속받은 각 마법 객체들입니다.
뭔가 처음 보는 녀석이 또 나왔죠? 여기서 super는 부모 클래스 객체를 의미합니다.
super.casting() 은 부모 객체의 casting() 메서드를 실행하라는 뜻입니다.
부모 객체의 메서드를 실행해 튀어나온 문자를 “번개” 뒤에 붙이겠다는 뜻입니다.
이렇게 부모 클래스에서 작성된 메서드 내용을 Ligit 클래스의 casting()처럼 일부 사용할 수도 있지만 나머지 Ice 나 Fire 클래스의 casting()처럼 전체 내용을 바꿔서 사용해도 아무런 문제가 없습니다.
이제 Mage에서 사용해 볼 시간입니다.
우리가 아는 대로 변수는 하나만 사용해서 세 개의 마법을 사용할 수 있습니다.
그리고 앞으로 새로운 마법이 추가되어도 Spell을 상속하고만 있다면 새로운 변수를 만들 필요가 없죠?
이렇게 다형성을 사용하면 변수를 하나만 사용하여 여러 형태의 클래스 객체를 활용할 수 있습니다.
그래서 다형성을 멤버 필드에 사용했다 하여 우리는 이것을 필드 다형성이라고 부릅니다.
이 다형성을 이용했을 때 유용한 점 한 가지가 더 있습니다. 지난번에 이 내용이 기억나실지 모르겠습니다.
만약 이런 클래스가 100 개라면 100 개의 변수를 만들어야 하고, 이 변수에 이름도 지어줘야 하는 등 번거로운 일들이 많겠지요? 배열을 사용하면 어떠냐 고요? 여기까지 생각하셨다면 프로그래밍에 상당한 재능이 있으신 겁니다. 하지만 아쉽게도 불가능합니다. 그 이유는 나중에 알아보도록 하겠습니다.
이제 이것을 알아볼 때가 되었습니다.
* 기억이 안 난다면 지난 편 다형성을 다시 보시는 것을 추천드립니다.
우리가 배열에 대해 공부했을 때로 다시 돌아가볼까요? 배열은 특정 타입의 데이터가 몇 개가 들어갈 수 있도록 일렬로 된 자리를 만들어 달라고 요청해야 했죠?
여기서 중요한 것은 특정 타입이 몇 개 들어간다입니다.
그런데 ch1, ch2, ch3 변수의 데이터 타입은 제 각각입니다. 그렇기 때문에 배열을 선언할 때 어떤 타입으로 선언하던지 못 들어가는 객체가 생기게 될 것입니다.
하지만 다형성을 이용한다면?
자식 객체는 부모형태의 타입에 들어갈 수 있으니 하나의 데이터 타입으로 통일이 가능해집니다.
위의 코드를 보면 자식객체들이 ParentHouse 형태로 들어갈 수 있기에 배열 선언 시 ParentHouse 형태로 3개가 들어갈 수 있는 배열을 만들 수 있게 되는 것입니다.
다른 예도 들어볼까요?
한 테스트 드라이버가 있습니다.
어떠한 자동차가 들어오면 그 자동차를 타고 이상이 없는지를 확인해 주는 일을 하는 분입니다.
그래서 어떤 차가 오던지 바로 타 보고 이상여부를 판단하여야 하는데 이 분은 자동차의 종류에 따라서 많은 적응이 필요하다고 합니다.
다른 자동차가 오면 오랜 시간 적응 시간이 있어야만 운전이 가능하다고요.
여러 차종이 수시로 들어오는 이 환경에서 여간 난감한 일이 아닐 것입니다. 하지만 이분 나름대로의 사정이 있다고 합니다.
어떤 사정인지 확인해 보니, 이 드라이버는 새로운 차종이 있다면 그 차종을 운전할 수 있는 방법(메서드)을 새로 배워야 하기 때문이었습니다.
그래서 만약 새로운 차종이 들어온다면 이 드라이버는 해당 차종을 다룰 수 있는 방법(메서드를)을 추가로 배워야 하는 것이지요.
코드로 살펴볼까요?
Driver 클래스는 Audi와 Benz 객체를 받아 실행하는 각각의 메서드가 존재합니다.
즉, 사용하는 객체마다의 메서드가 필요하다는 것이죠.
이 상황에 만약 Bmw라는 객체(차량)가 추가된다면, 그에 상응하는 메서드를 추가로 만들어줘야 합니다.
코드 상황으로 보더라도 사용하는 객체가 늘어나면 메서드도 같이 만들어 줘야 하기에 시간이 더 들 수밖에 없습니다.
이번엔 다른 드라이버를 살펴볼까요? 이 분은 이렇게 이야기합니다.
‘자동차 이기만 하면 바로 몰 수 있죠!’
어떻게 이게 가능한 걸까요?
이 분은 차라는 특성만 가지고 있다면 그게 어떠한 자동차 회사에서 나온 차종이든지 운전이 가능하기 때문이라고 하네요.
그래서 만약 새로운 차종이 들어온다 해도, 자동차라는 형태와 규격만 갖추고 있으면 새로운 방법을 배울(메서드 추가) 필요가 없다고 합니다.
이 경우도 코드로 살펴보겠습니다.
우선 각 차량은 자동차의 특성을 상속받은 자식들입니다. 즉, Audi와 Benz는 Car의 자식들이며 이들은 다형성에 의해 Car의 형태로 들어갈 수 있습니다.
그렇기에 Driver 클래스에 drive() 메서드의 매개변수 형태가 Car 가 된다면, 이 안에는 Car의 자식들은 누구라도 들어올 수 있게 되는 것입니다.
새로운 차량이 추가된다 해도 Car 만 상속받았다면 당연히 가능합니다.
어때요? 새로운 객체가 추가될 때마다 이젠 새로 메서드를 만들 필요가 없게 되었죠?
다형성 덕분입니다.
그리고 이렇게 메서드의 매개변수에 다형성을 활용한다 하여, 이것을 매개변수 다형성이라고 부릅니다.
다형성이라는 성질은 똑같지만 필드에 사용했느냐 매개변수에 사용했느냐에 따라 구분해서 부르는 것에 불과 하니 너무 명칭에 신경 쓸 필요 없습니다.
우리는 그저 어떻게 활용하는 것이 더 도움이 될까? 에 대해서만 집중하면 됩니다.
이렇게 자바에서는 OOP 4대 특성을 이용한 여러 가지 편리한 기능들을 제공해 줍니다.
우리는 이런 것들에 대해 이것이 왜 편리하며 어떻게 써야 유용하게 사용할지 고민을 해봐야 합니다.
아무리 좋은 도구도 어떻게 쓰느냐에 따라서 완전히 달라지니 말이죠.