인터페이스란?
만약 사람이 움직이는 것을 코드로 구현할 때, 사람은 걷기 뛰기 달리기 등 다양한 동작이 가능하다. 이러한 행동들을 필요할 때마다 추가를 한다면 코드를 작성하는 입장에서 굉장히 번거로울 것이다. 인터페이스를 사용하면 이러한 번거로움이 없어진다. 즉, 사람과 행동 사이에 중개자 역할을 담당하는 것이 인터페이스다. 그리고 행동에 필요한 틀을 선언해 놓아 구현을 강제한다.
장점
- 중개자를 통해 코드를 작성하기 때문에 동시 작업이 가능해진다.
(만약 중개자가 없는 상태에서 동시개발을 진행한다면 추가로 변경해야 하는 부분이 생겨 꼬일 수 있다.)
- 선언과 구현이 분리되어 있기 때문에 구현 부분을 수정해도 다른 클래스에 영향을 미치지 않는다.
- 코드 변경 없이 다양한 실행 내용이나 결괏값을 구할 수 있다.
역할
1. 구현을 강제
- 인터페이스는 구현부분이 없어 다른 클래스가 상속을 받는다면 무조건 구현을 해주어야 한다.
- 부모 클래스의 메서드를 재정하기 때문에 @Override를 붙여준다.(안 붙여도 문제없음)
- 구현이 강제되기 때문에 실수없이 구현할 수 있도록 도와준다.
2. 다형성 제공
- 공통적으로 구현하고 있는 인터페이스를 데이터 타입으로 객체를 선언할 수 있어, 실제 클래스가 무엇이냐에 따라서 다르게 동작할 수 있는 다형성을 제공한다.
- 개발코드 수정을 줄일 수 있고, 프로그램 유지보수성을 높일 수 있다.
- 예시를 보자.
Ex) 아래와 같은 경우 3개의 객체 모두 C클래스를 사용해 객체를 만든다. 하지만, 상속받은 인터페이스를 타입으로 객체를 생성할 수 있다.
c1은 I1과 I2 모두 구현해 놓았기 때문에 A(), B() 메소드 모두 실행이 가능하다. 하지만, c2는 자신의 인터페이스에 선언된 A() 메소드만 실행이 가능하고, c3도 마찬가지로 B() 메소드만 실행이 가능하다.
interface I1 {
public String A();
}
interface I2 {
public String B();
}
class C implements I1, I2 {
public String A() { return "A" }
public String B() { return "B" }
}
public class Main {
public static void main(String[] args) {
C c1 = new C();
I1 c2 = new C();
I2 c3 = new C();
}
}
3. 결합도 낮춤
- 구현체에서 만약 직접 행동을 결정한다면 계속 수정해야 하는 번거로움이 생긴다. 이 상태를 의존도가 높은 상태다 라고 말한다.
- 반대로, 구현체에서 행동을 스스로 결정하지 않고 외부에 맡긴다면 1개의 동작이 아니라 입력값에 따라 다양한 동작이 가능해 의존도가 낮아진다. 이 과정에서 객체들 서로 간의영향력이 줄어들기 때문에 동시에 결합도도 낮아진다.
- 수정이 필요한 경우 구현체가 직접적인 행동에 관련이 없기 때문에 호출하는 부분만 수정하면 된다. 그래서 유지 보수하기에도 용이해진다.
예시
- 목적
사람의 다양한 동작중 어떠한 동작이 요청되도 실행할 수 있도록 만들자
아래 코드는 Main에서 동작을 결정하고, 해당 동작을 인터페이스 타입의 객체가 받도록 한다. 그래서 해당 요청에 맞는 동작을 하도록 도와준다. 인터페이스가 중간에서 요청을 받아 구현되어 있는 함수를 실행시킨다.
(1) 인터페이스(추상체)
- move()라는 동작을 정의함
public interface Move {
public void move();
}
(2) 행동(구상체)
- 각 행동에 대한 결과값을 구현
public class Go implements Move {
@Override
public void move() {
System.out.println("Go");
}
}
public class Stop implements Move{
@Override
public void move() {
System.out.println("Stop");
}
}
(3) Main(실행)
- 최종으로 실행하는 곳에서 어떤 행동을 할지 결정한다. (Go 실행)
public class Main {
public static void main(String[] args) {
//구현코드를 호출하는 곳에서 최종으로 실행하는 곳에서 결정
new Main().run("Go");
}
}
- 실행 요청 메소드
- 인터페이스 타입으로 실행해 어떠한 요청을 하든 다양한 타입으로 실행하도록 한다. (다형성)
private static void run(String moveType) {
Move user = getBehavior(moveType);
//Move2 user = getBehavior(moveType);
user.move();
}
- 실행 메소드
- 각 요청에 따라 어떤 반환 값을 줄지(Go, Stop) 결정하는 곳
private static Move getBehavior(String moveType) {
if (moveType.equals("Go")) {
return new Go();
}
return new Stop();
}
추가 생각
제 생각과 배운것을 토대로 작성했지만, 아직 인터페이스를 많이 적용해보지 않아 이 글이 확실하다고 말을 할 수는 없을 것 같습니다.
앞으로 계속 개선해 나가겠지만 만약 고칠점이 있거나, 추가로 공부했으면 하는 것이 있다면 언제든지 환영합니다.
참조
https://www.youtube.com/watch?v=4IlT6p_H-ss
'Java' 카테고리의 다른 글
[Java] 디미터의 원칙 (0) | 2021.09.21 |
---|---|
[Java] Object(toString(), equals(), hashCode()) (0) | 2021.08.03 |
[Java] StringBuilder vs StringBuffer (0) | 2021.08.03 |
[Java] Solid란? (0) | 2021.07.28 |
[Java] 다형성, 캐스팅, 추상 클래스, 추상 메서드 (0) | 2021.06.22 |