[Java] 객체 지향 원리 적용

박채은·2022년 11월 13일
0

Java

목록 보기
21/30

객체 지향 설계란 변화와 확장에 유연한 설계라고 할 수 있다.

객체 지향적으로 잘 설계한 코드는 변화와 확장을 하더라도, 기존의 코드는 최대한 건들이지 않게 된다는 것!

이런 객체 지향 프로그래밍을 하기 위해서는 크게 2가지가 필요하다.

1. 구현에 의존하지 않고, 역할에 의존하기

A 클래스가 B 클래스에 의존한다.
= A 클래스에서 B 클래스에 정의된 속성이나 기능을 가져와 사용하고 있다.

이때 B 클래스에 변경 사항이 발생하거나, B 클래스가 아닌 C 클래스로 교체해야 하는 상황이 발생한다면?
=> B 클래스에 의존하고 있던 A 클래스의 코드 변경이 불가피하다.

코드가 점점 더 길어지고 복잡해지는데, 구현 클래스에 계속 의존하다 보면 코드를 수정하거나 유지/보수하는데 많은 어려움이 있다.

따라서 구현(클래스)에 의존하지 않고, 역할(인터페이스)에 의존해야 한다.

이때의 구현 클래스는 B, C 클래스(구체적인 클래스)를 의미하고, 역할 클래스는 B, C의 역할을 담고 있는 인터페이스를 말한다.

인터페이스 = 역할과 구현의 분리
인터페이스는 객체들이 반드시 해야할 역할을 규정해준다.

그러므로 인터페이스를 통해, 역할과 구현을 분리시켜야 한다.


[ 예제 코드 ]

public class Test{
    public static void main(String[] args) {
        User user = new User(); // User 클래스 객체 생성
        // user.callProvider(new Provider());
        user.callProvider(new Provider2());
    }
}

class User { // User 클래스
//    public void callProvider(Provider provider) {
//        provider.call(); // Provider 클래스에 의존한다.
//    }
    public void callProvider(Provider2 provider) {
        provider.call(); // Provider2 클래스에 의존한다.
    }
}

class Provider { // Provider 클래스
    public void call() {
        System.out.println("무야호~");
    }
}
class Provider2 { // Provider2 클래스
    public void call() {
        System.out.println("야호~");
    }
}
  • User가 Provider 클래스에 의존하다가 Provider2 클래스에 의존하게 된다면, Provider2 클래스를 새로 생성하고 callProvider 메소드의 매개변수를 Provider2로 바꿔야 할 것이다.
interface Cover { // 인터페이스 정의
    public abstract void call();
}

public class Test {
    public static void main(String[] args) {
        User user = new User();
        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("야호~");
    }
}
  • interface를 생성한 후, User 클래스가 인터페이스에 의존하게 만든다.
  • 이로써 역할에 의존하게 되고, 이 역할을 구현하는 어떤 구현 클래스가 들어와도 코드를 수정할 필요가 없다.(다형성)

[ 추가적인 예시 -1 ]
어떤 식당에서 할인 이벤트를 진행하고 있다. 이때 이벤트는 상시 또는 불특정하게 진행되며 새로운 이벤트가 생성되기도 하며 없어지기도 한다.
현재 존재하는 이벤트는 두 가지로 다음과 같다.

  • 중학생에게 20% 할인(A)
  • 고등학생에게 500원 할인(B)

만약 A, B 객체에 직접 의존하면, 나중에 이벤트가 사라지거나 추가될 때 많은 부분을 수정해야 한다.

즉 구현에 의존하지 않고, 역할에 의존해야 한다.
역할에 의존하기 위해, interface(역할)을 생성하고 이벤트들이 interface를 구현하도록 한다.
모든 이벤트들은 interface에서 강제한 기능을 구현하고 있고, interface 타입으로 이벤트를 받아 이를 실행할 수 있어 코드의 수정이 최소화된다.

[ 추가적인 예시 -2 ]
로미오와 줄리엣 연극을 한다고 생각해보자.
나는 로미오 역할을 맡았고, 로미오와 줄리엣 역할마다 5명의 연기자가 있다고 생각해보자.

나는 공연동안 5명의 줄리엣을 만나게 될 것이다.
줄리엣 연기자는 "김채은", "빅채은"..등이 있다.
(역할 = 줄리엣/ 구현 = 줄리엣 역할을 연기하는 5명의 연기자분)

내가 연습을 할 때, "김채은" 분에게 맞춰 모든 액션과 애드리브를 만들어뒀다면(= "김채은" 분께 의존했다)
나중에 실전에서 "박채은" 배우와 만났을 때는 새롭게 설정해야할 것도 많아진다.
따라서 나는 어떤 줄리엣 배우를 만나게 될 지 모르니, 배우에 의존할 것이 아니라, 줄리엣이라는 역할에 의존해서 연습을 해야할 것이다.


2. 의존성 주입(DI)

  • Spring의 핵심 개념
  • 의존성 주입은 추상화와 다형성을 기반으로 만들어진 기술이다.
  • 외부로부터 주입을 받아온다.

인터페이스를 생성한 다음, 인터페이스 타입으로 객체를 선언한다.

생성자 주입

  • 의존성 주입의 종류 중 하나
  • 생성자를 통해서, 외부로부터 주입을 받아온다.

0개의 댓글