도도한 개발자

[Java] #13. 인터페이스와 추상클래스, enum 본문

Backend/Java

[Java] #13. 인터페이스와 추상클래스, enum

Kiara Kim 2022. 3. 16. 16:50

* 방법론

 

어떤 시스템을 개발하든 간에 "방법론"이라는 것을 사용하여 개발한다. 방법론이라는 것은 시스템을 어떻게 만들 것인지에 대한 절차를 설명하고, 어떤 문서(=산출물)를 작성해야 하는지는 정리해놓은 공동 절차라고 보면 된다.

일반적인 절차는 다음과 같이 네 단계로 이루어져 있다.

 

· 분석

· 설계

· 개발 및 테스트

· 시스템 릴리즈

 

각 단계에 대해 간단히 알아보자.

 

** 분석

 

시스템을 분석하는 단계에서는, 시스템을 만들어달라고 한 사람들(SI에서는 고객, SM에서는 현업)에게 어떻게 개발하기를 원하는지 물어본다. 이러한 일련의 과정을 요구사항 분석이라고 한다.

 

** 설계

 

설계 단계에서는 분석 단계에서 만든 대략적인 그림을 프로그램으로 만들 수 있도록 설계하는 작업을 수행한다. 어떤 메소드를 만들 것인지, 데이터는 어떻게 저장할지 등등의 세부적인 것들을 정리한다.

 

** 개발 및 테스트

 

설계에서 만들기로 한 것들을 개발하는 단계다. 실제 시스템에서 제공해야 하는 기능들을 이때 만든다. 또한 필요한 기능들이 제대로 동작하는지 확인하는 "테스트" 작업을 수행한다.

 

** 시스템 릴리즈

 

시스템을 사용자들에게 제공하는 단계다. 시스템을 오픈한 이후에는 운영/유지보수 단계를 거치면서 문제가 있는 부분들을 고쳐나간다.

 

위의 설계 단계에서 프로그램을 어떻게 만들 것인지 정리한다고 했다. 그러나 이 정리하는 작업을 그냥 워드나 엑셀같은 파일에 문서만 만드는 것이 아니다. 어떤 클래스를 만들지, 어떤 메소드를 만들지, 어떤 변수를 만들지를 정리하는 작업도 같이 한다. 그런데 내용들을 문서에만 정리하면 나중에 변경했을 대 문서도 수정해야 하므로 일이 번거로워진다. 이에 설계 단계에서 '인터페이스'라는 것을 만들어 두면 개발할 때 메소드의 이름을 어떻게 할지, 매개 변수를 어떻게 할지를 일일이 고민하지 않아도 된다. 가장 일반적인 것이 DAO(Data Access Object)라는 패턴이다. 이 패턴은 데이터를 저장하는 저장소에서 원하는 값을 요청하고 응답을 받는다. 작성한 인터페이스를 구현해서 Oracle을 사용하든, MongoDB를 사용하든 간에 작성한 메소드에서 결과만 제대로 넘겨주면 된다. 이렇게 인터페이스를 정해 놓으면, 선언과 구현을 구분할 수 있다. 위의 내용을 정리해 인터페이스와 abstract 클래스를 사용하는 이유는 다음과 같다.

 

· 설계시 선언해 두면 개발할 때 기능을 구현하는 데에만 집중할 수 있다.

· 개발자의 역량에 따른 메소드의 이름과 매개 변수 선언의 격차를 줄일 수 있다.

· 공통적인 인터페이스의 abstract 클래스를 선언해 놓으면, 선언과 구현을 구분할 수 있다.

 

 

* 인터페이스 Interface 

 

저번 시간에 사용한 infoDTO를 관리하는 InfoManagerImpl이라는 클래스를 만들어야 한다고 가정하자. 인터페이스는 InfoManager라는 이름으로 만들어보자.

 

import chapter12.infoDTO;

public interface InfoManager {
	public boolean addInfo(infoDTO info);
	public boolean removeInfo(String name, String phone);
	public boolean updateInfo(infoDTO info);
}

 

