추상클래스와 인터페이스와 DI, IoC

HYEON·2023년 10월 18일
0

추상클래스와 인터페이스와 DI, IoC

추상클래스

추상클래스란?

  • 미완성 설계도에 비유할 수 있다.
  • 키워드 ‘abstract’ 를 클래스에 붙이면 된다.
  • 상속해서 사용한다. (단일 상속 가능)

추상메서드

  • 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 것이 추상메서드이다.

핵심

  • 추상 메서드는 멤버 변수를 가질 수 있고, 또한 추상 메서드가 아닌 메서드도 가질 수 있다.
  • 이를 상속해 사용하면 추상 메서드는 상속 받은 자식 클래스에서 반드시 구현을 해주어야 하고, 추상 메서드가 아닌 조상의 변수와 메서드는 일반 클래스를 상속 받았을 때 처럼 사용할 수 있다.

인터페이스

인터페이스란?

  • 일종의 추상클래스이다.
  • 추상클래스보다 추상화의 정도가 높다.
    • 그렇기 때문에 추상 클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버 변수를 구성원으로 가질 수 없다.
  • 해당 특성으로 인해 기본 설계도에 비유하기도 한다.
  • 작성 시 디폴트 메서드와 static 메서드를 제외한 모든 메서드가 public abstract이다.
  • 모든 멤버 변수는 public static final이다.
  • 사용시 다중 상속이 가능하다.
  • 인터페이스 구현 시 메서드를 일부만 구현한다면, abstract를 붙여서 추상클래스로 선언해야 한다.
    • 즉, 인터페이스는 추상클래스보다 더 추상화 됐다.

인터페이스를 이용한 다형성

  • 리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.
interface Parseable {
	public abstract void parse(String fileName);
}

