API란 응용 프로그램에서 사용할 수 있도록 운영체제나 프로그래밍 언어에서 제공하는 기능을 제어할 수 있게 하는 인터페이스이다
인터페이스란 사람과사람 , 사람과 사물 또는 기기를 연결하는 것
애플리케이션 간에 지정된 형식으로 요청과 응답을 할 수 있도록 연결하는 것
GUI는 Grapchic User Interface의 약자로 컴퓨터를 사용할 때 입출력 등을 좀 더 효율적이고 쉽게 조작할 수 있도록 아이콘 등으로 시각화한 사용자 인터페이스이다. 예> sourcetree
인터페이스의 기본 구조
인터페이스를 작성하는 것은 기본적으로 클래스를 작성하는 것과 유사하지만, class 키워드 대신 interface키워드를 사용한다는 점에서 차이가 있다.
또한 일반 클래스와 다르게 내부의 모든 필드가 public static final로 정의되고, static과 default 메서드 이외의 모든 메서드가 public abstract로 정의된다는 차이가 존재한다.
인터페이스 안에서 상수를 정의하는 경우에는 반드시 public static final로 , 메서드를 정의하는 경우에는 public abstract로 정의되어야 하지만 일부분 또는 전부 생략 가능 (여기서 생략된 부분은 컴파일러가 자동으로 추가해줌)
인터페이스의 구현
추상 클래스와 마찬가지로 인터페이스도 그 자체로 인스턴스를 생성할 수 없고, 메서드 바디를 정의하는 클래스를 따로 작성해야 한다.
extends키워드를 사용하는 클래스의 상속과 기본적으로 동일하지만, 구현하다 라는 의미를 가진 implements키워드를 사용한다는 점에서 차이가 있다.
class 클래스명 implements 인터페이스명 {
... // 인터페이스에 정의된 모든 추상메서드 구현
}
특정 인터페이스를 구현한 클래스는 해당 인터페이스에 정의된 모든 추상 메서드를 구현해야 한다 즉, 어떤 클래스가 특정 인터페이스를 구현한다는 것은 그 클래스에게 인터페이스의 추상 메서드를 반드시 구현하도록 강제하는 것을 의미한다. 다른 말로, 어떤 클래스가 어떤 인터페이스를 구현한다는 것은 그 인터페이스가 가진 모든 추상 메서드들을 해당 클래스 내에서 오버 라이딩하여 바디를 완성한다라는 의미를 가짐
인터페이스의 다중 구현
클래스 간에 상속에서 다중 상속은 안된다. 즉 하위 클래스는 단 하나의 상위 클래스만 상속받을 수 있다. But 인터페이스는 다중적 구현이 가능하다.(하나의 클래스에서 여러 개의 인터페이스를 구현할 수 있다. 다만 인터페이스는 인터페이스로부터만 상속이 가능하고, 클래스와 달리 Object클래스와 같은 최고 조상이 존재하지 않는다.
class ExampleClass implements ExampleInterface1, ExampleInterface2, ExampleInterface3 {
... 생략 ...
}
클래스에서 다중 상속이 불가능했었던 핵심적인 이유는 만약 부모 클래스에 동일한 이름의 필드 또는 메서드가 존재하는 경우 충돌이 발생하기 때문. 반면 인터페이스는 애초에 미완성된 멤버를 가지고 있기 때문에 충돌이 발생할 여지가 없고, 따라서 안전하게 다중 구현이 가능하다
마지막으로 특정 클래스는 다른 클래스로부터 상속을 받으면서 동시에 인터페이스를 구현할 수 있다.
abstract class Animal { // 추상 클래스
public abstract void cry();
}
interface Pet { // 인터페이스
public abstract void play();
}
class Dog extends Animal implements Pet { // Animal 클래스 상속 & Pet 인터페이스 구현
public void cry(){
System.out.println("멍멍!");
}
public void play(){
System.out.println("원반 던지기");
}
}
class Cat extends Animal implements Pet { // Animal 클래스 상속 & Pet 인터페이스 구현
public void cry(){
System.out.println("야옹~!");
}
public void play(){
System.out.println("쥐 잡기");
}
}
public class MultiInheritance {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
dog.cry();
dog.play();
cat.cry();
cat.play();
}
}
// 출력값
멍멍!
원반 던지기
야옹~!
쥐 잡기
인터페이스의 장점
의존하다 라는 의미는 User 클래스에서 provider에 정의된 특정 속성 또는 기능을 가져와 사용하고 있다는 의미이다.
public class InterfaceExample {
public static void main(String[] args) {
User user = new User(); // User 클래스 객체 생성
user.callProvider(new Provider()); // Provider 객체 생성 후에 매개변수로 전달
}
}
class User { // User 클래스
public void callProvider(Provider provider) { // Provider 객체를 매개변수로 받는 callProvider 메서드
provider.call();
}
}
class Provider { //Provider 클래스
public void call() {
System.out.println("무야호~");
}
}
// 출력값
무야호~
User클래스에 정의된 callProvider 메서드의 매개변수로 Provider 타입이 전달되어 호출되고 있는 것을 확인할 수 있다.
만약 이 코드에서 User클래스가 의존하고 있는 Provider클래스에 변경사항이 발생해서 Provider클래스가 아닌 Provider클래스로 교체해야 하는 상황이 발생하면
public class InterfaceExample {
public static void main(String[] args) {
User user = new User(); // User 클래스 객체 생성
user.callProvider(new Provider2()); // Provider객체 생성 후에 매개변수로 전달
}
}
class User { // User 클래스
public void callProvider(Provider2 provider) { // Provider 객체를 매개변수로 받는 callProvider 메서드
provider.call();
}
}
class Provider2 { //Provider 클래스
public void call() {
System.out.println("야호~");
}
}
// 출력값
야호~
변경된 내용을 중점으로 위의 코드를 보면, 원래 Provider 클래스에 의존했던 User 클래스의 의존관계를 Provider2 클래스로 변경하기 위해 Provider2 객체를 새롭게 생성해주고, User클래스의 callProvider 메서드가 동일한 타입의 매개변수를 받을 수 있도록 매개변수의 타입을 Provider2로 변경해주었다 > 요약하자면 Provider클래스에 의존하고 있는 User클래스의 코드의 변경이 불가피하다.
변경할 코드가 몇백 몇천 줄이 된다면 다 고쳐야 하므로 안 좋은 코드가 된다
인터페이스의 가장 큰 장점 중에 하나는 앞서 봤던 일반적인 인터페이스의 기능처럼 역할과 구현을 분리시켜 사용자 입장에서는 복잡한 구현의 내용 또는 변경과 상관없이 해당 기능을 사용할 수 있다는 점이다.
따라서 앞선 예제에서 봤었던 것과 같이 하나하나 코드를 일일이 변경해주어야 할 필요도 없어진다
인터페이스를 적용한 모습을 도식화해보면 위의 그림처럼 나타낼 수 있다.
기존의 Provider클래스에 인터페이스라는 껍데기를 씌운 형태이다
User클래스는 더 이상 Provider의 교체 또는 내용의 변경에 상관없이 인터페이스와의 상호작용을 통해 의도한 목적을 달성할 수 있다.
interface Cover { // 인터페이스 정의
public abstract void call();
}
public class Interface4 {
public static void main(String[] args) {
User user = new User();
// Provider provider = new Provider();
// user.callProvider(new Provider());
user.callProvider(new Provider2());
}
}
class User {
public void callProvider(Cover cover) { // 매개변수의 다형성 활용
cover.call();
}
}
class Provider implements Cover {
public void call() {
System.out.println("무야호~");
}
}
class Provider2 implements Cover {
public void call() {
System.out.println("야호~");
}
}
//출력값
야호~
Cover라는 인터페이스를 정의한 후에 각각의 구현체 implements 키워드를 사용해서 각각의 기능을 구현하고 있다. 그리고 User클래스에서는 매개변수의 다형성을 활용하여 구체적인 구현체가 아닌 인터페이스를 매개변수로 받도록 정의했다. 이에 따라 Provider 클래스의 내용 변경 또는 교체가 발생하더라도 User 클래스는 더 이상 코드를 변경해주지 않아도 같은 결과를 출력해낼 수 있다.
인터페이스는 기능이 가지는 역할과 구현을 분리시켜 사용자로 복잡한 기능의 구현이나 교체/변경을 신경 쓰지 않고도 코드 변경의 번거로움을 최소화하고 손쉽게 해당 기능을 사용할 수 있도록 한다.
반대로 기능을 구현하는 개발자의 입장에서도 선언과 구현을 분리시켜 개발 시간을 단축할 수 있고, 독립적인 프로그래밍을 통해 한 클래스의 변경이 다른 클래스에 미치는 영향을 최소화할 수 있다는 장점이 있다.
'TIL' 카테고리의 다른 글
[Spring MVC][Transaction] (0) | 2022.11.04 |
---|---|
[Spring MVC][DTO] (0) | 2022.11.03 |
[Spring][JPA][EntityMapping] (0) | 2022.11.01 |
[Spring][JPA][Persistence Context] (0) | 2022.11.01 |
[SpringMVC][Rest Client] (0) | 2022.10.31 |