Life is Good

수콩이의 시선

Coding/JAVA

인터페이스(interface), 접근제한자(public, private, protected, default)

Soocong 2022. 4. 4. 10:51

인터페이스(interface)

 

: 인터페이스란? 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다. 오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않는다. 

추상클래스를 부분적으로만 완성된 '미완성 설계도' 라고 한다면, 인터페이스는 구현된 것은 아무 것도 없고 밑그림만 그려져 있는 '기본 설계도' 라고 할 수 있다.

 

- 자바에서 인터페이스는 객체의 사용 방법을 정의한 타입이다.

- 객체의 교환성을 높여주기 때문에 다형성을 구현하는 중요한 역할을 한다. 

- (클래스는 필드, 생성자, 메소드를 구성 멤버로 가지는데) 인터페이스는 상수와 메소드만을 구성멤버로 가진다.

- 하나의 객체가 아니라 여러객체들과 사용이 가능하므로 어떤 객체를 사용하느냐에 따라서 실행내용과 리턴 값이 다를 수 있다. 따라서 개발 코드 측면에서는 코드 변경 없이 실행 내용과 리턴값을 다양화할 수 있다는 장점을 가지게 된다.

- 인터페이스는 다중상속이 가능하다. (클래스는 다중상속 불가능)

 

 

- 인터페이스의 장점

1) 개발 시간 단축한다.

2) 표준화가 가능하다.

3) 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다. 

4) 독립적인 프로그래밍 가능하다.

 

 

  • 인터페이스 생성

 

File> New> Interface를 선택

 

 

  • 인터페이스 선언

interface 인터페이스명{

}

 

- 모든 멤버변수는 public static final이어야하며, 이를 생략할 수 있다.

- 모든 메서드는 public abstract이어야하며, 이를 생략할 수 있다. 

 

 

  • 인터페이스 구현

public class 구현클래스명 implements 인터페이스명 {

        //인터페이스에 선언된 추상 메소드의 실체 메소드 선언

}

 

 

-클래스에서 클래스 상속받을 때 -> extends

-인터페이스에서 인터페이스를 상속받을 때 -> extends

-클래스에서 인터페이스를 상속받을 때 -> implements

-인터페이스에서 클래스를 상속받을 때 -> (X) 이런경우는 없다.

 

 

인터페이스에서 추상함수를 만들어서 자유롭게 쓰고, 멤버변수를 쓰는 경우는 거의없다. (static final로 선언된 변수는 치환이 안되기 때문)

 

 

함수의 리턴타입이 void인 경우에는 생략가능. 왼쪽, 오른쪽 둘다가능

 

 

 

 

예제 1)

/**

 * @Author : 김소현
 * @Date   : 2020. 5. 8.
 * @Description : 스마트폰의 모델 스펙은 다음과 같다.

 * @Description : 스마트폰의 모델 스펙은 다음과 같다.
 * 제품명      전화송수신      3G/LTE/5G         TV리모컨기능
 * Lphone     가능               3G                    탑재
 * Sphone     가능               LTE                   탑재
 * Iphone      가능               5G                   미탑재
 */

 

Phone.java , PhoneMain.java

IPhone.java, LPhone.java, SPhone.java

 

 

인터페이스는 다중상속이 가능하다. 

 

  • 익명 구현 객체

구현 클래스를 만들어 사용하는 것이 일반적이고, 클래스를 재사용할 수 있기 때문에 편리하지만, 일회성의 구현 객체를 만들기 위해 소스 파일을 만들고 클래스를 선언하는 것은 비효율적이다. 잡자에서는 소스 파일을 만들지 않고도 구현 객체를 만들 수 있는 방법을 제공하는데, 그것이 익명 구현 객체이다.

 

작성 시 주의할 점은 하나의 실행문이므로 끝에는 세미콜론(;)을 반드시 붙여야한다.

 

인터페이스 변수 = new 인터페이스(){

    //인터페이스에 선언된 추상 메소드의 실체 메소드 선언

};

 

 


 

접근제한자

  • public => 다 가능하다.
  • private => 다 불가능하다.
  • protected => 상속해서 사용할 수 있다. 
  • default => 패키지가 같은 경우에 자유롭게 쓸 수 있다. 
접근 지정자 접근 범위 동일 클래스 동일 패키지 다른 패키지의 자식클래스 다른 패키지
public 접근 제한 없음 O O O O
protected 동일 패키지와 상속 받은 클래스 내부 O O O  
default 동일 패키지 내에서만 O O    
private 동일 클래스 내에서만 O      

 

- 접근 제한자를 사용하는 이유?

