JAVA/JAVA Basic

[JAVA ] 인터페이스(interface)

IT록흐 2021. 6. 20. 09:01
반응형

 

 

 

이전 포스팅에서

자동타입변환(Promotion)을 정리하며

'객체의 부품화' 이야기를 한 적이 있다.

 

 

[ JAVA ] 다형성

한 번의 코딩으로 여러 개의 객체를 생성할 수 있는 원리가 '다형성(polymorphism)'이다. 상속과 같이 불필요한 코드 사용을 줄이는 중요한 개념이다. 개인적으로 다형성의 의미는 참조변수에 얼마

lordofkangs.tistory.com

 

객체의 부품화

 

 

 

자동 타입 변환(promtion)

 

 

자동타입변환이 없다면, 코딩을 할 때 car라는 변수가 아닌, Tesla(), Hyundae(), Toyota()의 '전용 타입'의 참조변수를 사용하여 코딩을 해야한다.

Tesla t1 = new Tesla(); // 현대차로 바뀌면

t1.run(); // 얘도 수정
t1.accelerate(); // 얘도 수정
t1.stop(); // 얘도 수정해야 됨

만약 차가 테슬라가 아닌 현대차로 바뀌었다고 가정해보자, 현대차로 바뀌었다면 t1 참조변수가 쓰인 모든 코드를 모두 수정해야한다. 이는 유지보수에 엄청난 비효율을 만든다. 그래서 자동타입변환을 통해 car라는 더 큰 추상개념을 사용하면 효율적인 유지보수가 가능하다.

Car car = new Tesla(); // 이 부분만 바꾸면 됨!

car.run();
car.accelerate();
car.stop();

 

자동타입변환을 사용하면, 나중에 객체를 바꾸어야 할 때, 객체생성 부분의 코드만 수정해주면 된다. Car car = new Hyundae(); 이렇게 수정하면 나머지 car참조변수의 메소드는 자동으로 Hyundae의 메소드를 가리킨다.

이것이 바로 '객체의 부품화'다. 코드가 어느 한 객체에 종속되지않고 여러 객체를 떼었다 붙였다 할 수 있다.

 

인터페이스

 

Promotion을 기반으로

여러 개념을 더하여

유지보수성을 최고로 올리는 개념이

'인터페이스'이다.

 

 

 

 

 

인터페이스를 생성해주고

코드를 작성해보았다.

 

추상메소드

 

public interface Remotecontrol {

	void turnOn(); // 추상 메소드
	void turnOff(); // 추상 메소드

}

 

 

일반적인 메소드형식이 아니다.

 

{}가 없다.

 

이런걸 두고

추상메소드라 부른다.

 

추상메소드의 존재 이유는

'지침'이다.

 

 

인터페이스는 본인의 객체를 생성해서 무언가를 해보려는 것이 아니다. 인터페이스는 자신을 implements한 클래스들에게 일종의 표준을 제시한다.

 

 

인터페이스를 implements한 클래스는

인터페이스의 추상메소드를 '재정의(Override)'

하지 않으면 이렇게 오류가 난다.

 

인터페이스는 '틀'을 강요한다.

 

'틀'은 '표준화', '규격화'를 제공한다. 이는 인터페이스의 사용 목적이다. "해당 기능을 구현하려면, 구현 클래스들은 아래와 같은 표준과 규격을 맞추어라!" 라고 말하는 것과 같다.

 

 

 

기능은 고유하나 기술자는 다양하다. 특정 기능을 수행할 객체를 만들려고 하는데, 기능과 해당 클래스를 직접 연결시키면 둘은 '종속'이 된다. 종속이 되버리면 유지보수 과정에서 다른 기술자로 대체하기가 상당히 어려워진다. 이미 기능의 상당부분이 해당 객체와 얽혀있기 때문이다.

 

그러므로 중간에 인터페이스를 둔다. 인터페이스가 기능과 클래스 사이에서 소통을 중재한다. 인터페이스는 구현을 담당할 클래스들에게 '표준'을 제시하고 구현 클래스들은 표준대로 오버라이딩을 해서 구체화한다.

 

 

<구현클래스>

public class TV implements Remotecontrol {

	@Override
	public void turnOn() {
		System.out.println("TV를 켭니다.");
	}

	@Override
	public void turnOff() {
		System.out.println("TV를 끕니다.");
	}
}
​

 

오버라이딩을 한 후,

구현클래스를 토대로 객체를 생성한다.

 

Remotecontrol remote = new TV();

 

인터페이스를 참조 타입으로 하고 참조변수에 구현클래스의 객체 주소를 넣는다. 이는 자동 타입 변환(Promotion)이다.

 