여기서 중요한 것은, 인터페이스 선언부는 public class로 시작하지 않고, public interface로 시작한다는 것이다. 이렇게 interface 내부에 선언된 메소드들은 몸통이 있으면 안 된다. 즉, 메소드 선언 이후에 중괄호를 열고 닫거나, 중괄호 안에 한 줄의 코드도 있어선 안 된다.

 

그러면 이렇게 만든 인터페이스를 어떻게 활용할까? 다음과 같이 InfoManagerImpl이라는 클래스를 만들어 보자.

 

public class InfoManagerImpl implements InfoManager {

}

 

이 클래스 선언문을 보면 implements 라는 단어 뒤에 InfoManager라고 방금 만든 인터페이스를 추가한 것을 볼 수 있다. 여기서 implements의 사전적 의미는 '구현하다'이다. 이렇게 인터페이스를 적용하기 위해선 클래스 선언문에서 클래스 이름 뒤에 implements라는 예약어를 쓴 후 인터페이스를 나열하면 된다. extends로 상속하는 것이 아닌 implements로 구현하는 것이기 때문에 여러개를 나열할 수 있는 것이다.

 

이렇게 하고 컴파일을 하면 'InfoManagerImpl 클래스는 abstract 클래스도 아니고, InfoManager에 정의되어 있는 updateInfo()라는 abstract 메소드도 구현하지 않았다'라는 에러 메세지를 출력한다.

 

인터페이스를 구현할 경우, 반드시 인터페이스에 정의된 메소드들의 몸통을 만들어 주어야만 한다.

 

import chapter12.infoDTO;

public class InfoManagerImpl implements InfoManager {
	@Override
	public boolean addInfo(infoDTO info) {
		return false;
	}
	
	@Override 
	public boolean removeInfo(String name, String phone) {
		return false;
	}
	
	@Override
	public boolean updateInfo(infoDTO info) {
		return false;
	}
}

 

InfoManager에 정의된 메소드들을 모두 구현해야만 컴파일이 정상적으로 수행된다. 정리하자면, 설계 단계에서 인터페이스만 만들어 놓고, 개발 단계에서 실제 작업을 수행하는 메소드를 만들면 설계 단계의 산출물과 개발 단계의 산출물이 보다 효율적으로 관리된다. 인터페이스는 보다시피 구현이 되어 있는 코드가 없다. 따라서 생성자도 없고 메소드의 내용 중 아무것도 채워져 있지 않아 그대로 사용할 수 없다. 다음의 InterfaceExample 클래스를 보자.

 

import chapter13.InfoManager;

public class InterfaceExample {

	public static void main(String[] args) {
		InfoManager info = new InfoManager(); // @
	}
}

 

InterfaceExample 클래스를 컴파일해보면  @에 대해 에러가 발생한다.

error : InfoManager is abstract; cannot be instantiated

InfoManager info = new InfoManager();

에러가 발생하고 InfoManager가 abstract이기 때문에 초기화 되지 않는다는 메시지가 출력된다. 아무것도 구현되어 있지 않은 걸로 초기화하려고 했기 때문이다. 다음과 같이 변경하면 된다.

 

InfoManager info = new InfoManagerImpl();

 

겉보기에 info 타입은 InfoManager이다. 그리고 InfoManagerImpl 클래스에는 인터페이스에 선언되어 있는 모든 메소드들이 구현되어 있다. 따라서 실제 info 타입은 InfoManager가 되기 때문에, info에 선언된 메소드들을 실행하면 InfoManagerImpl에 있는 메소드들이 실행된다.

 

 

* abstract 클래스

 

사전적으로 '추상적인'의 의미이다. abstract 클래스는 자바에서 마음대로 초기화하고 실행할 수 없도록 되어 있다. 그래서 abstract 클래스를 수형해 놓은 클래스로 초기화 및 실행이 가능하다. 위에서 사용한 infoManager의 코드를 보자.

 

import chapter12.infoDTO;

public interface InfoManager {
	public boolean addInfo(infoDTO info);
	public boolean removeInfo(String name, String phone);
	public boolean updateInfo(infoDTO info);
}

 

