다형성
다형성 정의
- 다형성(Polymorphism)이란 "여러 개의 형태를 가진다"의 의미의 그리스어에서 유래된 말로서 특정한 심벌이나 연산자에 대해 상황이 다르면 그 의미도 다르게 부여할 수 있는 특성을 말한다.
연산자의 경우 예로 플러스 기호(+) 일반적으로 두수를 더하라는 의미로 사용되지만, 불린검색에서는 논리 연산인 and를 의미한다. 객체지향언에서는 연산자 오버로딩이라는 용어로 다형성을 표현한다.
객체지향에서는 이러한 다형성을 오버로딩(overloading)과 오버라이딩(overriding) 두가지 형태를 모두 포함하고 있다.
오버로딩은 다시 연산자 오버로딩과 함수 오버로딩으로 분류하고 있다.
연산자 오버로딩이란 특정 연산자가 상황에 따라 연산자의 의미나 용도가 달리 쓰이는 현상을 말한다.
예) 플러스 연산자(+)
함수 오버로딩이란 동일 이름의 함수가 상황에 따라 달리 반응하는 현상을 말한다.
함수 오버로딩은 두 종류의 형태로 정의될 수 있다. 하나는 한 클래스 내에서의 함수 오버로딩이고, 다른 한 경우는 상속 구조에서의 함수 오버로딩이다.
한 클래스 내에서의 함수 오버로딩은 한 클래스 내에서 동일 이름의 함수가 여러번 중복 정의되어 있는 경우를 말한다.
이 경우는 몇가지 제약사항이 있다.
1. 함수의 명은 동일해야 한다.
2. 중복 정의되는 함수의 매개 변수 개수나, 매겨 변수 타입이 달라야 한다.
class function_overloading {
int x, y;
double a.b;
public void f1() {
x = 10;
y = 10;
}
public void f1(int a) {
x = 10+a;
y = 10;
}
public void f1(double c, double d) {
a = c;
b = d;
}
}
상속구조에서 부모 클래스가 추상 클래스이며, 추상클래스에 정의된 함수는 추상 함수 형태를 취한다.
추상클래스 내에 추상 함수가 정의되어 있다.
하위 클래스는 추상 함수를 통해 다르게 구현되어 있다.
함수가 각각 서브클래스들에서 서로 다르게 구현되어 있기 때문에 실제 특정 객체의 함수가 호출되는 경우는 프로그램 실행 시간에 결정된다. 이러한 의미에서 다형성을 동적바인딩(Dynamic Binding or Late Binding)이라고도 한다.
또한 이 경우는 한 클래스 내의 함수 오버로딩과 달리 오버로딩되는 모든 함수의 명칭과 매개 변수 개수 및 타입이 동일해야 한다.
abstract class Instrument {
public abstract void play();
}
class piano extends Instrument {
public void play() {
System.out.println("playing piano");
}
}
class drum extends Instrument {
public void play() {
System.out.println("playing drum");
}
}
class flute extends Instrument {
public void play() {
System.out.println("playing flute");
}
}
다형성의 함수 오버라이딩(함수 재정의)이다.
함수 오버라이딩은 상속 구조에서만 일어난다.
상속 구조에서의 함수 오버로딩과 차이점은 함수 오버로딩은 부모 클래스가 추상 클래스이고, 추상 함수에 대해서 오버로딩이 일어나는 것이다.
함수 오버라이딩은 상속구조에서 부모클래스가 추상 클래스이든 일반 클래스이든 상관없으며, 부모 클래스 내에 구현된 함수에 대해서 함수명만 상속받고 함수 구현은 서브 클래스들에서 각각 재정의하는 것을 말한다.
class Arc {
void draw() {
System.out.println("Drawing arc...");
}
void erase() {
System.out.println("Erasing arc...");
}
}
class Oval extends Arc {
void draw() {
System.out.println("Drawing oval...");
}
void erase() {
System.out.println("Erasing oval...");
}
}
class Circle extends Arc {
void draw() {
System.out.println("Drawing circle...");
}
void erase() {
System.out.println("Erasing circle...");
}
}
다형성의 특성
1. 코드의 재사용성을 높여준다.
- 다형성이 지원되지 않는 상황을 생각해보자. 예를 들어 '+' 기호를 여러가지 목적에 사용해야 한다고 가정했을 경우, 함수에 전달된 매개 변수의 데이터형을 일일이 확인해서 매개 변수 유형이 정수이면 덧셈을 수행하는 함수를 호출하고, 문자열이면 문자열 연결을 수행하는 함수를 호출하는 조건문을 프로그래머가 일일이 작성해야 한다.
만일 프로그램의 규모가 커지면 이러한 조건문이 등장하는 경우가 빈번히 발생할 뿐만 아니라 필요한 조건을 프로그래머가 코디하는 것을 놓치는 경우가 발생할 수 있다.
따라서 결국 프로그램의 재사용성이 떨어진다. 이러한 부분을 다형성이 해결해 줌으로써 프로그램이 매우 간결해지며 복잡도 또한 떨어진다.
2. 일관된 인터페이스를 제공한다(일관된 코드를 유지한다)
- 자바의 효율적인 다형성을 활용하기 위해서는 상속 구조를 통해 슈퍼클래스가 추상클래스나 인터페이스 형태를 취하는 것이 바람직하다.
이렇게 함으로써 하위 서브 클래스들이 슈퍼 클래스에 종속되는 것을 피할 수 있다.
그리고 슈퍼클래스에는 간단히 함수 선언 정도만 다루고, 하위 서브 클래스들이 상세한 함수 구현을 해야한다.
이를 다른 말로 "인터페이스에 프로그램한다"라고 한다.
이렇게 되면 이 클래스를 접근하는 다른 객체들은 하나의 통로(인터페이스 메서드나 추상 메서드)로 다양한 유형의 객체들을 접근 할 수 있다. 즉, 외부객체는 해당 메서드를 구현하는 객체들이 누군지 일일이 신경쓰지 않고도 하나의 메서드로 여러 유형의 객체들을 동적으로 사용할 수 있다.
예) 워드 프로세스를 사용하면서 파일을 출력해야할 경우, 어떤 프린트가 연결되었는지 신경 쓸 필요없이 인쇄 버튼 하나로 그때 시스템에 연결된 프린터를 동적으로 접근하여 인쇄할 수 있다. 파일 인쇄를 하기 위해 연결된 프린터에 따라 달리 메서드를 호출할 필요가 없는 것이다.
3. 모듈화를 높여준다.
- 다형성을 사용하게 되면 유사한 처리를 여러 개로 모듈화하여 처리해야 할 때 매우 중요하고 편리하게 사용할 수 있다.
예) 한 클래스 내에서 객체를 생서하는 생성자 함수를 매개 변수의 개수나 유형에 따라 달리 여러 개 정의할 수 있다.
이 경우, 생성자 함수이므로 객체를 생성하는 기능을 갖기 때문에 동일한 처리일 수 있다.
그러나, 다형성이 없으면, 함수의 이름을 각각 달리 정의해야 하지만, 다형성을 통하면 동일한 이름의 함수로 하되 매개변수의 개수나 유형만 달리 표현하면 된다.
class Customer {
public Customer() { ... }
public Customer(String name) { ... }
public Customer(String name, String pwd) { ... }
}
UML
함수명이 동일하고 매개변수의 개수나 타입을 다르게 설계할 수 있다.
public class Cash {
public void pay() { ... }
public void withdraw(String account, float money) { ... }
pulbic void withdraw(String account, float money, float balance) { ... }
}
public abstract class Payment {
public static void processPayment(Payment p) { p.pay(); }
pulbic abstract void pay();
}
public class CreditCard extends Payment {
public withdraw() { ... }
public void pay() {
// 신용카드 지불방식
}
}
public class Cash extends Payment {
public withdraw() { ... }
public void pay() {
// 현금 지불방식
}
}
public class Point extends Payment {
public withdraw() { ... }
public void pay() {
// 포인트 지불방식
}
}
public abstract class Payment {
public boolean checkPay(String id) { ... }
public abstract void pay ();
}
public class Cash extends Payment {
public boolean checkPay(String id) {
//현금 지불 체크 구현
}
}
정적바인딩/동적바인딩
정적바인딩
- 컴파일시에 호출될 함수가 결정(바인딩)
- 실행시에는 컴파일 시점에 결정된 함수를 호출
class A {
void f1()
}
class B {
void f1()
}
class Test {
void test1() {
A a = new A();
a.f1();
B b = new B();
b.f1();
}
}
동적바인딩
- 컴파일시에는 호출될 함수가 결정되지 않음
- 실행시 호출될 함수가 결정(바인딩)
- 추상함수를 구현한 구현클래스의 함수를 동적으로 호출
abstract class A {
abstract void f1();
}
class B extends A {
void f1() { ... }
}
class C extends A {
void f1() { ... }
}
public class Test {
void test1(A a) {
a.f1();
// a.f1() 호출이 B 클래스의 f1() 함수일지 C 클래스의 f1()일지는 실행시에 test1() 함수로 전달되는 클래스에 따라 동적으로 결정됨
}
}
정리
- 다형성은 특정 심벌이나 연산자에 대해 상황별로 의미를 달리 부여하는 장치이다.
- 다형성은 오버로딩과 오버라이딩으로 분류된다.
- 다형성을 동적 바인딩이라고 한다.
- 다형성은 코드의 재사용성을 높여준다.
- 다형성은 일관된 인터페이스를 제공한다.
- 다형성은 모듈화를 높여준다.
출처-UML과 JAVA로 배우는 객체지향 설계 및 구현