- 들어가기전에
interface는 객체지향의 SOLID 5원칙과도 연관성이 많다고 생각한다! 개인적으로 같이 연관지어서 생각해보면 이해가 조금은 더 잘 되었다.
interface 사용 이유
인터페이스(interface)
자바에서 클래스들이 구현해야 하는 모든 기능을 추상화로 정의만 하고 구현은 하지 않은 자료형을 의미.
interface Car { // 인터페이스 선언
void move(); // 추상화하고 구현은 하지않음.
void start(); // 추상화하고 구현은 하지않음.
}
- 구현한 클래스에서 특정 기능(메소드)을 구현하도록 강제 할 수 있는 기능을 제공한다.
- 다형성이 가능하도록 기능을 제공한다.
- 추상 클래스는 수직적이지만, 수평적 구조를 제공한다.
- 하나의 클래스가 여러 인터페이스를 상속받을 수 있다.
- 인터페이스를 사용하면, 변경에 유연하게 설계할 수 있다.
- 기능에 대한 명세 집합
interface의 특징
- 인터페이스는 interface 키워드를 사용하여 정의
- 인터페이스는 상수와 추상메소드로 구성(자바8부터 default 메소드와 static 메소드 사용 가능 )되어 있다.
- 인터페이스 안의 모든 상수는 public static final 타입 (생략가능)
- 인터페이스 안의 모든 추상메소드는 abstract public 타입 (생략가능)
- 추상클래스와 마찬가지로 인스턴스를 생성할 수 없다.
- 인터페이스는 레퍼런스 변수의 인터페이스 타입으로 사용될 수 있다
- 인터페이스는 다른 인터페이스를 extends 키워드로 상속 받을 수 있으며, 다중 상속(다중 implements)이 가능하다.
- 클래스에서 인터페이스의 구현은 implements 키워드를 사용하여 구현할 인터페이스를 지정후, 추상메소드를 모두 오버라이드 하여 기능을 구현하여야 한다.
상속
정의는 부모 클래스의 모든 멤버를 하위 클래스가 물려 받는 것이다. 단, IS A 관계가 성립해야 한다. 아무런 규약 없이 상속을 하게 되면 객체간에 강한 결합을 가지게 된다.
- 연관된 객체들끼리만 상속을 하는것이 강한 결합을 줄일 수 있다.
추상화
데이터나 프로세스 등을 의미가 비슷한 개념이나 표현으로 정의해 나가는 과정을 의미한다. 각 개별 개체의 구체적인 구현에 대한 상세함은 갖추는 것을 의미한다.
상속과, 인터페이스는 오버라이딩을 이용해서 다형성을 가능하게 한다. 상속을 받은 하위 클래스에서는 부모클래스의 메서드를 오버라이딩시켜서 다형성을 가능하게 한다. 인터페이스는 구현체클래스에서 인터페이스의 메서드들을 재정의하면서 다형성을구현한다.
default 메소드 및 static 메소드
- 자바8부터 인터페이스에 default 메소드와 static 메소드를 추가할 수 있다.
- 인터페이스가 여러곳에서 사용되고 있는 상황에서 새로운 추상메소드를 추가할 경우, 모든 곳에서 오버라이드를 해주어야 하는 번거로움다. 이에 대한 대응책으로 인터페이스에서도 구현부를 가질 수 있는 default 메소드를 활용할 수 있다.
- interface는 다중상속이 가능하기 때문에, default 메소드 이름이 충돌할수 있습니다. 이때는 해당 이름의 메소드를 오버라이드하여 재정의후 사용하시면 된다.
- interface 안에 default 메소드는 아래와 같이 { } 안에 실행문을 사용할 수 있다
인터페이스 장점 및 사용이유
- 인터페이스는 협업을 용이하게 한다. (기능(메소드)의 구현을 강제함으로써, 클래스의 설계 또는 표준화를 유도 할 수 있다)
- 변경, 교체에 용이하다 (인터페이스를 구현한 클래스들을 하나의 인터페이스 타입으로 다룰 수 있다.)
- 다중 상속을 가능하게 한다. (자바에서의 상속은 단일상속밖에 못한다. 하지만 인터페이스를 사용하면 다중 상속이 가능. )
1. 인터페이스는 협업을 용이하게 한다
인터페이스는 구현할 클래스들의 설계 또는 표준화를 유도할 수 있따.
만약 결제 기능을 가진 서비스가 필요하여, 서비스 인터페이스가 있고, 영이라는 개발자가 네이버페이 결제 개발을 맡고, 수라는 개발자가 카카오페이 결제개발을 맡는다고 가정하자.
서비스 인터페이스를 공통으로 두면, 네이버페이 결제 클래스와 카카오페이 결제 클래스 결제 서비스 인터페이스를 상속 받고,
인터페이스의 공통 결제 관련 메소드를 구현해야 하기 때문에, 기능이 조금 다른 두 클래스 이지만 메서드 네이밍이나, 공통 기능을 개발하므로 차후 코드상으로도 공통성(코드 컨벤션 유지 등)을 갖고 있고,
로직을 복사, 붙여넣기해서 재활용하기도 편리하다.
또한, 이 결제 서비스를 사용하게 된다면 두 클래스의 추상화된 인터페이스를 사용하므로,네이버페이를 쓰다가, 카카오페이로 쓰고 싶을 때 변경 교체에도 용이해진다!
interface PayService {
void 결제();
void 환불();
}
class NaverPayService implements PayService {
public void 결제() {
System.out.println("네이버페이 결제 기능");
}
public void 환블() {
System.out.println("네이버페이 환불 기능");
}
}
class KakaoPayService implements PayService {
public void 결제() {
System.out.println("카카오페이 결제 기능");
}
public void 환블() {
System.out.println("카카오페이 환불 기능");
}
}
2. 인터페이스는 변경, 교체에 용이하다 (OCP 원칙!?)
위 결제 서비스 예시에 이어, 클라우드 스토리지 (ex: S3)서비스가 있다고 하자.
클라우드 스토리지 서비스의 기능은 공통적으로 업로드, 다운로드, 삭제가 있다.
현재 AWS를 쓰고있지만, 차후에 구글 클라우드로 변경한다고 한다. 그런데 느낌상(예제니까 느낌상..) 네이버클라우드로도 변경할 수 있을것 같다.
그럼 코드상으로 수정해야 할 부분이 많아진다. 만약 새 클래스를 정의해서 사용한다면,
클래스 이름을 바꿔야 하고 메서드 이름도 바뀔 수가 있다..
인터페이스로 추상화하여 사용한다면 코드상으로 수정할 부분이 적어지게 된다. 즉, 사용하는 곳에서는 인터페이스를 사용하여 구현체를 주입받고, 우리는 구현체만 AWS에서 구글클라우드로, 구글클라우드에서 네이버클라우드로 갈아 끼우면 된다.
또 객체지향 5원칙인 SOLID중 O(Open-closed-principle)을 지킬 수 있게 된다.
- 확장에 대해서는 열려있지만(다른 클라우드 서비스로 확장) 수정에 대해서(코드 수정 등)는 닫혀 있어야 한다.
class PostService {
private final CloudStorageService cloudStroageService;
private final PostRepository postRepository;
public void 게시물_업로드(PostDto postDto) {
postRepository.save(postDto.toPost());
cloudStroageService.업로드(postDto.getFile());
}
public void 게시물_삭제(String fileName) {
postRepository.deleteByFileName(fileName);
cloudStroageService.삭제(fileName);
}
... //다운로드는 생략...
}
interface CloudStorageService { // 클라우드 스토리지 인터페이스
void 업로드(File file);
file 다운로드(String fileName);
void 삭제(String fileName);
}
class AwsStrorageService implements CloudStorageService { // AWS 스토리지 구현체
private AwsCloudClient awsCloudClient; // AWS에서 제공하는 SDK. 편의상 간단하게 지었다.
@Override
public void 업로드(File file) {
System.out.println("AWS 업로드 ");
awsCloudClient.업로드(file);
}
@Override
public file 다운로드(String fileName) {
System.out.println("AWS 다운로드");
return awsCloudClient.다운로드(fileName);
}
@Override
public void 삭제(String fileName) {
System.out.println("AWS 파일 삭제");
awsCloudClient.삭제(fileName);
}
}
class GoogleStrorageService implements CloudStorageService { // 구글 스토리지 구현체.
private GoogleCloudClient googleCloudClient; // AWS에서 제공하는 SDK. 편의상 간단하게 지었다.
@Override
public void 업로드(File file) {
System.out.println("Google 업로드 ");
googleCloudClient.업로드(file);
}
@Override
public file 다운로드(String fileName) {
System.out.println("Google 다운로드");
return googleCloudClient.다운로드(fileName);
}
@Override
public void 삭제(String fileName) {
System.out.println("Google 파일 삭제");
googleCloudClient.삭제(fileName);
}
}
예시를 썼지만 사실 코드는 문제가 많다고 생각한다. 강결합이라던가..(아직 초보개발자입니다..)
하지만 중요한것은 그게 아니다.
인터페이스의 장점을 먼저 생각해보자.
게시물을 업로드, 삭제, 다운로드 하기 위해서 interface (CloudStorageService)를 사용했다는것.
게시물 서비스(PostService)를 클라이언트라고 지칭하겠다.
만약 aws에서 google로 클라우드 플랫폼을 바꾸더라도, 우리는 코드를 수정할 일 없이
CloudStorageService 인터페이스에 주입할 구현체만 google로 갈아 끼우면 되는것이다.
공통부분을 추상화하하고, 기능을 메소드로 뽑아 사용하니 클라이언트 코드(PostService) 를 수정할 일이 없게 된것이다.
이것이 인터페이스와 상속, 다형성의 마법이라고 생각한다..
즉 그리고 SOLID의 OCP를 지키게 된것이다.
- 개방 폐쇄 원칙 : 확장(기능)에 대해 열려있고 수정(코드)에 대해서는 닫혀야 한다
기존의 코드를 변경하지 않으면서 구글 클라우드 기능을 추가해버린것이다.!
3. 다중 상속을 가능하게 한다. (Multiple inheritance)
일반적으로 자바는, 일반 클래스나 추상 클래스를 단 1번밖에 상속받지 못한다.(extends)
하지만 interface는 그 수가 몇개이던 상속받을 수 있다.(implements)
- 그렇다고 너무 많이 상속받아버리면.. 문제가 생길것 같은데.. 아직은 다 경험해보지 못했다..
실제로 java.util.ArrayList<E> 클래스를 보면 AbstractList를 extends 하고 추가로 다양한 인터페이스를 implements 한다.
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
...
}
필요한 인터페이스를 상속받아서 구현하면 여러 기능들을 구조적으로 포함할 수 있고,
잘 설계한다면 다양한곳에서 잘 이용할 수 있다. (인터페이스 타입으로 받아버리면 되니까.)
다음은, 같이 클린아키텍처를 스터디하는 내 오랜친구이자 개발자 선배랑 이야기 했던 내용과 많이 비슷한 내용을 닮은 이야기를
블로그에서 다루고 있어서 참조 해왔다..
만약 이 글을 보시는 분이 있다면 차후 업무에 코드 품질에 문제가 생겼을 때 헤쳐나갈때도 도움이 될것 같고,,
만약 설계 단계 이시라면 코드 품질에 도움이 될것같다.
< 상속 보다는 인터페이스 구현을 사용하자>
자주 읽어보고 이해해볼 것이다. 또 실제 구현도 해볼것이지만, 경험도 해보고 싶다.
상속보다는 인터페이스 구현을 사용하자
정리하자면
인터페이스를 사용했을때의 장점은
- 인터페이스는 협업을 용이하게 한다. (기능(메소드)의 구현을 강제함으로써, 클래스의 설계 또는 표준화를 유도 할 수 있다)
- 변경, 교체에 용이하다 (인터페이스를 구현한 클래스들을 하나의 인터페이스 타입으로 다룰 수 있다.)
- 다중 상속을 가능하게 한다. (자바에서의 상속은 단일상속밖에 못한다. 하지만 인터페이스를 사용하면 다중 상속이 가능. )
반대로 단점은.
인터페이스가 없으면 코드 수정이 많아질것이며, 같은 기능을 묶는 추상화와, 객체지향의 다형성을 구현하기 힘들것이며
협업도 더 어려워 질 수 있다고 생각한다.
또한 개발의 복잡성을 높힐 수도 있다고 생각한다.
참조
'Java' 카테고리의 다른 글
에러와 예외 (Error, Exception) (1) | 2022.11.26 |
---|---|
Java - double brace (생성과 동시에 초기화, 함수호출) (0) | 2022.11.20 |
Java List to String array. 리스트를 스트링으로 (0) | 2022.11.14 |
Java 동시성 제어 - 멀티스레드, Syncronized, volatile, Atomic (0) | 2022.09.04 |