[Java 입문기] Java Script와 Java는 무엇이 다를까 3 - 인터페이스와 다형성

파이·2021년 12월 23일
1

아직 제대로 간단한 토이프로젝트도 시도해보지 못했지만 나름 자바의 환경이 슬슬 익숙해 질 때쯤, 자바를 공부한 친구가 인터페이스(interface)를 빨리 배우라고 추천을 해 줬습니다.
사실 Type Script에서 인터페이스를 어렴풋이 접해보았기 때문에, 문제 될 것이 없다고 생각했으나...

하루를 꼬박 걸려서 어렴풋이나마 인터페이스의 목적과 문법을 겨우 익혔습니다.
그런 상태에서 생각 정리차 적는 글이기 때문에

깊고 정확한 정보가 아닐 수 있습니다.
그래도 머리가 그닥인 제가 다시 보고 이해할 수 있게끔 쉽게 적어보자고 합니다.

1. 다형성, 인터페이스 전에 익히고 가자.

사실 어떤 걸 먼저 익히는지는 상관 없는 것 같으나, 그래도 이 개념이 들어왔을 때 인터페이스가 완성 되는 것 같더라구요. 그래서 간단하게 먼저 정리합니다.

다형성 (Polymorphism)은 간단하게 하나의 객체 타입에 다양한 인스턴스를 참조 할 수 있는것 이라고 보면 됩니다.
사실 이게 왜 필요한지 모를수도 있겠는데요, 그러나 저도 타입스크립트를 쓰면서 한 두가지의 요소만 다른 인터페이스를 왕창 적다보니까 너무 복잡해 보였습니다.

때문에 객체에 다형성 (여러 타입을 참조 가능함)을 보장하여, 수십개의 타입이 선언되고 참조되는 복잡성을 방지하여 효율과 유지보수성을 높인다고 할 수 있겠습니다.

때문에 이 다형성을 위한 조건은 제 생각에 크게 한 가지 인데요,

다형성의 조건: 요소가 더 많은쪽은 참조 가능하다.

예시를 보여드리겠습니다.

class Sum { 
	public int getSum (int a, int b) {
		return a + b;
	}
}

class SumUp extends Sum {  // sum에 print 기능을 넣었다!
	public void print(int a, int b) {
		System.out.println(getSum(a, b));
	}
}
  1. 합계를 구하는 getSum() 메서드를 포함한 클래스와 (메서드 1개)
  2. Sum을 상속하고 print() 메서드까지 포함한 SumUp 클래스를 선언했습니다. (메서드 2개)

쉽게 말해, Sum이 SumUp의 부모 클래스입니다.
이 때 객체에 어떤 인스턴스가 참조 가능할까요?

1. 본인은 당연히 참조가 가능하다.

 Sum sum1 = new Sum();
 SumUp sum2 = new SumUp();

자세한 설명은 생략한다.

2. 요소가 크거나 같은 인스턴스를 참조 가능하다.

다르게 말하면, 부모 클래스는 자식 클래스를 참조 가능합니다.

Sum sum1 = new SumUp(); // 가능... 1
// SumUp sum2 = new Sum(); // 불가능...2
  1. 1번이 가능한 이유는 간단합니다.
    참조할 SumUp에 Sum이 갖고있는 메서드(getSum) 을 포함하고 있기 때문입니다.
    print 메서드가 실질적으로 참조되지 못한다 하더라도, 필요한 getSum은 갖고 있기에 오류 없이 가능합니다.

  2. 1번을 이해하면 2번이 불가능한 이유도 간단합니다.
    getSum과 print 두 개의 메서드를 가진 객체 sum2에,
    getSum 메서드 하나뿐인 Sum을 참조한다면 print는 참조할 값이 없기 때문입니다.

때문에, 더 요소가 많은 클래스(인스턴스)를 참조가 가능하다.

가 핵심입니다!



2. 인터페이스(Interface), 효율성을 위한 지침서

제목에서도 간단하게 (제가 이해한) 인터페이스의 목적을 정의하였지만
간단한 예시를 통해 더 쉽게 알아보겠습니다.

A. 인터페이스의 목적

상황: 팀 프로젝트 중, 팀원들에게

"덧셈을 구하는 메서드를 만들어줘!"

라고 요청하였다.
덧셈을 구하는 메서드는 아주 간단하지만, 여러분이 팀원들에게 좀 더 어려운 메서드를 요청했다고 가정해주세요 :)

아무튼 Kim, Lee, Park 세 명의 팀원은 각각 이러한 메서드를 구현했습니다.

class Kim {
	public void sum (int a, int b) {
		System.out.println(a + b);
	}
}

class Lee {
	public void plus (int a, int b) {
		System.out.println(a + b);
	}
}

class Park {
	public double getSum (double a, double b) {
		return a + b;
	}
}

어... 제가 너무 두루뭉술하게 말했나요?
셋 다 틀리진 않았지만 모두 다른 메서드 를 작성했습니다.

때문에 이 메서드를 시험해보려고 하는데,

public class InterfaceApp {
	public static void main(String[] args) {

		Kim result = new Kim();
		result.sum(1, 2);

		result = new Lee(); // 에러: 같은 메서드를 포함하지 않기 때문에 오류가 난다.
		result.sum(1, 2);
		
		result = new Park(); // 에러: 여기도 마찬가지.
		result.sum(1, 2);
	}
}