인터페이스는 선언시 class 라는 예약어를 사용하지 않고 바로 interface라는 예약어를 사용한다. 그리고 메소드 선언문은 일반 메소드 선언문과 동일하지만, 메소드의 몸통이 없다. 이제 abstract 클래스를 보자.

 

import chapter12.infoDTO;

public abstract class InfoManagerAbstract {
	public abstract boolean addInfo(infoDTO info);
	public abstract boolean removeInfo(String name, String phone);
	public abstract boolean updateInfo(infoDTO info);
	public void printLog(String data) {
		System.out.println("Data = " + data);
	}
}

 

abstract 클래스는 선언시 class라는 예약어 앞에 abstract이라는 예약어를 사용한 것을 볼 수 있다. 그리고 몸통이 없는 메소드 선언문에는 abstract이라는 예약어를 명시한 것을 볼 수 있다. abstract에 대해 정리해보자.

 

· abstract 클래스는 클래스 선언시 abstract이라는 예약어가 클래스 앞에 추가되면 된다.

· abstract 클래스안에는 abstract으로 선언된 메소드가 0개 이상 있으면 된다.

· abstract으로 선언된 메소드가 하나라도 있으면, 그 클래스는 반드시 abstract으로 선언되어야만 한다.

· abstract 클래스는 몸통이 있는 메소드가 0개 이상 있어도 전혀 상관 없으며, static이나 final메소드가 있어도 된다.

 

이런 abstract 클래스는 왜 만들었을까? 인터페이스를 선언하다 보니, 어떤 메소드는 미리 만들어 놓아도 전혀 문제가 없는 경우가 발생한다. 그렇다고 해당 클래스를 만들기는 좀 애매한. 특히 아주 공통적인 기능을 미리 구현해 놓으면 도움이 된다. 이럴 때 abstract 클래스를 사용한다.

 

 

** 인터페이스와 abstract 클래스, 그리고 클래스

 

  인터페이스 abstract 클래스 클래스
선언 시 사용하는 예약어 interface abstract class class
구현 안 된 메소드 포함 가능 여부 가능(필수) 가능 불가
구현된 메소드 포함 가능 여부 불가 가능 가능(필수)
static 메소드 선언 가능 여부 불가 가능 가능
final 메소드 선언 가능 여부 불가 가능 가능
상속(extends) 가능 불가 가능 가능
구현(implements) 가능 가능 불가 불가

 

그리고, extends 뒤에는 클래스가 단 하나만 존재할 수 있고, implements 뒤에는 인터페이스가 여러 개 존재할 수 있다.

 

 

* enum 사용법(1)

 

문자열이나 숫자들을 나타내는 기본 자료형의 고정 값을 '상수'라고 한다. 영어로는 constant. 어떤 클래스가 상수만드로 만들어져 있을 때 class라고 선언하는 부분에 enum이라고 선언하면, '이 객체는 상수의 집합이다'라는 것을 명시적으로 나타낸다. enum은 enumeration(셈, 계산, 열거, 목록, 일람표)의 앞부분만 따서 만들어진 예약어이다. enum 클래스를 선언한 다음의 코드를 보자.

 

public enum OverTimeValues {
	THREE_HOUR,
	FIVE_HOUR,
	WEEKEND_FOUR_HOUR,
	WEEKEND_EIGHT_HOUR;
}

 

어떤 회사의 평일 3시간 이상 ~ 5시간 미만일 때 야근 수당과 5시간 이상일 때의 야근 수당이 다르고, 주말 4시간 이상 ~ 8시간 미만일 때의 휴일 근무 수당과 8시간 이상일 때의 근무 수당이 있다고 하자. 이러한 enum 클래스는 어떻게 사용할까? 효과적으로 사용하는 방법은 switch 문을 사용하는 것이다.

 

public class OverTimeManager {
	public int getOverTimeAmount(OverTimeValues value) {
		int amount = 0;
		System.out.println(value);
		switch (value) {
		case THREE_HOUR:
			amount = 18000;
			break;
		case FIVE_HOUR:
			amount = 30000;
			break;
		case WEEKEND_FOUR_HOUR:
			amount = 40000;
			break;
		case WEEKEND_EIGHT_HOUR:
			amount = 60000;
			break;
		}
		return amount;
	}
}

 

