CodeOnWeb
Hanyang ERICA
Log in

다형성

Baik Jeanseong 2018/11/06, 17:28

CSE2016 프로그램 설계 방법론

추상 클래스

  • 메소드 머리부만 있고 몸체는 없는 미완성 메소드이므로 추상 클래스로부터 객체를 만들 수 없다.
  • 다른 클래스들에 대한 상위 클래스로서만 사용될 수 있다.
  • 보통 메소드들도 포함하고 변수들도 포함할 수 있다.
  • 당장은 유용하지 않은 메소드들을 완성하지 않고 머리부만 정의하는 것으로 작성을 쉽게 한다.
  • 클래스 머리부에 abstract라는 수정자를 포함함으로써 추상 클래스로 선언된다.
  • 하나 이상의 추상 메소드를 포함하는 클래스는 반드시 추상 클래스로 선언되어야 한다.

추상 클래스는 완전하게 정의하기에는 불충분한 추상적 개체를 나타낸다. 따라서 추상 클래스는 완성된 부분들과 미완성된 부분들로 이루어진다. 완성된 부분들은 클래스 계층에서 하위 (혹은 자손) 클래스에 의해 상속된다. 미완성된 부분들은 적절한 하위 (혹은 자손) 클래스에서 완성된다. 예를 들면, 다음 그림에 보여진 클래스 계 층 구조를 고려하라. 그 계층 구조의 맨 위에 있는 Shape 클래 스는 2 차원 공간의 일반적인 모양을 나타내는 클래스이다. 일반적인 모양은 면적파 둘레 를 가지나 그 값들을 구할 수 없다. 따라서 Shape 클래스에서는 면적을 구하는 메 소드와 둘례를 구하는 메소드가 있어야 한다는 정도만 기술한다. 그라고 그 클래스의 하위(혹은 자손) 클래 스에서 그 메소드들을 명확하게 구현할 수 있을 때 완성시키자는 아이디어이다. 즉 Shape 클래 스는 하위 클래 스인 Circle과 Rectangle, 손자 클래스인 Sguare 클래스의 공통적인 특성들의 추상화를 지원한다. Shape 클래스에서 모든 모양을 똑같이 다룸으로서 더 높은 수준의 프로그래밍을 가능하게 한다.

file
[모양 계층 구조]

다음 프로그램이 본 모양 계층 구조의 Shape와 Rectangle 클래스를 구현한다. Shape 클래스는 정의되지 않은 추상 메소드 getArea와 getPerimeter를 포함하므로 추상 클래스로 선언된다. Rectangle 클래스는 상위 클래스인 Shape 클래스의 변수 name과 getName 메소드를 상속받는다. 직사각형의 면적과 물례를 계산할 수 있으므로 메소드 getArea와 getPerimeter를 구현힌다.

코드의 일부분을 고려하라.

첫 번째 문은 맞으나 두 번째 문은 추상 클래스로부터 객체를 만들 수 없으므로 틀린 문이다.

다음 프로그램은 Shape 클래스를 얼마나 많은 모양들이 하위(혹은 자손) 클래스에서 만들어졌는지를 세기 위해 다시 작성한 코드이다.

ShapeDriver2 클래스의 main 메소드는 Shape2 클래스의 getNumShapes 메소드를 다음과 같이 호출하였다.

위 출력문들은 표본 출력에 보여진 것과 같이 지금까지 생성된 Shape 클래스의 하위 클래스의 객체들의 수를 출력하였다.

final 예약어

  • 다형성을 제한하기 위해 final 예약어를 사용한다.
  • 변수에 final 예약어를 적용하면 그 변수는 값이 바뀌지 않는 상수가 된다.
  • 메소드에 final 예약어를 적용하면 그 메소드는 재정의할 수 없다.

이렇게 할 경우 어떤 하위 클래스도 getName() 메소드를 재정의할 수 없다.

인터페이스

  • 여러 개의 독립적인 클래스 계층 구조가 공유하는 기능들을 제공하기 위해 사용한다.
  • public 상수들과 추상 메소드들의 모음이다.
  • 인터페이스내 의 추상 메소드는 public 메소드이고 객체 메소드여야 한다.
  • 인터페이스는 클래스 머리부와 같이 시작하나 클래스 머리부의 예약어 class 대신에 interface를 사용한다.

  • 인터페이스 내의 모든 메소드는 public 메소드이기 때문에 메소드 머리부에서 public을 생략할 수 있다.
  • 인터페이스는 생성자 메소드를 포함하지 않는다. 따라서 인터페이스로부터 객체를 만들 수 없다.
  • 메소드 머리부는 자바 문처럼 세미콜론(;)으로 끝나야 한다.
  • 관례적으로 인터페이스 이 름은 대문자로 시작하고 able나 ible로 끝난다.