1) 외부로부터 데이터를 보호하기 위해서

2) 외부에는 불필요한, 내부적으로 사용되는 부분을 감추기 위해서

=> private를 사용하는 이유(캡슐화)

 

 

인터페이스 개념과 역할

극단적으로 동일한 목적 하에 동일한 기능을 수행하게끔 강제하는 것이

바로 인터페이스의 역할이자 개념이다.  

자바의 다형성을 극대화하여 개발코드 수정을 줄이고

프로그램 유지보수성을 높이기 위해 인터페이스를 사용한다. 

 

교수님이 학생들에게 논문을 쓰라고 했다.

 

A학생은 PPT로 논문을 11일날 제출했다.

B학생은 EXCEL 2016 로 논문을 12일날 제출했다.

C학생은 EXCEL 2019 로 자기만의 색깔을 입혀 표 형식으로 12일날 제출했다.

D학생은 메모장에 '논문'을 쓰고 교수님이 말해준 당일 바로 제출했다.

 

 

학생들에게 2019.12.12일 18:00까지 홈페이지 본인 교수 홈페이지 제출란을 통해 논문을 제출해야하고

논문 파일 형식은 .PPT이며 10Page 안에 작성을 해야하고 논문 주제는 '블록체인을 활용한 공인인증서' 이다.

 

자 이번에 서울시는 밤길 여성들의 안전한 귀가를 위해 30억을 투입해 1000개 유명 중국 H사의 CCTV를 설치했다고 치자. 이제 CCTV에서 송출되는 영상을 각 지역 관할 파출소나 경찰서에서 모니터링 할 수 있도록 1년에 걸쳐 10억을 투자해 CCTV 모니터링 프로그램 시스템을 구축했다. 근데,, 문제가 생겼다 H사의 CCTV는 배터리가 불량고 비가 오면 방수처리가 잘 안되어 고장이 나기 시작했다.. 안되겠다 국산 G사의 CCTV로 전부 다 교체하기로 했다. 그런데 맙소사..중국 H사 CCTV의 영상 송출 모듈에만 특화된 프로그램을 만든 것이다. 다시 프로그램을 만들고 시스템을 구축해한다..응?

감이 올 것이다.

 

만약 현재 시중에서 판매되고 있는 CCTV 제조사가 CCTV의 송출 모듈을 모두 공통적으로 규격에 맞는 모듈로 개발했다면 이런 유지보수성의 불편함은 발생되지 않을 것이며 호환성이 높아질 것이다. 정리하면, 제조사가 다른 CCTV여도 영상 송출이라는 동일한 기능을 제공하게 하는 것이 바로 인터페이스이다.

 

자 이제 인터페이스의 개념을 정리하자!

 

인터페이스란? 극단적으로 동일한 목적 하에 동일한 기능을 보장하게 하기 위함!

어떻게? 자바의 다형성을 이용하여  개발코드 수정을 줄이고 유지보수성을 높인다!

 

 

2. 인터페이스 문법과 다형성 이해

 

자.. 이제 인터페이스가 어떤 놈인지는 알았고 실제 문법과 코드를 통해 이해해보자.

인터페이스는 interface 키워드를 통해 선언할 수 있으며 implements 키워드를 통해 일반 클래스에서 인터페이스를 구현할 수 있다.

 

또한, JAVA8 이전까지는 상수, 추상메소드만 선언이 가능하지만,

(상수, 추상메소드만 가능케했다는 것을 통해 그만큼 강제성이 강했다는 것을 유추할 수 있다.)

JAVA8부터 디폴트메소드, 정적메소드가 추가되었다.

(디폴트메소드, 정적메소드를 통해, 구현 강제성 안에 유연함을 심었다고 우선 이해하자)

 

왜 추가되었는지는 아래에서 따로 설명하고 우선 인터페이스에선 4가지를 정의하거나 구현할 수 있다.

 

public interface 인터페이스명 {

 

//상수

타입 상수명 = 값;

 

//추상 메소드

타입 메소드명(매개변수, ... );

 

//디폴트 메소드

default 타입 메소드명(매개변수, ... ){

  //구현부

}

 

//정적 메소드

static 타입 메소드명(매개변수) {

  //구현부

}

 

}

 

그럼 각각 어떤 의미가 있는지 직역해보자.

 

상수 : 인터페이스에서 값을 정해줄테니 함부로 바꾸지 말고 제공해주는 값만 참조해라 (절대적)

추상메소드 : 가이드만 줄테니 추상메소드를 오버라이팅해서 재구현해라. (강제적)