getOverTime() 메소드를 보면, OverTimeValues() 라는 enum 타입을 매개 변수로 받고, 변수명은 value로 지정했다. 야근 수당을 리턴할 amount라는 int 타입을 선언하고 0을 기본값으로 선언하고, 가장 마지막 줄에 그 값을 리턴한다. 그러먄 overTimeValues라는 enum 타입을 어떻게 getOverTimeAmount() 메소드에 전달할까?

 

public static void main(String[] args) {
		OverTimeManager manager = new OverTimeManager();
		OverTimeValues value = OverTimeValues.THREE_HOUR; // @1
		int myAmount = manager.getOverTimeAmount(value);  // @2
		System.out.println(myAmount);
}

 

@1과 @2를 보면 별도의 생섯자도 필요 없고 그냥 enum을 넘겨주는 것이 아니라는 것을 알 수 있다. 가장 쉽게 이해하는 방법은 enum 타입은 "enum 클래스이름.상수이름"을 지정함으로써 클래스의 객체 생성이 완료된다고 생각하면 된다.

컴파일 한 후 실행한 결과는 다음과 같다.

 

THREE_HOUR
18000

 

 

* enum 사용법(2)

 

enum 타입을 항상 switch로만 확일할 필욘 없다. enum 상수 값을 지정하는 것도 가능하나, 값을 동적으로 할당하는 것은 불가능하다. 위의 코드를 확장해보자.

 

public enum OverTimeValues2 {
	THREE_HOUR(18000),
	FIVR_HOUR(30000),
	WEEKEND_FOUR_HOUR(40000),
	WEEKEND_EIGHT_HOUR(60000);
	private final int amount;
	OverTimeValues2(int amount) {
		this.amount = amount;
	}
	public int getAmount() {
		return amount;
	}
}

 

각 상수들의 값이 지정됐다. 상수 아래에는 amount라는 변수가 final로 선언되어 있고 이 변수는 그 다음 줄에 있는 OverTimeValues2의 생성자에서 매개 변수로 넘겨받은 값을 할당할  때 사용된다. 이 enum 클래스를 컴파일하고 다음과 같이 OverTimeManager2 클래스를 만들자.

 

public class OverTimeManager2 {

	public static void main(String[] args) {
		OverTimeValues2 value2 = OverTimeValues2.FIVR_HOUR;
		System.out.println(value2);
		System.out.println(value2.getAmount());
	}
}

 

value2라는 변수에 OverTimeValues의 FIVE_HOUR라는 상수를 할당했다. 두번째 줄엔 value2를 출력하고 마지막엔 value2의 getAmount()라는 메소드를 호출했다. 출력된 결과는 다음과 같다.

 

FIVR_HOUR
30000

 

처음의 enum 클래스를 선언하면 선언 자체는 매우 간단해지지만, 구현이 약간 복잡해진다. 그리고 메소드를 호출하여 switch 로 값을 간단히 할당하기는 하지만 값이 변경되는 어떻게 될까? 원격 서버에 있는 값을 읽어오도록 하면 큰 문제는 없을 것이다. 그러나 방금 알아본 enum 클래스의 경우 만약 야근 수당이 이천원 오르면 자바 프로그램을 수정한 후 다시 컴파일해서 실행중인 자바 프로그램을 중지했다 다시 시작해야 한다는 단점이 있다. 그러나 성능은 방금 만든 방법이 훨씬 좋을 것이다.

 

 

* 실습

 

(1)

public interface List {
	public void add();				 //데이터 추가
	public void update(int index, Object value); 	// 특정 위치에 있는 값 수정
	public void remove(int index);			// 특정 위치에 있는 값 삭제
}
public abstract class AbstractList implements List {
	public abstract void clear();
}

 

(2)