인터페이스의 사용 예를 들어 보자. 시험 문제들은 문제마다 난이도가 있다. 예를 들면, 난이도를 1(가장 쉬운)부터 10(가장 어려운) 사이의 정수로 정의할 수 있다. 이와 같이 난이도를 명확하게 부여할 수 있는 객체들을 위 한 인터페이스를 작성해 보자 그 인터페이스는 최소한 난이도를 정의할 수 있는 객체들의 집합에 숫자로 된 난이도를 정하는 메소드와 난이 도를 알려 주는 메소드가 있어야 한다. 난이도를 다루는 기능을 가진 인터페이스는 다음 프로그램에 보여진다.

인터페이스 Complexity는 setComplexity와 getComplexity라는 추상 메소드를 포함한다.

인터페이스 구현

한 클래스는 인터페이스에 정의된 모든 추상 메소드를 완전하게 구현함으로써 그 인터페이스를 구현한다. 한 인터페이스를 구현하는 클래스의 머리부는 마음과 같은 구문을 가진다.

위에서 implements는 예약어이다. 예를 들면 시험 문제를 나타내는 클래스가 다음 프로그램에 보여진다.
이 클래스는 시험 문제의 난이도를 나타낸다. 특히 시험 문제에 대한 난이도를 주어진 값으로 바꿀 수 있어야 하고 난이도의 현재 값을 알려 주어야 한다.

여러 개의 클래스들이 같은 인터페이스를 구현할 수 있다. 이 경우에 각 클래스는 인터페이스의 추상 메소드들을 다르게 구현하여 다형성을 실현한다. 또한 한 클래스는 하나 이상의 인터페이스를 구현할 수 있다. 이 경우에는 그 클래스는 구현하는 인터페이스들에 있는 모든 추상 메소드들을 완전하게 구현해야 한다. 한 클래스가 여러 인터페이스들을 구현한다는 것을 나타내기 위해 인터페이스 이름들이 implements 예약어 다음에 쉽표로 분리되어 나열된다. 예를 들면

인터페이스는 추상 메소드 외에 상수들을 포함할 수 있다. 클래스가 인터페이스를 구현할 때 그 클래스는 그 인터페이스에 정의된 모든 상수들을 사용할 수 있다. 인터페이스는 우리가 클래스와 상호작용할 수 있는 방법들을 공식적으로 정의한다. 인터페이스는 다음에 다툴 다형성이라는 강력한 프로그래밍 기법에 대한 기초를 제공한다.

다형성

참조 변수의 데이터 형은 자주 가리키는 객체의 데이터 형과 같아야 한다. 예를 들먼 다음 선언문을 고려하랴.

Rectangle myRect;

myRect 변수는 Rectangle 객체를 가리키기 위해 사용될 수 있다. 그러나 일반적으로 한 참조 변수의 데이터 형과 그것이 가라키는 객체의 데이터 형이 호환성은 있어야 하나 반드시 같을 필요는 없다. 다형성이 라는 용어는 여 러 가지 의 형태를 취하는 것으로 정의될 수 있다. 다형적 참조(polymorphic reference)는 시점에 따라 다른 유형의 객체를 가라킬 수 있는 참조 변수이다. 한 다형 적 참조를 통해 호출되는 특정 메소드는 호출될 때마다 바뀔 수 있다. 예를 들면 다음 코드를 고려하라.

myObject.do();

myObject가 다형적 참조라면 그것은 시점에 따라 다른 유형의 객제를 가랴킬 수 있다. 그렇다면 do 메소드가 호출될 때마다 다른 do 매소드가 호출될 수 있다. 우리는 자바에서 다형적 참조를 두 가지 방법으로 만틀 수 있다.

  • 상속을 통한 다형성
  • 인터페이스를 통한 다형성

상속을 통한 다형성

상속은 다형성의 기초로서 사용될 수 있다. 한 참조 변수는 어느 경우에는 한 객체를 가라킬 수 있고, 다른 경우에는 상속에 의해 관련이 있는 다른 객체 를 가랴킬 수 있다. 예를 들면 Holiday 클래스가 Christmas 클래스의 상위 클래 스라고 하자 그라고 Christmas 클래스가 Holiday 클래스의 celebrate라는 매소드를 상속 받아 그대로 사용하지 않고 재정의한다고 가정한다. 다음 코드를 고려하라.

