개발자로 후회없는 삶 살기

[문법] 스프링 코드 분석을 위해서 반드시 알아야 하는 인터페이스 본문

[백엔드]/[Java | 학습기록]

[문법] 스프링 코드 분석을 위해서 반드시 알아야 하는 인터페이스

몽이장쥰 2024. 5. 19. 17:59

서론

※ 과거에 기록한 내용에서 중요한 부분만 발췌하여 모두가 이해하기 쉽게 다시 서술한다.

 

본론

- 추상 클래스란?

public abstract class Car {

    public abstract void drive();

    public abstract void stop();
}

추상 메서드를 가지는 클래스로, 추상 메서드를 가지는 것 외에는 일반 클래스와 다른 게 없다. 추상 메서드란 함수 원형만 있고 코드 블럭 내부는 비어 있는 메서드를 의미한다. 이제부터 추상 클래스와 추상 메서드를 왜 사용하고 어떠한 특징을 가지고 있는지 알아보자

 

-> 추상 클래스가 되는 조건 ✅

1. 부모 클래스로서 추상 메서드를 가짐
2. 자식 클래스인데 부모인 추상 클래스의 추상 메서드를 구현하지 않고 추상 메서드로 유지함

추상 클래스가 되려면 추상 메서드를 소유하면 된다.

 

-> 사용 목적

추상 클래스는 하위 구현 클래스가 상속 받아서 재정의 후 사용하라는 목적으로 사용된다. 상속의 목적에 따라 부모 클래스로서 일반화하기 위해 존재한다. 추상 클래스가 되는 조건✅ 에 따라 추상 클래스는 반드시 추상 메서드를 가지며, 하위 구현 클래스는 추상 클래스가 되지 않는 한 반드시 추상 메서드를 재정의해야 한다.

 

추상 메서드는 언젠간 사용될 수도 안 될 수도 있지만, 현재는 구체적으로 어떻게 구현될지 몰라서 자식에게 구현을 미루는 의미를 가진다. 일반화를 위해, 추상 클래스에는 자식 클래스가 사용할 필드와 메서드가 있으며 현재 어떻게 구현될지 아는 메서드는 구현하여 자식에서 사용할 수 있다.

 

public abstract class Animal {
    public void eat() {
        System.out.println("밥을 먹는다.");
    }
    
    public abstract void run();
}

예를들어서 동물이 밥을 먹고 물을 마시는 건 현재도 구현할 수 있는 부분이라서 추상 클래스에서도 구현할 수 있다. 하지만 달리는 것은 동물마다 다를 수 있다.

 

public class Rabbit extends Animal {
    @Override
    public void run() {
        System.out.println("깡총깡총 달린다.");
    }
}

public class Horse extends Animal {
    @Override
    public void run() {
        System.out.println("날쎄게 달린다.");
    }
}

즉, 하위 구현체에 따라 달라지므로, 구현체에게 구현을 미뤄야만 한다. 이처럼 상속의 활용과 비슷하게 자식에서 사용될 공통 멤버를 가지고 구현체의 이용을 편리하게 하는 것은 동일하나, 구현체에게 구현을 미루는 추상 메서드의 여부로 추상 클래스가 결정된다.

 

-> 특징

1. 생성할 수 없음

추상 클래스는 추상 메서드를 가지고 있고 추상 메서드는 생성 시 메모리 할당을 받을 수 없기에 추상 클래스는 생성이 불가능하다.

 

2. 다중 상속의 불가

만약 여러 개의 부모 중, 다른 클래스가 동일한 이름의 추상 메서드를 가진다면 자식 클래스가 어떠한 메서드를 재정의 해야 하는지 알 수 없기에 다중 상속이 불가능하다. 이는 추상 클래스만의 특징은 아니며, 클래스의 상속에는 항상 적용된다.

 

3. 상수와 추상 메서드 외에도 멤버를 가짐

