세번째, 책 고급 프로그래밍 요점 및 정의 정리

다형성(Polymorphism)

다형성은 한 개체가 여러 개의 형태로 변하는 성질을 의미한다.
객체지향에서도 이와 유사한 의미로 다형성이 사용된다.

객체지향 패러다임의 다형성

오퍼레이션이 한 곳 이상에서 정의되어 각 정의된 오퍼레이션이 다른 행위를 수행하는 것을 의미하거나,
두 개 이상의 클래스가 동일한 메소드 호출에 대해 다른 행위를 수행함으로써 다형성을 지원한다.

이는 마치 동물 '짖다'라는 동일한 행위를 가지지만 각 동물 객체마다 다른 짖는 소리를 내는 것과 유사하다.
즉, 개는 '멍멍'이라고 짖지만, 고양이는 '야옹', 닭은 '꼬끼오'으로 짖어,
'짖다'라는 동일한 행위가 다른 방식으로 수행되는 것이다. 이를 다형성이라 한다.

오버로딩(Overloading) - 시그니쳐 다름

메소드 시그니처(Signature)는 이름, 입력 매개변수, 반환타입으로 구성된다.
이 중 이름만 동일하고 메소드가 필요로 하는 입력 매개변수 또는 반환타입이 다른 메소드를 여러번 정의하는 것을 메소드 ‘오버로딩’이라고 한다.
오버로딩의 범위는 한 클래스내에 동일 이름을 가지는 여러 메소드들이다.

오버라이딩(Overriding) - 시그니쳐 같음

메소드 오버라이딩은 이름, 입력 매개변수, 반환 타입은 같지만 메소드가 구체적으로 수행하는 행위가 다른 경우에 해당한다.
오버라이딩은 상속 관계에서 발생하며, 슈퍼 클래스의 정의된 메소드를 서브 클래스에서 목적에 맞게 재정의 할 때에 사용된다.

다형성의 효과(장점)

다형성은 상속과 같이 객체의 재사용성을 높일 수 있는 주요 매커니즘으로 사용된다.

동일 메세지로 목적에 맞는 여러 행위를 호출할 수 있음

메세지를 전달하는 송신 객체에서는 동일한 메세지를 전달해도, 런타임에 목적에 맞게 다른 메소드가 호출될 수 있다.

프로그램 확장성 보장

다형성을 가진 메소드를 정의하면 최소한의 비용으로 기존 시스템에 새로운 클래스를 추가하여 확장성을 증진 시킬 수 있다.
오버라이딩을 통해 이미 슈퍼 클래스에 정의된 메소드를 사용하는게 아니라 목적에 맞게 재정의 할 수 있다.
이에 따라, 새로운 클래스를 기존 상속 구조에 쉽게 추가할 수 있다.
송신 객체에서는 한 메세지로 다형성을 이용하여 여러 메소드를 호출 할 수 있으므로, 송신 객체는 수정이 필요없다.

오버로딩(Overloading)

다음은 오버로드된 메소드들의 예제이다.

void print()
void print(String s)
void print(String s, String v)
void print(Int i, String s)
void print(Int i, String s, String v)

오버로딩의 활용

메소드 오버로딩은 한 클래스에서 동일한 이름을 가진 여러 매소드를 정의해야 할 때 사용되며,
일반 메소드 뿐 아니라 생성자 정의 시에 효과적으로 활용된다.

생성자 정의시 활용
public class Employee {
    // 생략
    
    // 기본 생성자
    publc Employee() {} 
    
    // 매개변수 두개
    publc Employee(String name, int salary) {
        this.name = name;
        this.salary = salary;
        
        employee.add(this);

    } 

    // 매개변수 세개
    publc Employee(String name, String dept, int salary) {
        this.name = name;
        this.dept = dept;
        this.salary = salary;
        
        employee.add(this);
    } 
}
// 다양한 조합의 입력 매개변수로 인스턴스가 생성되는 것을 가능하게 한다.
메소드 정의시 활용

기능성은 동일하지만, 데이터 타입에 따라 이를 처리하는 알고리즘이 다를 경우 메소드 오버로딩을 효과적으로 사용할 수 있다.
‘+’ 연산자는 오버로드된 메소드의 대표적인 예이다.
int, float와 같이 수와 연관된 데이터 타입은 두 수를 더하는 연산을 수행하지만,
string과 같은 문자열 데이터 타입과 사용되면 두 문자를 연결하는 연산을 수행한다.

오버라이딩(Overriding)

상속 관계에서 메소드 시그니쳐가 동일한 메소드를 여러 클래스에 정의하는 장치이다.

객체지향에서의 오버라이딩 장치

오버라이딩에 대해 이해가 쉽도록 파이썬 예제를 작성하였습니다.

class Animal() : 
    def bark(self) :
        print('짖다')


class Dog(Animal) : 
    def bark(self) :
        print('멍멍')
        
        
class Duck(Animal) : 
    def bark(self) :
        print('꽥꽥')

메소드 시그니쳐만 동일하고 구현부를 다르게 정의 함으로써 슈퍼 클래스의 메소드를 여러 서브 클래스에서 오버라이드 할 수 있다.

상위 클래스 참조

서브 클래스에서 메소드가 오버라이드되어 있다 하더라도, 필요에 따라 슈퍼클래스의 동일 메소드를 호출 할 필요가 있다.
서브 클래스의 오버라이드된 메소드에서 슈퍼 클래스의 정의된 동일한 메소드를 호출하는 방법이다.

// java
super.methodName();

// cpp
SuperClassName::methodName();

super 라는 예약어를 통해 슈퍼 클래스에 접근할 수 있으며, 이는 각 언어마다 다를 수 있다.