이 경우에 day가 Holiday 객체를 가랴키는 잠조 변수로 정의되었으나 배정문에서 Christmas 객체를 가리킨다. 따라서 day. celebrateO는 Christmas 클래스에 정의된 celebrate 메소드를 호출한다.

다음 프로그램은 메소드 재정의를 통한 다형성을 보여 준다. 동물을 나타내는 Animal 클래스는 하위 클래스들로서 물고기를 나타내는 Fish 새를 나타내는 Bird와 개를 나타내는 Dog 클래스를 가진다 Animal 클래스는 동물이 움직인다는 사실을 나타내는 move 메소드를 가지고 있다. 그러나 Fish, Bird와 Dog 클래스는 각각 다르게 웅직인다는 사실을 나타내기 위해 Animal 클래스의 move 메소드를 상속받지 않고 재정의한다. Driver 클래스에서 animalArray[index].move() 매소드가 호출될 때 animalArray[index] 가 하위 클래스의 객체를 가라카므로 Animal 클래스의 move 매소드가 아닌 대응하는 하위 클래스의 move 메소드가 호출된다.

인터페이스를 통한 다형성

클래스 이름이 객체를 가라거는 참조 변수의 데이터 헝을 선언하기 위해 사용될 수 있는 것과 같이 인터페이스의 이 름이 한 참조 변수의 데이터 형으로 사용될 수 있다. 그러면 한 인터페이스를 참조하는 변수는 그 인터페이스를 구현하는 어떤 클래 스의 객체를 참조하기 위해 사용될 수 있다. 예를 들면 다음과 같이 Printable이라는 인터 페이 스가 있다고 가정하자.

그러면 Printable이라는 인터페이스 이름이 다음과 같이 배 열을 선언하기 위해 사용될 수 있다.

배열 printerQueue는 Printable 인터페이스를 구현하는 어떤 클래스의 객제도 참조하기 위해 사용될 수 있다. 이는 인터페이 스를 통해 다형성이 실현되는 한 예이다. 인터페이스의 융통성은 우리 가 다형적 참조들을 만드는 것을 가능하게 한다. 인터페이스를 사용함으로써 우리는 같은 인터페이스를 구현하는 여러 객체들 사이에서 비슷한 다형적 참조를 만들 수 있다. 예를 들면 우리가 Printable 인터페 이스를 구현하는PrintableText라는 클래스를 정 의하고 printer가 그 클래 스의 객체를 가리키는 참조 변수라고 가정하자. 또한 우리가 Printable 인터페이스를 구현하는 PrintableGraphics라는 클래스를 정의한다면 다음과 같은 코드가 가능하다.

위 코드에서 printer라는 참조 변수는 처음에는 PrintableText 객체를 가리거고 나중에는 PrintableGraphics 객체를 가리킨다. 따라서 첫 번째 호출문은 PrintableText 클래스의 printMe 메소드를 호출하고 두 번째 호출문은 Printa bleG ra phics 클래스의 printMe 메소드를 호출한다. 그러므로 어 느 메소드가 호출되는지 를 결정하는 것은 호출 시점에 참조 변수가 가리거는 객체 에 기초한다. 이는 다형성이 적용되는 또 다른 예이다.

내부 클래스

한 클래 스는 변수들과 메소드들 외에 다른 클래스(들)을 포함할 수 있다. 한 클래스 내에 선언된 또 다른 클래 스는 내부 클래스(inner class) 라고 부른다. 내부 클래스를 둘러싸고 있는 클래 스는 외부 클래스(outer class) 라고 부른다. 내부 클래스는 또 다른 클래스 내에 정의된 클래스이다. 내부 클래스는 머리부에 private이라는 예약어를 사용한다.

내부 클래스는 그것을 포함하는 외부 클래스의 변수들과 메소드들을 자동적으로 이용할 수 있다. 어떤 상황에서 이것이 클래스들의 구현을 더 쉽게 만든다. 왜냐하면 클래스들이 정보를 쉽게 공유할 수 있기 때문이다. 더욱이 내부 클래스는 외부 클래스에 의해 외부에서의 사용으로부터 보호받을 수 있다. 내부 클래스는 외부 클래스의 각 객제와 연관된다. 한 내부 클래스의 한 객체는 외부 클래스의 한 객 체 내에서만 존재 할 수 있다. 내부 클래스는 두 개의 클래스들 사이에 밀접한 관계가 있고 어떤 다른 클래스에 의해 접근되지 않는 경우에 만든다.
내부 클래스는 별도의 바이트코드 파일을 만든다. Inside라는 내부 클래스가 Outside라는 외부 클래스 안에 선언된다면 컴파일 후 다음과 같은 두 개의 바이트코드 파일들이 만들어 진다.