디폴트메소드 : 인터페이스에서 기본적으로 제공해주지만, 맘에 안들면 각자 구현해서 써라. (선택적)

정적메소드 : 인터페이스에서 제공해주는 것으로 무조건 사용 (절대적)

 

참고로 절대적이란 정말 아무것도 손댈 수 없음을 의미하고, 강제적이란 그래도 인터페이스를 implements하지 않으면 피할수는 있기에 강제적이라고 표현했다. 강제성이 있다고 해서 절대적인 것은 아니니.....

 

이제 아래 실제 코드를 보면서 자연스럽게 이해해보자.

 

하나의 예를 들어보겠다. 대한민국에서 은행 사업을 하려면, 금융결제원에서 정의한 어떠한 가이드를 따라야한다고 치고, Bank 라는 이름으로 인터페이스를 만들었다.

이제 어느 은행이든 은행 시스템은 운영하려면 Bank라는 인터페이스 가이드에 맞게 구현해야한다.

인출메소드, 입금메소드는 각 은행에서 오버라이딩 해서 재구현을 해야하며 블록체인 인증 메소드는 무조건 금융결제원에서 제공해주는 메소드를 사용해야 한다. 따라서 정적메소드로 구현하여 오버라이딩을 할 수 없게 만들었다. (그냥 가져다가 쓰라는 소리)

public interface Bank {

	//상수 (최대 고객에게 인출해 줄 수 있는 금액 명시)
	public int MAX_INTEGER = 10000000;
	
	//추상메소드(인출하는 메소드)
	void withDraw(int price);
	
	//추상메소드(입금하는 메소드)
	void deposit(int price);
	
	//JAVA8에서 가능한 defualt 메소드(고객의 휴면계좌 찾아주는 메소드 : 필수구현은 선택사항)
	default String findDormancyAccount(String custId){
		System.out.println("**금융개정법안 00이후 고객의 휴면계좌 찾아주기 운동**");
		System.out.println("**금융결제원에서 제공하는 로직**");
		return "00은행 000-000-0000-00";
	}
	
	//JAVA8에서 가능한 정적 메소드(블록체인 인증을 요청하는 메소드)
	static void BCAuth(String bankName){
		System.out.println(bankName+" 에서 블록체인 인증을 요청합니다.");
		System.out.println("전 금융사 공통 블록체인 로직 수행");
	}
		
	
}

그런데 여기서 디폴트 메소드에 대해서 생각해보자. 도대체 디폴트 메소드는 무엇일까? 하나의 예시를 들어보겠다.

 

금융결제원에서 이미 인터페이스를 각 은행사에 가이드 하였고 정상적으로 서비스가 되고 있는데 갑자기 금융 트렌드가 바뀌면서 고객의 휴면계좌를 찾아주는 서비스를 정부에서 점진적으로 도입하라고 지시를 하였다면.. 어떤일이 벌어질까??

 

추상메소드를 그냥 추가해서 다시 가이드 하면 되지 않을까?? 라고 생각했지만 쉽지 않다. 왜냐...각 은행사마다 개발환경 및 운영환경이 다르고, 휴면계좌 찾아주기 신규 프로세스를 도입하는데 있어 은행사마다 개발기간이 모두 상이하기 때문에 조금은 러프한 메소드를 추가해줘야 한다. 즉, 만약 추상메소드를 인터페이스에서 추가한다면, 이를 implements한 모든 클래스에서 강제적으로 추상메소드를 구현해야하고 구현하지 않을시 전부 에러가 난다. 

 

하지만 디폴트 메소드를 정의하고 기본 구현부를 제공하고 만약 기본 구현부가 맘에 들지 않으면 각자가 오버라이딩을 하여 재구현할 수 있도록 선택적인 메소드를 가이드한다면 시스템 운영 유지보수성이 확보가 될 것이다.

이를 더 쉽게 말하면, 결국 이미 운영되고 있는 시스템에서 추가 요건으로 인해 불가피하게 반영을 해야할 때 디폴트메소드를 쓰면 효과적이란 소리다.

 

자 이제 KB은행, SH은행은 규격화 된 Bank 인터페이스를 통해 각자에 맞는 스타일대로 은행 인출/입금 서비스를 제공한다. 아래 코드를 보자.

public class KBBank implements Bank{

	@Override
	public void withDraw(int price) {
		System.out.print("KB은행만의 인출 로직...");
		if(price < Bank.MAX_INTEGER){
			System.out.println(price+" 원을 인출한다.");	
		}else{
			System.out.println(price+" 원을 인출실패.");	
		}
	}

