기초

[Java]추상클래스와 인터페이스

  • -
반응형

요즘 이직을 앞두고 이곳저곳 면접을 보는 중입니다.

오늘은 면접 중에 나온 질문에 대해 포스팅을 남겨볼까 합니다. 


추상클래스

하나 이상의 추상 메소드를 포함하는 경우 이것을 추상 클래스라고 말한다. 다만 추상 클래스에 반드시 추상 메소드가 있어야 하는 것은 아니다. 이러한 추상 클래스는 abstract로 정의되어 사용된다. 

 

그렇다면 추상 메소드란 무엇이고, 추상 클래스는 어떤 경우 사용될까?

먼저, 추상 메소드는 선언만 되어 있고 구현되어 있지 않은 메소드를 말한다. 그렇기 때문에 추상 클래스는 인스턴스화가 불가능하다.

abstract class Champion {

	public abstract void chooseOne();
	
}

chooseOne()로 선언되어 있는 메소드는 추상 메소드이다. 그리고 추상 메소드를 포함하고 있는 클래스의 이름은 Champion이다.

여기에 String 멤버 변수일반 메소드 start()를 추가했다.

abstract class Champion {
	String spell = "";
	
	public abstract void chooseOne();	

	public void start() {
		chooseOne();
		
		System.out.println("Spell : " + spell);		
	}
}

예제 소스는 게임 리그오브레전드를 생각하며 만들어봤다.

게임 리그오브레전드는 플레이할 챔피언과 스펠이라는 옵션을 선택해 플레이하게 된다. 이 게임에는 Top, Mid, Bottom이라는 포지션이 있고 해당 포지션에는 대표적인 챔피언이 존재한다.

 

게임에서 챔피언과 스펠을 선택하고 플레이(start()) 하는 것은 공통으로 정의된 룰이다. 다만 포지션마다 챔피언은 다르게 정해진다. 또한 스펠도 다르게 정할 수 있다. 즉 챔피온이라는 추상화된 개념은 각각의 세부적인 포지션으로부터 만들어졌다.

 

스펠의 출력 문법은 동일하다. 그래서 공통으로 사용되는 일반 메소드 start()에서 출력된다. 하지만 챔피온의 출력 문법은 각 포지션별 다른 출력 형태를 띈다. 따라서 추상 클래스를 상속받는 자식 클래스에서 구현하도록 했다.

 

추상 클래스 Champion를 상속받은 두 개의 클래스 Top, Mid, Bottom을 생성했다. extends를 통해 상속받은 클래스는 반드시 추상 메소드 chooseOne()를 오버라이딩해야 한다.

class Top extends Champion {
	public void chooseOne() {
		this.spell = "점멸";
			
		System.out.println("Top : 티모");		
	}
}

class Mid extends Champion {
	public void chooseOne() {
		this.spell = "유체화";
		
		System.out.println("Mid : 빅토르");		
	}
}

class Bottom extends Champion {
	public void chooseOne() {
		this.spell = "회복";
		
		System.out.println("Bottom : 베인");		
	}
}

포지션에 따라 챔피온과 스펠을 선택했다.

이제 자식 클래스를 인스턴스화하고 일반 메소드를 실행해보자.

public class LgOfLegendDemo {
	public static void main(String[] args) {
		Top top = new Top();		
		top.start();
		
		Mid mid = new Mid();
		mid.start();
		
		Bottom bottom = new Bottom();
		bottom.start();
	}
}

결과

 

여기까지 확인된 추상 클래스 특징은 다음과 같다.

  • 기본 뼈대가 만들어짐 : 추상 메소드를 자식 클래스에게 구현하게 함으로써 Top, Mid, Bottom뿐 아니라 추가적인 포지션이 생겨도 구현만 해주면 되는 기본적인 뼈대가 생성되었다.(기존 소스의 수정 없이 확장에 유연해짐)
  • 개발 규칙 정의 : 상속을 통해 스펠 값과 챔피온을 선택하는 메소드명을 통일함으로써 규칙이 잡혔다.
  • 코드의 중복 방지 : 공통으로 사용하는 start()를 한 곳에서 관리함으로써 코드 중복을 방지할 수 있다.
  • 자유로운 구현 : Top, Mid, Bottom 클래스별 다르게 구현이 가능하다. (각기 다른 챔피언과 스펠을 선택)

 

정리해보자.

추상 클래스는 상속과 오버라이딩을 통해 자식 클래스에 구현을 강제할 때 사용된다. 강제를 하는 이유는 추상 클래스를 상속받는 자식 클래스가 추상 메소드를 구현함으로써 다형성을 실현하고 유지보수를 높이기 위함이다. 이것은 객체지향 설계의 중요한 첫 걸음이 될 것이다.

그럼에도 불구하고 인터페이스를 사용하는 이유는 클래스의 다중 상속이 안되기 때문이다. 

 