Outside.class
Outside$Inside.class

예제 프로그램 작성

야구와 농구 선수의 통계를 기록하는 간단한 프로그램을 작성해 보자. 각 선수는 공통적으로 이름, (소속)팀명과 득점수를 가진다. 야구 선수는 추가적으로 안타수와 에러수를 가진다. 농구 선수는 추가적으로 도움수와 리바운드수를 가진다. 선수의 이름과 팀명을 넘겨 받아 객제를 생성할 수 있어 야 한다. 객체를 생성할 때 각종 기록(득점수, 안타수, 에러수, 도움수 혹은 리바운드수)은 0으로 초기화해야 한다. 선수의 각종 기록을 각각 알려 줄 수 있어야 한다. 또한 선수의 모든 데이터를 한꺼번에 알려 줄 수 있어야 한다. 선수의 각종 기록을 적절하게 댄경하거나 주어진 값을 이용하여 새로 계산할 수 있어야 한다. 예를 들면, 야구 선수가 안타를 치면 안타수는 1 만큼 증가하고 농구 선수가 리바운드를 하면리바운드수가 1 만큼 증가한다. 농구 선수기- 2점 슛을 성공시키면 득점수는 2 만큼 증가하나 3 점 슛을 성공시키면 득점수는 3 만큼 증가한다. 반면에 야구 선수가 홈에 들어 오면 득점수는 1 만큼 증가한다.

이 프로그램을 작성하기 위해 필요한 클래스들을 찾아보자. 먼저 우리는 야구 선수와 농구 선수를 모텔하기 위해 어떤 유형의 클래스들을 사용할지를 결정해야 한다. 그 클래스들을 설계하기 위한 두 가지 방법이 있다. 첫 번째 방법은 두 개의 서로 관련이 없는 클래스들을 정의하는 것이다. 즉 야구 선수를 위한 클래스와 농구 선수를 위한 클래스를 별도로만드는 것이다. 두 번째 방법은 야구 선수와 농구 선수를 상속 계층으로 관계가 있는 클래스들을 사용하여 모댈하는 것이다.

공통 데이 터나 연산틀을 공유하는 객체들에 대해 두 개의 서로 무관한 클래스들을 정의하는 것은 비효율적이다. 왜냐하면 두 개의 클래스들에 공통적인 코드를 중복해서 작성해야 하기 때문이다. 야구 선수와 농구 선수는 다르지만 많은 공통 데이터와 연산들을 공유한다. 따라서 우리는 이 두 클래 스들을 상속을 이용하여 정의한다.우리는 실제로 네 개의 클래스들을 정의한다. 첫 번째 클래스는 야구 선수와 농구 선수에공통적인 데이터와 연산을 나타내는 PlayerStats 클래스이다. 두 번째 클래스는 야구 선수에만 해당되는 데이터와 연산을 나타내는 BaseballStats 클래스이다. 세 번째 클래스는 농구 선수에만 해당하는 데이터와 연산을 나타내는 BasketballStats 클래스이다. 이세 클래스들의 상속 관계를 나타낸 상속 계층도는 다음 그림에 보여진다. 네 번째 클래스는 이 세 개의 클래스들을 시험하는 Driver 클래스이다.

file

file

다음으로 각 클래스를 설계해 보자 먼저 PlayerStats 클래스는 선수를 나타낸다. 야구선수와 농구 선수의 공통적인 데이터는 이름 팀명과 득점수이다. 선수와 관련된 연산은 다음과 같다.

(1) PlayerStats 객체를 생성한다
(2) PlayerStats 객체의 모든 데이터를 반환한다
(3) 득점수를 반환한다
(4) 득점수를 주어진 값에 따라 적절하게 변경한다

PlayerStats 클래스의 설계 문서는 다음 표에 보여진다. PlayerStats 클래스의 구현은 다음 프로그램에 보여진다. 득점수를 변경하는 메 소드는 필요하나 야구 선수이냐 농구 선수이냐에 따라 다르게 작성되어야 하므로 추상 메소드로 구현한다. 따라서 PlayerStats 클래스는 추상 클래스가 된다.