public enum HealthInsurance {
	LEVEL_ONE(1000, 1.0), 
	LEVEL_TWO(2000, 2.0), 
	LEVEL_THREE(3000, 3.2), 
	LEVEL_FOUR(4000, 4.5), 
	LEVEL_FIVE(5000, 5.6), 
	LEVEL_SIX(6000, 7.1);
	
	private final int maxSalary;
	private final double ratio;
	
	HealthInsurance(int maxSalary, double ratio) {
		this.maxSalary = maxSalary;
		this.ratio = ratio;
	}
	
	public double getRatio() {
		return ratio;
	}
	
	public static HealthInsurance getHealthInsurance(int salary) {
		if(salary < 1000)
			return LEVEL_ONE;
		else if(salary < 2000)
			return LEVEL_TWO;
		else if(salary < 3000)
			return LEVEL_THREE;
		else if(salary < 4000)
			return LEVEL_FOUR;
		else if(salary < 5000)
			return LEVEL_FIVE;
		else {
			return LEVEL_SIX;
		}
	}
	
	public static void main(String[] args) {
		int salaryArray[] = new int[] {1500, 5500, 8000};
		HealthInsurance[] insurances = new HealthInsurance[3];
		insurances[0] = HealthInsurance.getHealthInsurance(salaryArray[0]);
		insurances[1] = HealthInsurance.getHealthInsurance(salaryArray[1]);
		insurances[2] = HealthInsurance.getHealthInsurance(salaryArray[2]);
		
		for(int loop = 0; loop < 3; loop++) {
			System.out.println(salaryArray[loop] + " = " + insurances[loop] + ", " + insurances[loop].getRatio());
		}
	}
}

 

 

* 정리

 

인터페이스에 선언되어 있는 메소드는 body(몸통)이 있어도 되나요?
인터페이스에서는 메소드를 선언만 해야 한다. 메소드 선언후 중괄호를 열고 닫기만해도 컴파일이 되지 않는다.

인터페이스를 구현하는 클래스의 선언시 사용하는 예약어는 무엇인가요?
클래스 선언시 class 가 들어가는 자리에 interface가 위치해야 한다.

메소드의 일부만 완성되어 있는 클래스를 무엇이라고 하나요?
abstract 클래스는 인터페이스처럼 메소드를 선언만 할 수도 있고, 일부 메소드를 구현 할 수도 있다.

위에 있는 문제의 답에 있는 클래스에 body(몸통)이 없는 메소드를 추가하려면 어떤 예약어를 추가해야 하나요? 
abstract 메소드 선언시에는 abstract 예약어를 사용해야 한다. 당연히 해당 클래스도 abstract class로 선언 되어 있어야만 한다.


클래스를 final로 선언하면 어떤 제약이 발생하나요?
final로 선언된 클래스는 확장(extends)할 수 없다.

메소드를 final로 선언하면 어떤 제약이 발생하나요?
메소드를 final로 선언하면 override할 수 없다. 

변수를 final로 선언하면 어떤 제약이 발생하나요?
변수를 final로 선언하면, 그 값을 변경할 수 없다. 따라서 변수는 대부분 선언과 동시에 값을 할당한다. 

enum 클래스 안에 정의하는 여러 개의 상수들을 나열하기 위해서 상수 사이에 사용하는 기호는 무엇인가요?
enum 클래스의 상수들은 콤마 , 로 구분한다. 

enum 으로 선언한 클래스는 어떤 클래스의 상속을 자동으로 받게 되나요?

모든 enum 클래스의 부모 클래스는 java.lang.Enum 이다. 

 

enum 클래스에 선언되어 있지는 않지만 컴파일시 자동으로 추가되는 상수의 목록을 배열로 리턴하는 메소드는 무엇인가요?

values() 메소드는 enum클래스에 선언된 상수의 목록을 배열로 리턴한다.

 

그럼 이만.

'Backend > Java' 카테고리의 다른 글

[Java] #15-1. String - String의 생성자  (0) 2022.03.17
[Java] #14. 예외 (Exception)  (0) 2022.03.16
[Java] #12. Object. 모든 클래스의 부모 클래스  (0) 2022.03.15
[Java] #11. API  (0) 2022.03.15
[Java] #10. 상속  (0) 2022.03.14