본문 바로가기

언어 기초/JAVA

다형성 [JAVA | 학습을 위한 자료 | 김영한 자바 기본]

다형적 참조

다형적 참조는 객체 지향 프로그래밍에서 매우 중요한 개념이다. 이를 간단히 말하자면, 부모 클래스 타입의 변수가 자식 클래스 타입의 객체를 참조할 수 있다는 의미이다.

부모는 자식을 담을 수 있지만 자식은 부모를 담을 수 없다.

이때는 다운캐스팅이라는 기능을 사용해서 부모 타입을 잠깐 자식 타입으로 변경하면 된다.

부모 클래스 타입의 변수를 자식 클래스 타입으로 변환하고 싶을 때는 다운캐스팅을 사용해야 한다

다형성과 메서드 오버라이딩

다형성을 이루는 또 하나의 중요한 핵심 이론은 바로 메서드 오버라이딩이다. 메서드 오버라이딩에서 꼭! 기억해야 할 점은 오버라이딩 된 메서드가 항상 우선권을 가진다는 점이다. 그래서 이름도 기존 기능을 덮어 새로운 기능을 재정의 한다는 뜻의 오버라이딩이다.

추상 클래스

동물( Animal )과 같이 부모 클래스는 제공하지만, 실제 생성되면 안되는 클래스를 추상 클래스라 한다. 추상 클래스는 이름 그대로 추상적인 개념을 제공하는 클래스이다. 따라서 실체인 인스턴스가 존재하지 않는다. 대신에 상속을 목적으로 사용되고, 부모 클래스 역할을 담당한다.

순수 추상 클래스는 다음과 같은 특징을 가진다.

interface 키워드를 사용

  • 인스턴스를 생성할 수 없다.
  • 상속시 자식은 모든 메서드를 오버라이딩 해야 한다.
  • 주로 다형성을 위해 사용된다.

인터페이스는 앞서 설명한 순수 추상 클래스와 같다. 여기에 약간의 편의 기능이 추가된다.

  • 인터페이스의 메서드는 모두 public , abstract이다.
  • 메서드에 public abstract 를 생략할 수 있다. 참고로 생략이 권장된다.
  • 인터페이스는 다중 구현(다중 상속)을 지원한다.

추상 메서드

부모 클래스를 상속 받는 자식 클래스가 반드시 오버라이딩 해야 하는 메서드를 부모 클래스에 정의할 수 있다. 이것을 추상 메서드라 한다. 추상 메서드는 이름 그대로 추상적인 개념을 제공하는 메서드이다. 따라서 실체가 존재하지 않고 메서드 바디가 없다.

  • 추상 메서드는 선언할 때 메서드 앞에 추상이라는 의미의 abstract 키워드를 붙여주면 된다.
  • 추상 메서드가 하나라도 있는 클래스는 추상 클래스로 선언해야 한다. 그렇지 않으면 컴파일 오류가 발생한다. 추상 메서드는 메서드 바디가 없다. 따라서 작동하지 않는 메서드를 가진 불완전한 클래스로 볼 수 있다. 따 라서 직접 생성하지 못하도록 추상 클래스로 선언해야 한다.
  • 추상 메서드는 상속 받는 자식 클래스가 반드시 오버라이딩 해서 사용해야 한다. 그렇지 않으면 컴파일 오류가 발생한다. 추상 메서드는 자식 클래스가 반드시 오버라이딩 해야 하기 때문에 메서드 바디 부분이 없다. 바디 부분을 만들면 컴파일 오류가 발생한다. 오버라이딩 하지 않으면 자식도 추상 클래스가 되어야 한다.
  • 추상 메서드는 기존 메서드와 완전히 같다. 다만 메서드 바디가 없고, 자식 클래스가 해당 메서드를 반드시 오버라 이딩 해야 한다는 제약이 추가된 것이다.

인터페이스 다중상속

자바에서 인터페이스의 다중 상속 기능은, 클래스가 여러 개의 인터페이스를 동시에 구현할 수 있다는 개념을 의미한다. 자바는 클래스의 다중 상속을 지원하지 않지만, 인터페이스의 경우는 다중 상속을 허용한다. 이를 통해 여러 인터페이스에서 정의된 메서드를 한 클래스에서 구현할 수 있다.

만약 두 인터페이스에 동일한 메서드가 정의되어 있고, 이를 구현하는 클래스에서 명확하게 구현하지 않으면 메서드 충돌 문제가 발생할 수 있다. 자바에서는 이를 해결하기 위해 명시적으로 해당 메서드를 구현해 주어야 한다.

역할과 구현

인터페이스는 여러 클래스가 공통적으로 수행해야 하는 작업의 규약을 정의한다. 이때 인터페이스는 그 자체로는 구체적인 구현을 제공하지 않고, 오직 메서드 시그니처(메서드 이름, 반환 타입, 파라미터)만 제공한다. 이러한 역할은 어떤 작업이 수행될지를 규정하지만, 어떻게 수행되는지는 각 클래스에서 자유롭게 정의할 수 있다.

OCP(Open-Closed Principle) 원칙

체지향 설계 원칙 중 하나로 "확장에는 열려 있고, 수정에는 닫혀 있어야 한다"는 개념을 의미한다. 즉, 기존 코드를 변경하지 않고도 새로운 기능이나 요구 사항을 쉽게 추가할 수 있어야 한다는 것을 강조한다.

문제와 풀이

문제1: 다중 메시지 발송

한번에 여러 곳에 메시지를 발송하는 프로그램을 개발하자. 다음 코드를 참고해서 클래스를 완성하자

요구사항

  • 다형성을 활용하세요.
  • Sender` 인터페이스를 사용하세요.
  • EmailSender , SmsSender , FaceBookSender 를 구현하세요.
public class SendMain {
     public static void main(String[] args) {
         Sender[] senders = {new EmailSender(), new SmsSender(), 
         new FaceBookSender()};
         
         for (Sender sender : senders) {
						sender.sendMessage("환영합니다!"); 
				 }
		}
}

실행결과

메일을 발송합니다: 환영합니다!

SMS를 발송합니다: 환영합니다!

페이스북에 발송합니다: 환영합니다!

문제2: 결제 시스템 개발

여러분은 기대하던 결제 시스템 개발팀에 입사하게 되었다. 이 팀은 현재 2가지 결제 수단을 지원한다. 앞으로 5개의 결제 수단을 추가로 지원할 예정이다. 새로운 결제수단을 쉽게 추가할 수 있도록, 기존 코드를 리펙토링해라.

요구사항

  • OCP 원칙을 지키세요.
  • 메서드를 포함한 모든 코드를 변경해도 됩니다. 클래스나 인터페이스를 추가해도 됩니다.
  • 단 프로그램을 실행하는 PayMain0 코드는 변경하지 않고, 그대로 유지해야 합니다.
  • 리펙토링 후에도 실행 결과는 기존과 같아야 합니다.
public class KakaoPay implements Pay {
  @Override
	public boolean pay(int amount) { 
		
		System.out.println("카카오페이 시스템과 연결합니다."); 
		System.out.println(amount + "원 결제를 시도합니다."); 
		
		return true;
	} 
}
public class NaverPay implements Pay {
  @Override
	public boolean pay(int amount) { 
		
		System.out.println("네이버페이 시스템과 연결합니다."); 
		System.out.println(amount + "원 결제를 시도합니다."); 
		
		return true;
	}
}
public class DefaultPay implements Pay {
  @Override
	public boolean pay(int amount) { 
		
		System.out.println("결제 수단이 없습니다."); 
		
		return false;
	} 
}

사용자입력