public ParserManager {
	public static Parseable getParser(String type) {
		if(type.equals("XML") {
			return new XMLParser();
		} else {
			return new HTMLParser():
		}
}

Parseable parser = ParserManager.getParser("XML");

// 객체 지향적이지 않은 코드
Parseable parser = new XMLParser(); -> 이렇게 하면 HTMLParser로 변경 시 클라이언트 코드가 바뀐다
  • 위의 코드는 ParserManager가 Parseable 객체의 구현체를 대신 주입해주고 있다.
  • 이렇게 되면 클라이언트는 Parseable의 실제 구현체가 변경되더라도 코드 변경이 없거나 적다.
  • 메서드를 사용(호출)하는 쪽(클라이언트)에서는 사용하려는 메서드의 선언부만 알면 된다. → 캡슐화

핵심

  • 클래스 A(클라이언트)는 클래스 B(Provider)의 인스턴스를 생성하고 메서드를 호출한다.
  • 이 두 클래스는 직접적인 관계를 가지고 있다.
  • 이 경우 A 클래스를 작성 시에 B 클래스가 이미 작성되어 있어야 한다. 그리고 B 클래스 변경 시 A클래스 또한 변경 되어야 한다.

  • 인터페이스를 통해서 B에 접근하게 되면, A 클래스는 B 클래스의 메서드를 여전히 호출하지만, 인터페이스를 통해 간접적으로 접근하기 때문에 실제 사용하는 클래스의 이름을 몰라도 되고 심지어는 실제로 구현된 클래스가 존재하지 않아도 문제가 되지 않는다.
  • 이를 이용해 JDBC 또한 추상화 되어 있어서 실제 구현 클래스 MySQL, MSSQL 같이 여러 종류의 실제 구현 클래스들을 몰라도 어떤 데이터베이스를 사용할 지 결정한다면, 동적으로 결정된다.

아래는 예제 코드이다.

public interface I {
    public abstract void play();
}

public class A {
    void play(I i) {
        i.play();
    }
}

public class B implements I{
    @Override
    public void play() {
        System.out.println("B play");
    }
}

public static void main(String[] args) {
        A a = new A();
        a.autoPlay(new B());
    }

위의 코드를 유심히 보자. 뭔가 이상하지 않은가?

A class는 B의 존재에 대해서 몰라도 되지만, 실제 A를 사용하는 main 메서드에서는 a.autoPlay(new B()); 에서 new B() 와 같이 실제 구현체를 알아야 한다.

그럼 해당 구현체가 아닌 다른 구현체를 쓰고 싶다면? 결국 메인 메서드에서 실제 구현체에 대한 코드를 바꿔야 한다. 이를 해결할 좋은 방법이 없을까?

다시 이전 Parser 예제에 정답이 있다.

public ParserManager {
	public static Parseable getParser(String type) {
		if(type.equals("XML") {
			return new XMLParser();
		} else {
			return new HTMLParser():
		}
}

바로 ParserManager 같은 외부에서 실제 구현체를 주입을 도와주는 객체가 있으면 된다.

그래서 Spring 에서는 이를 도와주는 스프링 컨테이너가 있다.

IoC와 DI

  • 위의 실제 구현체 주입을 도와주는 스프링 컨테이너에서는 해당 구현체들을 Bean으로 등록해 사용한다.
public interface I {
    public abstract void play();
}

@Component
public class A {

		@Autowired // 생성자로 의존 관계를 주입한다.
    public A(I i) {
        this.i = i;
    }

    void autoPlay(I i) {
        i.play();
    }
}

@Component
public class B implements I{
    @Override
    public void play() {
        System.out.println("B play");
    }
}

public static void main(String[] args) {
		ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
		A a = context.getBean(A.class);
		a.autoPlay();
}
  • 위의 메인 코드는 스프링 컨테이너를 사용하여 의존성 주입(Dependency Injection, DI)을 수행하는 예제이다. AnnotationConfigApplicationContext는 스프링 컨테이너를 초기화하고, AppConfig 클래스를 통해 빈(Bean) 설정을 로드한다.
  • 그런 다음 context.getBean(A.class); 코드로 스프링 컨테이너에서 A 타입의 빈을 가져온다. 이렇게 가져온 빈은 이미 필요한 의존성이 주입된 상태이다.
  • 위의 코드 같이 실제 구현체를 몰라도 스프링 컨테이너에서 주입해주기 때문에 코드가 작동한다.
  • 따라서 실제 I의 구현체가 바뀌어도 main 메서드에서는 코드를 변경할 필요가 없다.

DI(Dependency Injection, 의존 관계 주입, 예제에선 I 클래스의 실제 구현체를 외부에서 주입하는 것)을 스프링 컨테이너가 해주는 것이고 이는 IoC(Inverse of Controll, 제어의 역전, 개발자가 작성한 객체나 메서드의 제어를 개발자가 아니라 외부에 위임(스프링 컨테이너)하는 설계 원칙)의 한 형태이다.

알게된 점

  • 인터페이스
  • 추상클래스
  • DI, IoC

개인적으로 인터페이스 부분은 정말 매번 볼 때마다 그 뜻이 새로운 것 같다.
‘처음 봤을 땐 이걸 어디다가 사용할까?’라고 생각했다. 시간이 지나고 많은 학습을 하고, 프로젝트를 하면서 직접 코드를 수정하고, 분해하는 유지보수의 과정을 거치면서 객체지향적인 코드의 필요성을 알게 되고, 다시끔 자바의 정석을 펴 이 부분을 보니 정말 새로웠다.

또한, 그저 그렇구나 하고 넘겼던 부분들을 이제와 곱씹어 보니 모두가 연결 되어있었구나. 라고 생각이 들게 된다.
예전 콜라와 펩시콜라, 코카콜라 같은 코드 예제를 보면서 DI와 IoC를 이해하던 시간이 있었는데, 이렇게 추상클래스부터 맥락을 따라 내려오면서 공부하다 보니 좀 더 이해가 쉬운거 같다.

맥락을 짚으면서 깊게 내려가는 공부를 하자 !

profile
레벨업하는 개발자

0개의 댓글