file

야구 선수를 모델하는 BaseballStats 클래 스의 설계 문서는 다음 표에 보여진다. 야구 선수에만 해당되는 데이터인 안타수와 에러수를 저장하는 변수가 필요하다. 또한 선수의 이름과 팀명을 각각 주어진 값으로 초기화하고 안타수와 에러수는 각각 0으로 초기화하면서 객체를 생성하는 생성자 메소드가 필요하다. 또한 PlayerStats 클래스에서 추상 메소드로 정의된 earnScore 메소드를 득점수를 주어진 값(1) 만큼 증가시키면 된다. 나머지 메소드들은 변경자/접근자 메소드이다. BaseballStats 클래스는 다음 프로그램에 보여진다.

file

농구 선수를 모벨하는 BasketballStats 클래스의 설계 문서는 다음 표에 보여진다. 농구 선수에만 해당되는 데이 터인 도움수와 리 바운드수를 저장하는 변수가 필요하다. 또한 선수의 이름과 팀명을 각각 주어진 값으로 초기 화하고 도움수와 리바운드수는 각각 0으로 초기화하면서 객제를 생성하는 생성자 메소드가 필요하다. 또한 PlayerStats 클래스에서 추상 메소드로 정의된 earnScore 메 소드를 득점수를 주어진 값(2 혹은 3)만큼 증가시키면 된다. 나머 지 메소드들은 변경자/접근자 메소드이다. BasketballStats 클래스는 다음 프로그램에 보여진다.

file

마지막으로 주 클래스인 Driver 클래스를 설계해 보자 이 클래스에서는 BaseballStats 객체와 BasketballStats 객체를 각각 생성하여 표본 출력의 결과가 나오도록 해당 메소드들을 호출한다. 이는 main 메소드에서 다 할 수 있다 maIn 메소드에서 필요한 변수들은 다음과같다.

  • playerl: Baseba끄Stats 객체 잠조변수
  • player2: BasketballStats 객체 참조변수

main 메소드의 알고리즘은 객체들을 생성한 후 필요한 메소드들을 호출하기만 하면 된다. Driver 클래스는 다음 프로그램에 보여진다.

요약

• 추상 클래스는 일반적인 개념을 나타내는 클래스이다.
• 추상 클래스는 하나 이상의 추상 메소드를 포함한다.
• 추상 매소드는 메소드 머리부만 있고 몸체는 없는 메소드이다.
• 추상 클래스의 하위 클래스는 모든 추상 메소드들을 재정의해야 한다,
• 추상 클래스의 생성자 메소드는 직접적으로 호출되지 않고 하위(혹은 자손) 클래스의 생성자 메소드가 호출될 때 간접적으로 호출된다.
• 상속을 제한하기 위해 final 예약어를 사용한다.
• 인터페이스는 여러 클래스들이 공유하는 기능들을 나타낸다. 인터페이스는 상수들과 추상 메소드들의 모음이다.
• 한 클래스는 한 인터페이스에 정의된 모든 추상 메 소드를 완전하게 구현함으로써 그 인터페이스를구현한다.
• 여러 개의 클래스들이 같은 인터페이스를 구현할 수 있다. 이 경우에 각 클래스는 인터페이스의 추상 메소드들을 다트게 구현하여 다형성을 실현한다.
• 다형적 참조는 시점에 따라 다른 유형의 객체를 가리킬 수 있는 참조 변수이다, 한 다형적 참조를 통해 호출되는 특정 메소드는 호출 때마다 바뀔 수 있다.
• 메소드 재정의를 통해 다형성을 실현할 수 있다.
• 인터페이스의 이름야 한 참조 변수의 데이터 형으로 사용될 수 있다.
• 한 인터페이스를 참조하는 변수는 그 인터페이스를 구현하는 어떤 클래스의 객제를 가리키기 위해 사용될 수 있다.
• 한 클래스는 변수들과 메소드들 외에 다른 클래스틀을 포함할 수 있다. 한 클래스 내에 선언된 또 다른 클래스는 내부 클래스이다.

3220 reads

Author

No discussion

Please log in to leave a comment

16.5.11618.20190612.allo
Report · Terms of Service · Privacy Policy
Lablup Inc. · Teheran-ro 145, Gangnam-gu, Seoul, Republic of Korea · 대표: 신정규 · 사업자번호: 864-88-00080 · +82 70-8200-2587

Decline Confirm

Close
Favorite Bookmark Discussion

Please copy the following address