본 예제에서 사용된 champ()라는 추상 메소드는 다양한 형태로 만들어졌다. Top, Mid, Bottom으로 한정하여 예제를 만들었지만 만약 새로운 포지션이 추가된다면 어떨까? 마찬가지로 추상 클래스 상속 후 champ() 메소드를 구현해서 사용하면 될 것이다.(확장)

또한 추상 클래스를 사용함으로써 통일된 코드로 규격화가 가능하다. 때문에 팀 프로젝트를 진행할 때, 소스의 중복 방지와 개발 퍼포먼스가 한층 더 나아질 것이다.

 

인터페이스

인터페이스는 추상 메소드와 상수로 이루어져있다. 추상 메소드(abstract)와 상수(public static final)는 컴파일시 자동 인식을 해주기 때문에 키워드를 생략해도 된다.

*Java8 이상에서는 default 키워드를 통해 일반 메소드 선언이 가능해졌다.

 

추상 클래스의 예제를 인터페이스로 작성했다.

interface ChampInter {			
		
	String spell = "없음";
	
	public void chooseOne(String name);   
}

ChampInter를 상속받는 Top2 클래스를 만들었다. 

spell 값을 변경하고 싶지만 인터페이스의 변수는 초기화된 변수로 값을 변경할 수 없다.

class Top2 implements ChampInter {	
	public void chooseOne(String name) {					
		
		System.out.println("Top : " + name + "/ spell : " + spell);		
	}	
}

결과

이번에는 챔피언과 스펠을 두 개의 인터페이스로 분리해 인터페이스의 특징인 다중 상속을 사용해보려고 한다.

자바에서는 다중 상속시 메소드 출처의 모호성으로 인해 클래스의 다중 상속이 불가능하다. 하지만 인터페이스는 다중 상속이 가능하다.

interface ChampInter {			
		
    public void chooseOne(String name);   
}

interface SpellInter {			
	
    public void chooseTwo(String name1, String name2);   
}

인터페이스 역시 구현체가 없기 때문에 인스턴스화는 불가능하다.

인터페이스를 상속받은 자식 클래스에서는 추상 메소드를 반드시 구현해야 한다.(추상 클래스와 동일)

클래스에서 인터페이스를 상속받을 때는 implements로 사용한다.

 

ChampInter와 SpellInter를 상속받은 Top2 클래스는 chooseOne() 메소드와 chooseTwo() 메소드를 구현했다.

챔피언은 포지션별로 한가지를 선택할 수 있고, 스펠은 두 가지를 선택할 수 있다.

class Top2 implements ChampInter, SpellInter {
	public void chooseOne(String name) {					
		System.out.println("Top : " + name);		
	}
	
	public void chooseTwo(String name1, String name2) {					
		System.out.println("Spell : " + name1 + "/" + name2);		
	}
}

이제 Top2 클래스를 인스턴스화하고 메소드를 실행해보자.

public class LgOfLegendDemo2 {

	public static void main(String[] args) {
		Top2 top2 = new Top2();
			
		top2.chooseOne("티모");
		top2.chooseTwo("순간이동","점멸");
	}
}

여기까지 확인된 인터페이스의 특징은 다음과 같다.

  • 초기화된 멤버 변수 : 추상 클래스와 달리 인터페이스의 멤버 변수는 수정이 불가능하다.
  • 다중 상속 : 챔피언, 스펠을 따로 분리해서 관리하고 같이 선택할 수 있게 되었다.
  • 기능 강제 : Top2 클래스는 반드시 챔피언과 스펠을 체크해야 한다.

 

정리해보자.

추상 클래스와 인터페이스는 상속과 오버라이딩을 통해 자식 클래스에 구현을 한다는 공통된 점이 있다. 그럼에도 불구하고 추상 클래스와 인터페이스를 나눠 사용하는 이유는 무엇일까? 추상 클래스와 인터페이스는 사용 목적에 차이가 있다고 생각하면 된다.

추상 클래스는 각 클래스들의 공통점을 찾아 추상화함으로써 개발적인 관점에서 이익(공통 기능을 한 곳에서 관리하고 필요한 부분만 재정의하여 사용)을 얻을 수 있는 경우 사용하면 된다.

인터페이스는 다중 상속을 요하거나 인터페이스를 상속함으로써 인터페이스가 지닌 기능을 각 클래스마다 수행한다는 보장이 필요한 경우 사용하면 된다.


전체 소스는 깃허브에서 확인하실 수 있습니다.

+ 피드백은 언제나 환영입니다 :)

반응형

'기초' 카테고리의 다른 글

프론트엔드 로드맵  (0) 2024.04.08
백엔드 로드맵  (0) 2024.04.08
[알고리즘]재귀함수(Recursion)  (0) 2020.02.01
[Java]==와 equals  (0) 2018.09.03
[Java]Iterator  (2) 2018.08.28
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.