현재는 인터페이스를 수정하면 구현 클래스를 전부 수정해야 하는 의존성을 줄이기 위해 다양한 편의 멤버가 생겼지만, 추상 클래스는 인터페이스와 다르게 상수와 추상 메서드 외에도 모든 멤버를 가질 수 있다. 일반 클래스가 가질 수 있는 멤버에 추상 메서드까지 가질 수 있다고 생각하면 된다.

 

- 추상 클래스 활용 탬플릿 메서드

탬플릿 메서드란 실행할 메서드의 순서를 정해두고 이를 구현하는 클래스에서 추상 메서드를 커스터 마이징 하여 일련의 시나리오 대로 동작하게 탬플릿을 작성하는 것이다.

 

public final void run() {
    startCar();
    drive();
    stop();
    turnOff();
}

차가 달리는 시나리오를 시동 켜기 > 운전하기 > 정차하기 > 시동 끄기로 정해두고 각각의 메서드를 구현 클래스에서 재정의하면 run()으로 상위 클래스에서 원하는 순서대로 동작시킬 수 있다.

 

-> 추상 클래스

public abstract class Car {

    public abstract void drive();

    public abstract void stop();

    public void startCar() {
        System.out.println("시동을 켠다");
    }

    public void turnOff() {
        System.out.println("시동을 끈다.");
    }

    public final void run() {
        startCar();
        drive();
        stop();
        turnOff();
    }
}

추상 클래스의 목적에 맞게 현재 구현할 수 있는 메서드는 구현하고, 하위 클래스마다 다르게 동작하는 메서드의 구현은 하위 클래스에게 미룬다.

 

public class CarImpl extends Car {
    @Override
    public void drive() {
        System.out.println("사람이 운전한다.");
    }

    @Override
    public void stop() {
        System.out.println("사람이 브레이크를 밟는다.");
    }
}

하위 클래스에선 하위 클래스의 목적에 맞게 반드시 추상 메서드를 구현해야 한다.

 

구현 클래스를 완성하고 시나리오를 실행하면 작성한 순서대로 동작한다. 만약 템플릿에 원하는 동작을 추가하고 싶다면, 추상 메서드를 만들고 run()에 삽입하기만 하면 되므로 매우 편리하다.

 

✅ 규칙? 프레임워크처럼?

프레임워크는 공통으로 사용될 수 있는 특정한 기능들을 모듈화한 것으로, 폴더명, 파일명 등에 대한 규칙이 있다. 프레임워크를 사용하려면 개발자가 반드시 구현해야 하는 메서드들이 있는데, 이때 탬플릿 메서드를 상위 클래스에 정의하고, 하위 클래스에서 상속 받아 구현한 후 일련의 규칙대로 동작하게 한다.

 

- 인터페이스란?

상수와 추상 메서드만을 멤버로 가지는 구조이다. 현재는 다양한 요소들이 추가되었지만, 초기에는 2가지 요소만으로 구성되었다. 고정된 데이터와 빈 메서드만을 가지고 할 수 있는 것은 무엇이며, 어떻게 동작하는 지 알아보자

 

- 인터페이스의 용도

인터페이스는 사용 방법을 정의해 놓은 설명서와 같다. 인터페이스를 읽는 대상은 2종류로 인터페이스를 구현할 서버 코드와 구현된 인터페이스를 사용할 클라이언트 코드이다. 서버 측은 인터페이스에 적혀 있는 사용방법과 메서드 이름을 토대로 추상 메서드를 구현한다. 클라이언트 측은 인터페이스에 적혀있는 사용 방법대로 목적에 맞게 구현 메서드를 사용한다.

 

-> 예시

Statement createStatement() throws SQLException;

PreparedStatement prepareStatement(String sql)
        throws SQLException;

DB에 연결하기 위해선 Connection 인터페이스가 필요하다. Connection 인터페이스를 보면 전부 추상 메서드이고 내부 코드가 하나도 없다.

 

✅ 여기서 서버와 클라이언트는 누구일까?