객체 대치성(Subtitutability)과 동적 바인딩(Dynamic Binding)

메소드 오버라이딩의 큰 장점은 슈퍼클래스의 메소드를 서브클래스에서 재 정의하여 목적에 맞게 재사용 할 수 있다는 점이다.
객체 대치성과 동적 바인딩 메커니즘은 메소드 오버라이딩의 장점이 극대화되기 위해 반드시 필요한 매커니즘이다.

해당 부분은 내용이 긴 부분이므로, 용어정리와 간단한 예제만으로 구성하였습니다.

객체 대치성

상속 관계에 있는 두 클래스 간에서 슈퍼클래스 인스턴스는 서브클래스의 인스턴스로 대체될 수 있음을 의미한다.

동적 바인딩

바인딩(Binding)은 프로그램 내에 정의된 변수나 데이터 등이 가리키고 있는 메모리 주소나 데이터 타입이 결정되는 과정이며,
바인딩이 일어나는 시점에 따라 정적, 동적바인딩으로 구분된다.

- 정적 바인딩 : 컴파일 시점
- 동적 바인딩 : 런타임에 프로그램이 실행되는 시점
// 이 예제는 설명을 위한 예제입니다.

public class A {
    int print() { printf("%s", "A class") }
}


public class B Extends A {
    virtual int print() { printf("%s", "B class") }
}


public class C Extends B {
    virtual int print() { printf("%s", "C class") }
}

위와 같은 상속 구조일 때, classB 타입으로 참조변수 b를 정의하면 classB 또는 classC 인스턴스만 접근 할 수 있으며, classC 타입의 참조변수 c를 정의하면 classC의 인스턴스만 접근 할 수 있다.
그러므로, 객체 대치성과 동적 바인딩의 이점을 최대한 활용하기 위해 상위 클레스에 위치한 참조변수로 접근하는 것이 좋다.

* 왜 동적 바인딩이 필요할까?

정적 바인딩만 지원될 경우, 컴파일 시점에 슈퍼 클래스에 정의된 메소드 또는 서브 클래스에 정의된 메소드를 호출할 지 이미 결정된다.
이와 같은 경우에는 객체 대치성과 같은 매커니즘을 활용 할 수 없게 되며, 메소드 오버라이딩의 활용성 및 장점이 크게 줄어든다.

오버라이딩 = 객체 대치성 + 동적 바인딩

추상 클래스(Abstract Class)

슈퍼 클래스에 정의된 메소드가 서브 클래스들의 공통 기능을 나타내도록 구현하는 것이 어려운 경우가 있다.
이를 위해 추상 클래스라는 개념을 활용한다.

두 가지 수준의 기능 공통성

- 명세 수준의 공통성
여러 클래스들이 기능 명세만 동일하게 필요할 경우(메소드 바디는 다름)

- 구현 수준의 공통성(명세 수준의 공통성을 세분화한 형태)
메소드 시그니쳐와 메소드 바디 모든 부분을 재사용

추상 메소드와 추상 클래스

- 추상 메소드란 메소드의 시그니쳐(이름, 인수, 반환 데이터 타입)만으로 구성된다.
- 추상 클래스는 다른 말로 가상 클래스라고도 하며, 추상 메소드를 하나 이상 가진 슈퍼 클래스의 한 형태이다.
- 추상 클래스는 명세 수준의 공통성과 기능 수준의 공통성을 표현한 메소드를 모두 포함한다.
- 추상 클래스는 객체 생성이 불가능 하고 애초의 목적이 객체 생성 목적이 아니다.
- 서브클래스가 반드시 재정의하는 기능에 대한 명세를 정의하고 있다.
- 추상 클래스를 상속받은 클래스를 구체적 클래스 또는 concrete 클래스라고 한다.

추상 클래스의 장점

- 프로그램 구조에 대한 이해도 향상
- 재사용성(Reusability) 향상
- 확장성(Extensibility) 향상 : 객체 대치성과 동적 바인딩을 이용하여 기존 코드를 적게 수정하며, 새롭게 추가된 Concrete 클래스 메소드에 접근이 용이하다.
- 유지보수성(Maintainability) 향상

추상 클래스 정의 방법

// java
abstract [반환타입][메소드명]([입력 매개변수 목록]);

// cpp
virtual [반환타입][메소드명]([입력 매개변수 목록]) = 0;


cpp에서 virtual 예약어는 두가지의 용도로 사용된다
- 해당 메소드가 동적 바인딩 되어야 함을 나타낼 때
- 해당 메소드가 추상 메소드로 선언되어 Concrete 클래스에서 반드시 이 메소드를 구현해야 할 때

추상 클래스의 활용

- 복수 개의 Concrete 클래스간 공통적인 속성과 메소드는 추상 클래스에 위치 시키고, Concrete 클래스 간에 기능적 명세(메소드 시그니쳐)만 동일하고 그 구현 방식이 다른 메소드는 추상 메소드로 정의한다.
- 각 Concrete 클래스는 추상 클래스를 상속하고, 모든 추상 메소드를 오버라이드하여 각 클래스의 목적에 맞게 정의한다.
- 객체 대치성을 활용하여 추상 클래스 타입의 자료구조(Array, Vector, ArrayList 등)을 정의하고, 자료 구조의 각 요소는 Concrete 클래스 인스턴스를 가리키도록 한다.
- 동적 바인딩을 활용하여 자료 구조의 각 요소를 슈퍼클래스인 추상 클래스 타입의 변수로 접근하도록 하면, 실제 가리키고 있는 인스턴스 타입에 따라 오버라이딩된 메소드가 실행된다.