객체지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용한다. 유연하고 변경이 용이하다는 말은 컴퓨터 부품을 갈아 끼우듯이 구성요소를 쉽고 유연하게 변경하면서 개발할 수 있는 방법을 뜻한다. 그 방법의 핵심은 다형성이라고 할 수 있다.
프로그램 언어의 각 요소들(상수, 변수, 식, 오브젝트, 함수, 메소드 등)이 다양한 자료형(type)에 속하는 것이 허가되는 성질을 가리킨다.
쉽게 말해 하나의 객체에 여러가지 타입을 대입할 수 있다는 말인데 실세계와 비유를 통해 이해한다면 더욱 받아들이기 쉬울것이다.세상을 역할과 그 역할을 수행하는 구현으로 구분한다. 여기서 역할은 인터페이스, 구현은 인터페이스를 구현한 객체라고 보면 된다.
운전자와 자동차의 예를 들어보자. 운전자는 자동차의 종류와 상관없이 자동차 운전방법만 알고 있다면 어느 자동차라도 문제없이 탈 수 있을 것이다. 모든 자동차들은 자동차의 역할에 맞는 인터페이스 맞춰 동일하게 구현되어있기 때문이다. 따라서 운전자는 자동차 인터페이스, 역할만 알고 있으면 된다. 이렇게 설계된 까닭은 운전자를 위한 것이다. 왜냐하면 운전자는 각 자동차의 내부구조를 알 필요가 없다. 또 내부구조나 종류가 바뀌더라도 자동차의 역할만 하고 있다면 운전자는 아무런 문제없이 운전할 수 있다. 이로써 자동차 세상은 무한한 확장이 가능해진다. 새로운 엔진이 나오고 새로운 기술이 나와도 자동차 역할만 하고 있다면 어떤 자동차가 나와도 문제없기 때문이다. 프로그램으로 생각해본다면 클라이언트에 영항을 주지 않고 새로운 기능을 제공할 수 있다는 의미이다. 이것이 가능한 이유는 역할과 구현을 완벽하게 구분했다는 데에 있다.
이렇게 역할과 구현을 구분하면 프로그램이 단순해지고, 유연해지며 변경도 편리해진다. 다형성의 장점을 다시한번 정리해본다면
실제 다형성을 활용한 코드를 작성해보자.
interface Fightable {
void move(int x, int y);
void attack(Fightable fightable1, Fightable fightable2);
}
class Fighter implements Fightable {
String name;
public Fighter(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
@Override
public void move(int x, int y) {
System.out.println(x + ", " + y + "로 이동");
}
@Override
public void attack(Fightable fightable1, Fightable fightable2) {
System.out.println(fightable1 + "이 " + fightable2 + "를 공격");
}
}
public class Main {
public static void main(String[] args) {
Fightable f1 = new Fighter("Player1");
Fightable f2 = new Fighter("Player2");
f1.move(1, 2);
f1.attack(f1, f2);
}
}
이처럼 다형성을 활용하게되면 여러객체를 하나의 타입으로 관리가 가능하기 때문에 확장성이 높아지고 코드 관리가 편해 유지보수에 용이하다.