클라이언트 : Connection 메서드를 사용하는 개발자
서버 : Connection 메서드를 구현하는 Mysql과 Oracle

개발자는 DB를 사용할 때 인터페이스에 적혀있는 사용 방법대로 실행만 할 뿐 내부 코드를 작성하지 않는다. 서버 측에서 인터페이스를 전부 구현해서 jar 파일로 배포하기에 개발자는 jar를 가져다가 Connection 인터페이스 메서드를 사용하기만 하면 DB에 연결하는 인터페이스 메서드를 사용할 수 있다. 서버측에게 JDBC 인터페이스의 역할은 "자바가 DB를 연결하려면 이러이러한 메서드들을 구현해야 한다"를 알려주는 설명서이다.

 

🚨 스프링 개발자가 되려면 인터페이스를 잘 알아야 한다.

신입 개발자가 인터페이스를 작성하는 일은 거의 없다. 인터페이스는 개발 처음 단계에 프로그램의 규격을 잡기 위해 작성하며, 인터페이스를 작성하기 위해선 해당 프로그램의 전반적인 것을 알아야 한다. 따라서 신입 개발자보다는 회사 스타일을 잘 아는 시니어 개발자가 한다.

하지만, 위에서 알아봤듯이 프레임워크를 사용하기 위해선 정해진 규칙을 따라야 하며, 이는 대부분 인터페이스와 추상 메서드로 규칙을 제공한다. 스프링 프레임워크는 엄청나게 다양한 인터페이스를 제공하며 개발자는 추상 메서드를 재정의 하여 스프링이 의도한대로 규칙을 지켜야 한다.

해당 추상 메서드가 어떠한 역할을 하는지 인터페이스에 정의된 사용 방법을 파악하고 커스터 마이즈하면 스프링을 굉장히 실용적으로 사용할 수 있다. 왜냐하면, 스프링은 개발자가 필요한 거의 모든 기능을 이미 인터페이스로 만들어 두고 컨테이너에 등록하여 사용하기 때문이다.

 

- 인터페이스에 추가된 기능

-> 인터페이스의 기본 요소

상수 : public static final을 기본으로 하며, 생략시 컴파일러가 만들어줌
추상 메서드 : public abstract를 기본으로 하며, 생략시 컴파일러가 만들어줌

이제 왜 이 두가지 요소만으로 인터페이스를 사용할 수 있는지 알아봤다. 구현체가 아닌 사용 설명서이기 때문이다. 하지만, 자바 8 이전에는 인터페이스에 구현 능력이 없어서 인터페이스를 바꾸면 이를 구현하는 모든 클래스도 고쳐야 하는 문제점이 있었다. 자바 8 이상부터는 인터페이스 안에 추상 메서드 정의뿐만 아니라 구현을 가능하도록 하여 의존성을 해소하고자 했다.

 

1. default 메서드

같은 패키지라면 접근 가능한 메서드로 인터페이스에서 내부를 구현하여 구현체에서 바로 사용하는 것을 목적으로 만들었다.

 

2. static 메서드

기본이 public인 static 메서드는 구현 메서드로 구현체를 생성하지 않고 바로 사용하는 것을 목적으로 만들었다. 구현체에서 재정의 할 수 없다.

 

3. private 메서드

인터페이스 내부에서 사용할 목적인데, 위처럼 public이나 static이면 외부에서 접근할 수 있었고, 인터페이스를 구현하는 클래스가 특정 method에 접근하는 것 자체를 막고 싶을 수도 있기에 인터페이스 내부에서만 사용할 목적으로 private 구현 메서드를 제공한다.

 

private : 구현 메서드 가능, 구체 클래스에서 접근 불가능
public : 일반 public 메서드는 구현 메서드 불가능하고 abstract 메서드로 사용해야 함
static : 구현 메서드 가능하고 기본이 public, 구현 클래스 재정의 불가
default : 구현 메서드 가능하고 같은 패키지, 같은 클래스에서 사용 가능, 재정의 가능

정리하면, 위와 같다.

Comments