[Java] 인터페이스
인터페이스란?
인터페이스(Interface)는 클래스 혹은 프로그램이 제공하는 기능을 명시적으로 선언하는 역할을 한다. 인터페이스는 추상 메서드와 상수로만 이루어져 있다. 구현된 코드가 없기 때문에 당연히 인터페이스로는 인스턴스도 생성 할 수 없다. 그렇다면 구현코드도 없는 인터페이스는 어떻게 사용하는 걸까? 인터페이스를 직접 만들어 보면서 살펴보자.
인터페이스를 사용해 간단한 계산기 프로그램을 만들어보자.
내가 사용하고 있는 책에서는 이클립스를 사용하고 있는데, 패키지에서 오른쪽 버튼 클릭 후 New -> Interface를 클릭하면된다. 나는 IntelliJ를 사용하고 있기 때문에 방법이 다른데, 클래스를 추가하면 클래스 이름을 설정하라고 나올때, 형식을 클래스가 아닌 Interface로 설정하면 된다.
1
2
3
4
package interfaceex;
public interface Calc {
}
이렇게 하면 인터페이스를 사용할 준비가 끝났다.
그럼 인터페이스의 구현부를 추상 클래스와 상수로 채워보자.
1
2
3
4
5
6
7
8
9
10
11
package interfaceex;
public interface Calc {
double PI = 3.14;
int ERROR = -99999999;
int add(int num1,int num2);
int substract(int num1, int num2);
int times(int num1, int num2);
int divide(int num1,int num2);
}
인터페이스의 메서드와 멤버변수는 추상메서드와 상수로만 이루어져야 한다고 했다. 근데 위 인터페이스는 파이와 에러가 변수로 선언되어있으며 메서드도 추상메서드로 선언하는 예약어인 abstract로 선언하지 않았다.
인터페이스의 메서드와 변수는 예약어를 명시하지않아도 컴파일 과정에서 자동으로 추상메서드로 변환되고, 변수는 상수가 된다. 따라서 public abstract나 public static final 예약어를 선언하지 않아도 된다.
클래스에서 인터페이스 구현하기
그럼 이제 선언한 인터페이스를 클래스에서 사용해보자. 상속을 할때는 기능을 확장한다는 의미로 extends를 썼었다. 인터페이스에서는 인터페이스에서 선언한 기능을 클래스가 구현한다는 의미로 implements 예약어를 사용한다. calc 인터페이스를 Calculator에서 구현하는 방법은 다음과 같다.
1
2
3
4
package interfaceex;
public class Calculator implements Calc{
}
이렇게 implements로 선언해주면 추상 메서드 4개를 포함하는 Calculator 클래스가 선언된다. 이 추상메서드 4개를 구현하지 않으면, Calcultor 클래스도 추상클래스가 된다. 그럼 추상메서드의 개념까지 한번 더 생각해 볼겸, add()와 substract() 메서드만 구현한 추상메서드로 한번 더 추상클래스를 선언해보고, CompleteCalc에서 모든 기능을 구현해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
package interfaceex;
public abstract class Calculator implements Calc{
@Override
public int add(int num1, int num2) {
return num1 + num2;
}
@Override
public int substract(int num1, int num2) {
return num1 - num2;
}
}
2개의 메서드만 구현했으므로 이 클래스 또한 추상 클래스이다. 따라서 abstract 예약어를 사용해서 선언한다.
그럼 이제 모든 메서드를 구현한 클래스를 만들어보자. 위에서 만든 추상클래스 Calculator를 상속받아 CompleteCalc에 아직 구현되지 않은 times()와 divide() 추상 메서드를 이 클래스에 구현해주면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package interfaceex;
public class CompleteCalc extends Calculator{
@Override
public int times(int num1, int num2) {
return num1 * num2;
}
@Override
public int divide(int num1, int num2) {
if(num2 != 0)
return num1 / num2;
else
return Calc.ERROR;
}
public void showInfo(){
System.out.println("Calc 인터페이스 구현 완료");
}
}
이제 이렇게 만든 클래스를 Test 파일에서 메인함수를 만들어 출력해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package interfaceex;
public class Test {
public static void main(String[] args){
int num1 = 10;
int num2 = 5;
CompleteCalc cal = new CompleteCalc();
System.out.println(cal.add(num1,num2));
System.out.println(cal.substract(num1,num2));
System.out.println(cal.divide(num1,num2));
System.out.println(cal.times(num1,num2));
cal.showInfo();
}
}
>>> 15
>>> 5
>>> 2
>>> 50
>>> Calc 인터페이스 구현 완료
덧셈, 뺄셈, 나눗셈, 곱셈 연산을 실행하고 그 값을 출력해보았다. 이처럼 인터페이스는 선언부를 모아놓았기 때문에, 해당 메서드의 구현부를 재정의하여 사용 할 수 있게 해준다.
인터페이스 구현과 형 변환
Calculator 클래스는 인터페이스에서 선언한 추상 메서드 중 일부 메서드만 구현했으므로 추상 클래스이다.
그리고 이를 상속받은 CompleteCalc 클래스는 Calc에서 구현하지 않은 나머지 추상 메서드를 모두 구현하고 showInfo() 메서드를 추가로 구현했다. 이러한 관계에서 하위 클래스의 형 변환은 어떻게 이루어지는지 알아보자.
상속관계에서는 하위클래스는 상위클래스로 묵시적 형변환이 언제든 가능하다고 했다. 인터페이스도 마찬가지로 CompleteCalc 클래스는 상위클래스인 Calculator 형이면서 Calc형이다. 따라서 다음과 같은 코드 선언이 가능하다.
1
Calc calc = new CompleteCalc();
이렇게 하면 calc 변수에 대입한 newCalc는 인터페이스에서 선언한 메서드만 사용할 수 있고, CompleteCalc 에서 구현한 showInfo() 메서드는 사용할 수 없다. 이것이 바로 인터페이스의 중요한 역할이다.
인터페이스의 역할
지금까지 인터페이스를 정의하고 클래스에서 구현해보았는데, 그러면 이러한 인스턴스는 어디에 쓰는 코드일까?
인터페이스는 클라이언트 프로그램에 어떤 메서드를 제공하는지 미리 알려주는 명제 또는 약속의 역할로, 만약 Abc 클래스를 구현한 A클래스가 있다고 가정하자. 이 클래스를 사용하는 Z라는 프로그램이 있다면, Abc인터페이스에는 구현해야할 추상메서드가 모두 선언되어있고, 어떤 매개변수가 사용되는지, 어떤 자료형 값이 반환되는지 선언되어 있을 것이다. 즉 Z프로그램에서는 A클래스의 구현부를 살펴보지 않고 Abc 인터페이스의 선언 부분만 보고도 A클래스의 사용방법을 알게되는 것이다.
만약 Z프로그램에서 Abc 인터페이스를 구현한 다른 클래스 B를 사용하고 싶다면, 인터페이스 명을 B클래스로 교체하여 사용가능하다.
1
2
3
4
Abc abc;
abc = new A();
abc = new B();
abc = new C();
이렇게 Z프로그램에서 각 클래스를 사용하려고 할 떄, 클래스에서 구현한 내용을 몰라도 Abc 인터페이스에서 선언한 메서드의 매개변수 자료형과 반환값을 알면 인터페이스를 구현한 어떤 클래스든 사용가능하다.
인터페이스와 다형성
인터페이스를 사용하면 다형성을 구현하여 확장성 있는 프로그램을 만들 수 있다.
즉, 클라이언트 프로그램을 많이 수정하지 않고 기능을 추가하고 다른 기능을 사용할 수 있게된다.
다음 예제를 보자.
고객센터에는 전화 상담을 하는 상담원이 있다. 고객센터로 전화가 오면 대기열에 저장되고, 상담원이 지정되기 전까지 대기 상태가 된다. 전화를 상담원에게 분배하는 방법은 세가지가 있는데,
- 순서대로 배분하기: 모든 상담원에게 순서대로 전화를 준다.
- 짧은 대디열 찾아 헤메기 : 가장 적은 대기명을 보유하지않은 상담원에게 전화를 준다.
- 우선순위에 따라 배분하기 : 고객 등급에 따라 등급이 높을 수록 업무능력이 높은 상담원에게 우선 배분한다.
이 전화를 배분하기 위해 Scheduler 인터페이스를 만들어보자.
1
2
3
4
5
6
package interfaceex;
public interface Scheduler {
public void getNextCall();
public void sendCallToAgent();
}
그럼 순서대로 배분하는 경우 부터 인터페이스의 구현부를 채워보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package interfaceex;
public class RoundRobin implements Scheduler {
@Override
public void getNextCall(){
System.out.println("전화를 순서대로 대기열에서 가져온다.");
}
@Override
public void sendCall() {
System.out.println("다음 순서 상담원에게 배분");
}
}
먼저 순서대로 배정하는 경우 (RoundRobin)를 구현해보았다.
마찬가지로implements를 사용하여 Scheduler 인터페이스를 기반으로 클래스를 선언헀다.
다음은 짧은 대기열 먼저 분배하는 LeastJob 클래스, 우선순위에 따라 배분하는 PriorityAllocation 클래스를 구현해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package interfaceex;
public class LeastJob implements Scheduler{
@Override
public void getNextCall() {
System.out.println("상담 전화를 순서대로 대기열에서 가져옵니다.");
}
@Override
public void sendCall() {
System.out.println("대기가 적은 순으로 상담원에게 할당합니다.");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package interfaceex;
public class PriorityAllocation implements Scheduler{
@Override
public void getNextCall() {
System.out.println("고객등급이 높은 순서대로 대기열에서 가져온다.");
}
@Override
public void sendCall() {
System.out.println("업무 역량이 높은 상담원 우선 배정한다.");
}
}
이제 해당 클래스들을 main 함수에서 구동해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package interfaceex;
import java.io.IOException;
public class SchedulerTest {
public static void main(String[] args)throws IOException {
System.out.println("전화 상담 할당 방식을 선택하세요");
System.out.println("R : 한명씩 차례로 할당");
System.out.println("L : 대기가 적은 상담원에게 할당");
System.out.println("P : 우선순위가 높은 고객 먼저 할당");
int ch = System.in.read();
Scheduler scheduler = null;
if(ch == 'R' || ch == 'r'){
scheduler = new RoundRobin();
}
else if(ch == 'L' || ch == 'l'){
scheduler = new LeastJob();
}
else if(ch == 'P' || ch == 'p'){
scheduler = new PriorityAllocation();
}
else{
System.out.println("지원하지 않는 기능입니다.");
return;
}
scheduler.getNextCall();
scheduler.sendCall();
}
}
위 함수를 실행하면, 입력받은 전화 할당 방식에 따라서 각 기능에 따른 메서드가 실행된다.
만약 새로운 정책을 추가해야 한다면, 앞에서와 마찬가지로 Scheduler 인터페이스를 구현하는 새 클래스로 만들면된다. 어떤 클래스를 구현하던간에 클라이언트가 인터페이스를 구현한 클래스를 사용하는 방식은 다음과 같다.
1
2
scheduler.getNextCall()
scheduler.sendCall();
이렇게 클라이언트 프로그램은 각 클래스의 구현 방법을 몰라도 인터페이스에서 선언된 매개변수, 반환값을 보고 클래스를 사용할 수 있다.
인터페이스 상수
인터페이스는 추상 메서드로 이루어지므로 인스를 생성할 수 없으며 멤버 변수도 사용할 수 없다.
1
2
3
4
public interface Calc{
double PI = 3.14;
int ERROR = -9999999999;
}
그런데, 위의 코드와 같이 변수를 선언해도 오류가 발생하지 않는다. 그 이유는 인터페이스에서 선언한 변수를 컴파일하면 상수로 변환 되기 떄문이다.
디폴트 메서드와 정적 메서드
원래 자바는 인터페이스를 구현한 여러 클래스에서 사용할 메서드가 클래스마다 같은 기능을 제공하는 경우에도 인터페이스에 메서드를 구현할 수 없기 때문에 클래스마다 똑같이 그 기능을 반복해 구현해야 했다. 하지만 자바 8부터는 인터페이스 활용성을 높이기 위해 디폴트 메서드와 정적 메서드를 지원한다.
정적 메서드는 인스턴스 생성과 관계없이 사용할 수 있는 메서드이다. 하지만 디폴트 메서드나 정적 메서드를 추가 했다고 해서 인터페이스가 인스턴스를 생성할 수 있는건 아니다. 그럼 인터페이스에 구현하는 디폴트 메서드와 정적 메서드가 무엇인지 살펴보자.
디폴트 메서드
디폹트 메서드란 말 그대로 기본으로 제공되는 메서드로, 인터페이스에서 구현하지만 이후 인터페이스를 구현한 클래스가 생성되면 그 클래스에서 사용할 기본 기능이다. 디폴트 메서드는 default를 사용해 선언한다.
1
2
3
4
5
6
7
public interface Calc {
...
default void description(){
System.out.println("정수 계산기를 구한다.");
}
}
디폴트는 일반 메서드와 동일하게 구현하고 자료형 앞에 default만 붙여주면 된다.
그럼 디폴트 메서드를 호출해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package interfaceex;
public class Test {
public static void main(String[] args){
int num1 = 10;
int num2 = 5;
CompleteCalc cal = new CompleteCalc();
System.out.println(cal.add(num1,num2));
System.out.println(cal.substract(num1,num2));
System.out.println(cal.divide(num1,num2));
System.out.println(cal.times(num1,num2));
cal.showInfo();
cal.description();
}
}
다음과 같이 인스턴스 cal을 생성하면 디폴트 메서드를 호출 할 수 있다.
이러한 디폴트 메서드도 마찬가지로 하위클래스에서 기능을 다르게 구현하고 싶다면, 재정의가 가능하다.
정적 메서드
정적 메서드는 static 예약어를 사용하여 선언하며 클래스 생성과 무관하게 사용할 수 있다.
정적 메서드를 사용할 때는 인터페이스 이름으로 직접 참조하여 사용한다. 그러면 Calc 인터페이스에 매개변수로 전달된 배열의 모든 요소 값을 더하는 정적 메서드 total()을 추가해 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package interfaceex;
public interface Calc {
double PI = 3.14;
int ERROR = -9999999;
int add(int num1,int num2);
int substract(int num1, int num2);
int times(int num1, int num2);
int divide(int num1,int num2);
default void description(){
System.out.println("정수 계산기를 구한다.");
}
static int total(int[] arr){
int total = 0;
for(int i : arr){
total+=i;
}
return total
}
}
위 처럼 static을 사용하여 정적 메서드인 total을 선언해보았다.
마찬가지로 main함수에서 호출해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package interfaceex;
public class Test {
public static void main(String[] args){
int num1 = 10;
int num2 = 5;
CompleteCalc cal = new CompleteCalc();
System.out.println(cal.add(num1,num2));
System.out.println(cal.substract(num1,num2));
System.out.println(cal.divide(num1,num2));
System.out.println(cal.times(num1,num2));
cal.showInfo();
cal.description();
int[] arr = {1,2,3,4,5}
System.out.println("총합 :"+ Calc.total(arr));
}
}
마지막줄에 정적메서드인 total을 호출하였다. 특이한점은 인스턴스로 참조한게 아니라 직접 인터페이스 이름으로 정적메서드를 호출하였다.
private 메서드
자바 9부터는 인터페이스에 private 메서드를 구현할 수 있다. private 메서드는 인터페이스를 구현한 클래스에서 사용하거나 재정의할 수 없다. 따라서 기존에 구현된 코드를 변경하지 않고 인터페이스를 구현한 클래스에서 공통으로 사용하는 경우에 private 메서드로 구현하면 코드 재사용성을 높일 수 있다.
private 메서드는 코드를 모두 구현해야 하므로 추상 메서드에 private을 사용할 순 없지만, static은 함께 사용할 수 있다. 그럼 Calc인터페이스에 private와 private static을 구현하고 이를 호출해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package interfaceex;
public interface Calc {
double PI = 3.14;
int ERROR = -9999999;
int add(int num1,int num2);
int substract(int num1, int num2);
int times(int num1, int num2);
int divide(int num1,int num2);
default void description(){
System.out.println("정수 계산기를 구한다.");
myMethod();
}
static int total(int[] arr){
int total = 0;
for(int i : arr){
total+=i;
}
myStaticMethod();
return total;
}
private void myMethod() {
System.out.println("private 메서드입니다.");
}
private static void myStaticMethod(){
System.out.println("private static 메서드 입니다.");
}
}
private와 static private로 선언한 메서드를 각각 디폴트 메서드와 정적메서드에서 호출시켜주면 올바르게 호출 된 것을 확인 할 수 있다.
인터페이스 활용하기
한 클래스가 여러 클래스를 상속받으면 메서드 호출이 모호해지는 문제가 발생하곤 한다. 하지만 인터페이스는 한 클래스가 여러 인터페이스를 구현할 수 있다. 만약 Customer 클래스가 추상 메서드 buy()와 sell() 두 인터페이스를 구현하고 있다고 하자.
1
2
3
public interface Buy{
void buy();
}
1
2
3
public interfce Sell{
void sell();
}
이렇게 선언해준 추상메서드 두개를 구현해보면,
1
2
3
4
5
6
7
8
9
public class Customer implemeent Buy,Sell{
@override
public void buy(){
System.out.println("구매하기)
}
public void sell(){
System.out.println("구매하기)
}
}
인터페이스는 구현 코드나 멤버 변수를 갖지 않기 떄문에 여러 개를 동시에 구현 가능하다. 두 인터페이스에 이름이 같은 메서드가 선언되어도 구현은 클래스에서 이루어지므로, 어떤 메서드를 호출해야 하는지 모호하지 않은것이다.
이제, 테스트 프로그램에서 두가지 인터페이스를 구현가능한 Customer를 호출해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package interfaceex;
public class Test2 {
public static void main(String[] args){
Customer customer = new Customer();
Buy buyer = customer;
buyer.buy();
Sell seller = customer;
seller.sell();
if(seller instanceof Customer){
Customer customer2 = (Customer) seller;
customer2.buy();
customer2.sell();
}
}
}
Customer 클래스형인 customer를 Buy 인터페이스 형인 buyer에 대입하여 형 변환한 뒤 buy()를 호출해주고, 다음은 customer를 Sell 인터페이스형은 seller에 대입하여 sell() 메서드를 호출 해보았다. 이처럼 우리가 구현한 Customer 클래스는 두가지를 다중 상속받은 것처럼 Buy와 Sell로 형변환이 가능한 것을 알 수 있다. 상속과 마찬가지로 instanceof로 하위클래스로의 형변환도 당연히 가능하다.
두 인터페이스의 디폴트 메서드가 중복되는 경우
정적 메서드는 인스턴스 생성과 상관없이 사용이 가능하다. 만약 Customer 클래스가 Buy,Sell 두 인터페이스를 구현하고
두 인터페이스에 pay()라는 정적메서드가 있다고 생각해보자. 이 경우 Buy.pay()와 Sell.pay()로 특정하여 호출할 수 있기 떄문에 문제가 되지 않는다. 그런데 디폴트 메서드는 어떻게 될까? 디폴트 메서드는 인스턴스를 생성해야 호출 가능한 메서드이기 때문에, 다음처럼 이름이같은 디폴트 메서드가 두 인터페이스에 있으면 문제가 된다.
다음 코드를 보자.
1
2
3
4
5
6
7
public interface Buy{
void buy();
default void order(){
System.out.println("구매 주문");
}
}
1
2
3
4
5
6
7
public interface Sell{
void buy();
default void order(){
System.out.println("판매 주문");
}
}
이렇게 정의 하고 두 인터페이스를 모두 구현하면 Customer 클래스에서는 오류가 발생한다.
두 디폴트 메서드가 중복되었으므로, Customer 클래스에서 재정의 해주어야 동일한 이름이어도 오류가 발생하지 않는다.
1
2
3
4
5
6
7
8
9
public class Customer implements Buy, Sell{
...
@Overide
public void order(){
System.out.println("고객 판매 주문")
}
}
위 와같이 재정의 해주면 오류가 발생하지않고, 재정의된 메서드가 호출된다.
인터페이스 상속
인터페이스 간에도 상속이 가능합니다. 인터페이스 간 상속을 구현 코드를 통해 기능을 상속하는 것이 아니므로 형 상속(type inheritance) 라고 부른다. 클래스의 경우에는 하나의 클래스만 상속받을 수 있지만, 인터페이스는 여러 개를 동시에 상속 받을 수 있다. 한 인터페이스가 여러 인터페이스를 상속받으면, 상속받은 인터페이스는 상위 인터페이스에 선언한 추상 메서드를 모두 가지게 된다.
만약 X와 Y라는 인터페이스를 선언해주고 이를 상속받는 인터페이스 MyInterface를 선언해준다면 이 인터페이스는 3개의 추상 메서드를 가진 인터페이스가 되는것이다. 따라서 MyInterface를 구현하려면 3개의 메서드를 구현해주어야 한다.
인터페이스 구현과 클래스 상속 함께 쓰기
한 클래스에서 클래스 상속과 인터페이스 구현을 모두 할 수도 있다. 다음은 Queue 인터페이스를 구현하고 Shelf 클래스를 상속받는 BookShelf 클래스를 나타낸 그림입니다.
BookShelf 클래스는 책을 넣은 순서대로 꺼내어 볼 수 있도록 만들려고 한다. BookShelf 클래스를 구현하기 전에 더 큰 개념인 Shelf 클래스를 먼저 만들어보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package bookshelf;
import java.util.ArrayList;
public class Shelf {
protected ArrayList<String> shelf;
public Shelf(){
shelf = new ArrayList<String>();
}
public ArrayList<String> getShelf(){
return shelf;
}
public int getCount(){
return shelf.size();
}
}
Shelf 클래스에는 자료를 순서대로 저장할 배열 객체를 선언했다. 이름을 저장 할 수 있도록 자료형은 String을 사용한다. getShelf() 메서드는 저장되어있는 배열 shelf를 반환하고, getCount() 메서드는 배열 shelf에 저장된 요소 개수를 반환한다.
그럼 Queue 인터페이스를 정의해보자. Queue 인터페이스는 먼저 들어온 자료를 먼저 꺼내는 기능을 정의한다.
1
2
3
4
5
6
7
package bookshelf;
public interface Queue {
void enQueue(String title); //배열의 맨 마지막에 추가
String deQueue(); //배열의 맨 처음 항목 반환
int getSize(); //현재 Queue에 있는 개수 반환
}
이제 Shelf 클래스와 Queue 인터페이스를 사용하여 BookShelf 클래스를 다음과 같이 구현 할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package bookshelf;
public class BookShelf extends Shelf implements Queue{
@Override
public void enQueue(String title) {
shelf.add(title);
}
@Override
public String deQueue() {
return shelf.remove(0)
}
@Override
public int getSize() {
return getCount();
}
}
위의 코드를 보면 Shelf를 상속받고 Queue도 구현하는 BookShelf의 코드이다.
상속받은 shelf 멤버 변수를 사용해서 Queue 인터페이스에서 선언한 메서드를 구현한 것을 볼 수있다.
이제 책의 이름을 배열에 추가해주고, 순서대로 앞에서부터 출력해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package bookshelf;
public class BookShelfTest {
public static void main(String[] args){
Queue shelfQueue = new BookShelf();
shelfQueue.enQueue("태백산맥 1");
shelfQueue.enQueue("태백산맥 2");
shelfQueue.enQueue("태백산맥 3");
System.out.println(shelfQueue.deQueue());
System.out.println(shelfQueue.deQueue());
System.out.println(shelfQueue.deQueue());
}
}
>>> 태백산맥 1
>>> 태백산맥 2
>>> 태백산맥 3
올바르게 배열의 앞에서부터 반환되어 큐의 역할을 잘 수행하는 것을 볼 수 있다.
실무에서 인터페이스를 사용하는 경우
인터페이스는 클래스가 제공할 기능을 선언하고 설계하는 것이다. 만약 여러 클래스가 같은 메서드를 서로 다르게 구현한다면 어떻게 해야할까? 우선 인터페이스에 메서드를 선언한 다음 인터페이스를 구현한 각 클래스에서 같은 메서드에 대해 다양한 기능을 구현하면 된다. 이것이 바로 인터페이스를 이용한 다형성의 구현이다.
이런 경우를 생각해보자. 어느회사에서 자료를 저장하기 위해 데이터베이스를 사용한다고 하면, 처음에는 MySQL 데이터 베이스를 사용했는데, 이 시스템을 다른 회사에 가서 설치하려고 하니 오라클 데이터 베이스를 사용하여 해달라고 하고, 또 다른 회사는 MS-SQL을 사용한다고 한다. 프로그램은 하나인데 사용하는 데이터 베이스가 제각각 인 것이다. 하지만 이프로그램의 웹페이지나 모바일 페이지는 데이터베이스의 종류와 관계없이 동일하게 수행된다.
데이터베이스와 연관되는 코드는 프로그램의 특정 부분인 것이지,나머지는 동일한것이다.
이런경우에 데이터베이스 기능을 인터페이스로 정의해주면 된다. 그리고 인터페이스 정의에 맞게 여러 데이터베이스 관련 모듈을 개발하면 되는것이다.
인터페이스를 잘 정의하는 것이 확장성 있는 프로그램을 만드는 시작이라고 할 수 있다.
댓글남기기