모든 타입의 목적은 데이터에 알맞는 범위를 설정하여 메모리를 효율적으로 사용하는 것에 있다. int의 범위는 –2,147,483,648 ~ 2,147,483,647로 한정되어있고 클래스 타입은 정의된 메소드와 필드로 한정되어있다.

 

그러므로 TV 클래스에 다양한 메소드가 있다한들, Remotecontrol 인터페이스 타입으로 선언된 이상, 인터페이스에 선언된 메소드와 필드만 접근할 수 있다. (표준화)

 

이것이 바로 인터페이스의 '틀'이고, 위에서 말한 '객체의 부품화'이다.

 

디폴트메소드

 

추상메소드가 아니면 defalut나 static 접근제한자를 넣어야 함.

 

 

 

이클립스는 인터페이스가 일반적인 메소드를 생성하는 것을 막는다. 인터페이스는 유지보수성을 극도로 올리기 위해, 강제성이 강하게 부여된 클래스이다. 위 사진처럼 메소드를 일반적으로 정의하면 에러가 난다.

 

1. 추상메소드

2. 디폴트메소드

3. 정적메소드

 

이 3가지가 인터페이스에서 생성가능한 메소드들이다. 디폴트 메소드를 사용해보자.

 

 

유지보수에는

부품교체도 있지만

기능 추가도 있다.

 

 

기능을 추가하려면 인터페이스에 추상메소드를 생성하고 모든 구현클래스에서 해당 추상메소드를 오버라이딩 해야한다. 이렇듯 기능이 추가되면 코드를 수정해야하는 클래스가 많아진다. 이런 문제를 해결하기 위해, '디폴트메소드'가 존재한다. 인터페이스에 디폴트메소드를 추가하면 구현 클래스들은 해당 메소드를 상속받겠되니 굳이 자신의 클래스에 코드를 추가하지 않아도 사용이 가능해진다.

 

<인터페이스 클래스>

public interface Remotecontrol {
    // 본래 기능
	void turnOn();
	void turnOff();
	
    // 음소거 기능 추가
	default void mute(boolean mute) { 
		if(mute) {
			System.out.println("소리를 끕니다.");
		}
		else {
			System.out.println("소리를 킵니다.");
		}
	}
}

 

<구현 클래스>

public class TV implements Remotecontrol {

	@Override
	public void turnOn() {
		// TODO Auto-generated method stub
		System.out.println("TV를 켭니다.");
	}
	@Override
	public void turnOff() {
		// TODO Auto-generated method stub
		System.out.println("TV를 끕니다.");
	}
}

 

<메인 클래스>

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Remotecontrol remote = new TV();
		remote.mute(true); // 음소거 ON
	}
}

 

<출력결과>

이렇듯 인터페이스 클래스에 디폴트메소드 하나만 추가해주면, 구현클래스는 상속받은 메소드를 그냥 사용해주면 된다.

 

static 메소드

 

static의 가장 큰 특성은

'공용성'에 있다고 이전 포스팅에서 말했다.

 

 

[ JAVA ] static 알아보기

JAVA는 객체지향언어다. 객체들은 데이터를 주고받으며 대화를 한다. 하지만 데이터 중에서도 '공용성'이 높은 데이터들이 있다. 예를들어 테슬라가 만들어내는 차는 모두 '전기차'이다. 그러므

lordofkangs.tistory.com

 

디폴트 메소드는 객체를 생성 후 사용된다. 하지만 static 메소드는 객체 생성없이도 사용가능하다. 그러므로 모든 객체는 static 메소드를 사용할 수 있는 공용성을 가진다.

 

그럼 공용성으로

무엇을 할 수 있을가?

 

바로 객체들의 데이터를

관리하는 목적으로 사용된다.

 

 

예를 들어, NULL 체크를 한다고 가정해보자. 디폴트 메소드는 인터페이스로부터 상속받아 사용되지만, 오버라이딩이 가능하여 메소드가 구현클래스에 종속 될 수 있다. 하지만 static 메소드는 구현 클래스에서 오버라이딩 할 수 없다. Null 체크 기능이 한 가지 구현 클래스에 종속되어버리면, 다른 구현 클래스에 적용되지 못하는 불상사가 벌어진다. 그렇게 되면, 해당 구현 클래스에 새로운 Null 체크 코드를 작성해야만 하는 코드낭비가 발생한다.

 

 

<인터페이스 클래스>

public interface Remotecontrol {
	
	boolean setName(String name); 
	String getName();
	