	@Override
	public void deposit(int price) {
		System.out.println("KB은행만의 입금 로직..."+price+" 원을 입금한다.");
	
	}

}

KB은행은 휴면계좌 찾아주기 메소드를 재구현하지 않았다. 즉 금융결제원이 제공해주는 메소드를 사용하겠다는 뜻이거나 혹은 아직 사용하지 않겠다라고 이해하면 된다.

public class SHBank implements Bank{

	@Override
	public void withDraw(int price) {
		System.out.println("SH은행만의 인출 로직...");
		if(price < Bank.MAX_INTEGER){
			System.out.println(price+" 원을 인출한다.");	
		}else{
			System.out.println(price+" 원을 인출실패.");
		}
	}

	@Override
	public void deposit(int price) {
		System.out.println("SH은행만의 입금 로직..."+price+" 원을 입금한다.");
		System.out.println("SH은행은 별도의 후행처리 작업을 따로 한다.");
	
	}
	
	//JAVA8에서 가능한 defualt 메소드(고객의 휴면계좌 찾아주는 메소드)
	@Override
	public String findDormancyAccount(String custId){
		System.out.println("**금융개정법안 00이후 고객의 휴면계좌 찾아주기 운동**");
		System.out.println("*SH은행만의 로직 적용*");
		return "00은행 000-000-0000-00";
	}

}

 하지만 SH은행은 휴면계좌 찾아주기 메소드를 재정의하여 SH은행사 만의 휴면계좌 찾아주기 로직을 재구현했다. 

 

public class KakaoBank{

	public void kakaoWithDraw(int price) {
		System.out.print("Kakao은행만의 인출 로직...");
		System.out.println(price+" 원을 인출한다.");	
	}

	public void kakaoDeposit(int price) {
		System.out.println("Kakao은행만의 입금 로직..."+price+" 원을 입금한다.");
	}
	
	public void kakaoFindDormancyAccount(){
		System.out.println("kakao은행만의 휴면계좌 찾아주기 로직");
	}

}

마지막으로 아래 신규 인터넷 은행사 카카오뱅크 코드를 보자. 위 코드에서 볼 수 있듯이 카카오뱅크는 인터페이스를 implements를 하지 않은 채 자신만의 메소드를 구현했다. 어떤 문제점이 있을까? 당연...금융결제원에서 제공해주는 그 어떠한 서비스도 사용할 수 없으며 호환성이 없으며 연동이 불가할 것이다. 

 

아래 메인 소스를 보면, bank = new kakaoBank(); 부분에서 type mismatch 에러가 날 것이다.

또한, 자바의 다형성을 극대화 하여 개발 코드 수정을 줄일 수 있다고 했는데 어떤부분에서 가능한 것일까?

만약 이 메인함수가 특정 대학교에 등록금 인출, 입금 등의 업무와 관련있는 소스라고 하자. 대학교는 간혹 등록금 납부 주관 은행을 교체하기도 한다. 물론 가상계좌를 통해 납입하지만 주은행을 변경하게 되면 대학교 등록금 납부 시스템에

기존은행에서 교체할 은행으로 변경을 해줘야한다.

 

이럴 경우 간단하게 인스턴스만 바꾸면 호환성이 보장된 상태에서 동일한 기능을 수행할 수 있을 것이다. 물론 해당 소스에서 보면, 금액, 고객ID, 은행명 등 하드코딩이 들어가 있지만 예시니까 이해해 주시길.....^^;;

 

public class Main {

	public static void main(String[] args) {
		
		Bank bank = new KBBank();
		bank.withDraw(100);
		bank.deposit(100);
		bank.findDormancyAccount("763231");
		Bank.BCAuth("KBBank");
		
		System.out.println("\n*************인스턴스 교체!!***************\n");
		
		bank = new SHBank();
		bank.withDraw(100);
		bank.deposit(100);
		bank.findDormancyAccount("4311");
		Bank.BCAuth("SHBank");
		
		System.out.println("\n*************카카오은행 연동 실패!!***************\n");
		//호환성 불가
		/*
		bank = new KakaoBank();
		bank.withDraw(100);
		bank.deposit(100);
		bank.findDormancyAccount("4311");
		*/
		
		System.out.println("\n*************대학교 주은행  교체!!***************\n");
		
		bank = new KBBank(); //new KBBank();
		bank.withDraw(20000);
		bank.deposit(1000);
		bank.findDormancyAccount("855512");
		Bank.BCAuth("SHBank");

	}

}

 

 카카오뱅크 부분을 제거하고 수행하면 아래와 같은 결과를 얻을 수 있다.