이런... result라는 객체를 만들어서 시험해보려고 했으나
모두 다른 메서드 때문에 쉽게 테스트가 되지 않는군요.

이런 간단한 상황은 큰 문제가 아니지만, 어렵고 다양한 메서드를 가진 클래스를 부탁하거나, 외주를 맡겼다면 하나하나 일일히 비교해보기 쉽지 않겠군요...


B. 인터페이스의 사용법

그래서 이번엔 "인터페이스" 라는 기능명세서를 가지고 팀원분들께 부탁해 보았습니다.

interface CalSum {
	int sum (int a, int b)
}

interface 인터페이스 명 { 추상메서드 } 로 사용합니다.


이 메서드의 머리(?)처럼 생긴 명세서를 해석하자면,

  1. 정수 두개의 인자를 받고
  2. 정수 타입을 리턴하는
  3. CalSum 이라는 이름을 가진 메서드를 부탁할게!

라는 명세서가 되겠군요.

위에서 보았듯, 저 이름, 인자, 리턴 타입 세 가지를 명확히 지정하여 다형성을 향상시키는 것이 인터페이스의 목적입니다.


아무튼, 이렇게 부탁했더니 이번엔 세 팀원들이 각각 이렇게 메서드를 구현했습니다.

class Kim implements CalSum {
	public int sum (int a, int b) {
		return a + b; 
	}
}

class Lee implements CalSum {
	public int sum (int a, int b) {
		int value = a + b;
		return value; 
	}
}

class Park implements CalSum {
	public int sum (int a, int b) {
		System.out.println(a + b);
		return a + b; 
	}
}

클래스 뒤에 implements 인터페이스명 으로 사용합니다.


세 팀원 모두 약간씩 다르게 구현했지만 받는 인자와 이름, 리턴 타입 이라는 기준을 훌륭히 지켜주었군요!

때문에

public class InterfaceApp {
	public static void main(String[] args) {
	
		CalSum result = new Kim();
		result.sum(1, 2);
		
		result = new Lee(); // 같은 메서드를 포함하지 않기 때문에, 오류가 난다.
		result.sum(1, 2);
		
		result = new Park(); //여기도 마찬가지.
		result.sum(1, 2);
	}
}

이렇게 손쉽게 테스트 해 볼 수 있었어요!
이런 식으로 작성시, 주의사항이 한 가지 있는데, 바로 다형성의 법칙 입니다.

이는 처음에 result라는 객체를 만들 때, CalSum로 타입을 지정하고 Kim 인스턴스를 참조하였습니다.

Kim, Lee, Park에게 참조된 CalSum이라는 인터페이스는 이 클래스들 보다 요소가 적거나 같기 때문에 이렇게 참조 할 수 있습니다.

아무튼 이렇게 잘 따라준다면, 수 많은 요소를 가진 클래스를 요구하더라도 효과적으로 디버깅과 기능을 테스트 해 볼 수 있겠군요!
굿 잡!



C. 인터페이스에는 뭐가 들어올 수 있을까?

인스턴스 안에 들어올 수 있는 요소들과 그 종류들을 간단하게 설명해보려고 합니다.
참고로, interface 일반적으로 외부 클래스에 참조되기 위해 만들어졌으므로
모든 접근제어자가 public 타입입니다.

1. 상수

[public static final] String TeamName = "Java";
// []괄호 안에 부분은 생략이 가능합니다.

말 그대로 고정된 상수값입니다. 굳이 implements 할 때 적지 않아도 자동으로 상속됩니다. 이 상수를 활용한 메서드를 짠다면 필요하겠군요.

2. 추상 메서드

[public abstract] String join(String name);
// 메서드 이름, 인자 타입과 갯수, 리턴 타입이 주어진다.

위에 팀원들에게 요청했던 메서드의 머리, 추상 메서드입니다.
이 이름과 인자, 리턴 타입을 가진 메서드를
꼭! 오버라이딩 (덮어쓰기) 해야 합니다. 그렇지 않다면 오류가 납니다!

3. 디폴트 메서드

[public] default void printName(String name){
		System.out.println("안녕하세요 " + name + " 입니다");
	}

앞에 default를 붙여서 선언해야하는 디폴트 메서드입니다.
이는 오버라이딩 해도 되지만, 안해도 해당 메서드 자체가 자동으로 상속 됩니다.
하거나 말거나 자유!

4. 정적 메서드

static void byebye(String name){ // default 대신 static을 붙이면 정적 메서드가 된다.
		System.out.println("잘 가, "+ name + " !");
	}

static을 붙여 사용하며, 상수처럼 고정된 메서드입니다.
이것 역시 따로 적지 않아도 그대로 상속되며,
정적 메서드는 인터페이스에서만 접근 가능합니다.


아무튼, 이러한 목적과 요소 외에도 Java에서 정적 메서드가 갖는 의미와 용도가 훨씬 더 큰것 같다.
그래도 이정도만 이해하고 나중에 더 자세하게 이해하고 정리해야지...
또, 여유가 된다면 Type Script의 인터페이스 까지 좀 더 공부하고 싶어졌다 :)

그럼 이만!

profile
기록

0개의 댓글