	static boolean nullCheck(String str) {
		System.out.println("Null Check를 시작합니다.");
        //공백 혹은 NULL 값이 들어있는지 삼항연사자로 확인
		return str == null ? true : " ".equals(str) ? true : false;
	}	
}

 

<구현 클래스>

public class TV implements Remotecontrol {
	
	String name = null;
	
	@Override
	public boolean setName(String name) {
		// TODO Auto-generated method stub

		if(!Remotecontrol.nullCheck(name)) {//Static 메소드 사용
			this.name = name;
			return true;
		}
		else {
			System.out.println("이름 형식이 잘못되었습니다. 다시 입력해주세요!");
			return false;
		}
	}
	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return name;
	}
	
}

 

<메인 클래스>

import java.util.Scanner;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner scan = new Scanner(System.in);
		Remotecontrol remote = new TV();
		String name;
		
		do {
		System.out.print("리모컨과 연결 할 기기 이름을 입력해주세요 : ");
		name = scan.next();
		
		}while(!remote.setName(name)); // 잘못된 값이 입력되면 다시 입력받기
		System.out.println("현재 리모컨과 연결된 기기 이름 : " + remote.getName());
	}
}

 

<출력 결과>

 

이와 같이, static 메소드로 null check 과정을 구현해 놓으면 어떤 객체가 와도 새로운 코드를 작성할 필요없이 null check를 해줄 수 있다. 이외에도 배열이나 리스트를 구현 객체들이 공통적으로 가지고 있다면 이를 정렬시켜주는 메소드도 static으로 만들어 줄 수 있다. 어차피 객체마다 정렬시키는 코드는 똑같을텐데 굳이 구현 클래스마다 정렬하는 코드를 반복해서 작성할 이유가 없기 때문이다.

 

이처럼 static으로 메소드를 만들어주면, '공용성'의 성질을 활용하여 구현 개체들의 데이터를 공통으로 '관리'해줄 수 있는 기능을 추가 할 수 있다.

 

상수필드(static final)

 

인터페이스는

지침이다.

 

객체를 직접 만드는 것이 아닌,

이렇게 객체를 만들어라

하는 표준안이다.

 

그래서

인터페이스는 데이터가 필요없다.

그래서 필드도 없다.

 

하지만 인터페이스를 implements하는 구현 클래스들이 공통으로 필요한 데이터를 제시할 필요성은 있다. 그래서 인터페이스는 구현된 객체가 모두 사용가능하고, 한번 선언되면 절대 수정할 수 없는 데이터를 갖고 있을 필요가 있다.

 

인터페이스는 유일하게 상수필드만 가질 수 있다. 인터페이스 안에서 생성되는 필드는 자동으로 [ public static final ] 이 붙는다.

 

<인터페이스 클래스>

public interface Remotecontrol {
	
	int MAX_VOLUME = 100; //상수필드
	int MIN_VOLUME = 0; // 상수필드
	
	void volumeUp();
	void volumeDown();
	int getVolume();
		
}

 

<구현클래스>

public class TV implements Remotecontrol {
	
	int volume = 0;

    @Override
	public void volumeUp() {
		this.volume += 20;
	}
    @Override
	public void volumeDown() {
		this.volume -= 20;
	}
    @Override
	public int getVolume() {
		return this.volume;
	}
}

 

<메인 클래스>

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Remotecontrol remote = new TV();
		
		
		//볼륨 올리기 
		
		while(remote.getVolume() < Remotecontrol.MAX_VOLUME) {  //인터페이스 상수랑 비교
			
			remote.volumeUp();
			System.out.println("현재 음량 : " + remote.getVolume());
			
			if(remote.getVolume() == Remotecontrol.MAX_VOLUME) {
				System.out.println("더 이상 볼롬을 올릴 수 없습니다.");
			}
		}
		
		System.out.println();
		
		//볼륨 내리기
		
		while(remote.getVolume() > Remotecontrol.MIN_VOLUME) { //인터페이스 상수랑 비교
			
			remote.volumeDown();
			System.out.println("현재 음량 : " + remote.getVolume());
			
			if(remote.getVolume() == Remotecontrol.MIN_VOLUME) {
				System.out.println("더 이상 볼롬을 내릴 수 없습니다.");
			}
		}
	}
}

 

<출력 결과>

 

 

 


 

정리

 

 

1. 추상메소드는 구현클래스들에게 표준안을 제공한다.

 

2. 디폴트메소드는 유지보수간 기능 추가를 가능하게 만든다.

 

3. static메소드는 공용성을 기반으로 구현된 객체들을 '관리'한다.

 

4. 필드는 객체들이 공동으로 상용할 수 있고 함부로 초기화 할 수 없는 상수로 자동 정의된다.

 

 

반응형