Java - 인터페이스와 DI

지우·2023년 1월 26일
0

CNU SW 아카데미 2기

목록 보기
7/10

인터페이스의 기능

1. 구현을 강제하는 기능

인터페이스 : 모든 메소드가 추상 메소드로 구성된 클래스

따라 구현을 강제한다. 메소드를 구현하지 않았을 때 오류가 발생하고, (인텔리제이 기준) Alt + Enter 키로 빠른 수정을 하면 구현하지 않은 메소드가 자동으로 구현하게끔 생겨난다.

2. 다형성 제공

interface MyRunnable {
    void myRun();
}

interface YourRunnable {
    void yourRun();
}

public class Test implements MyRunnable, YourRunnable {
    public static void main(String[] args) {
        Test t = new Test();
        t.myRun();
        t.yourRun();
        
        MyRunnable mr = new Test();
        mr.myRun();
        
        YourRunnable yr = new Test();
        yr.yourRun();
    }

    @Override
    public void myRun() {
        System.out.println("My Run");
    }

    @Override
    public void yourRun() {
        System.out.println("Your Run");
    }
}

위 코드처럼 Test 객체를 생성했을 땐 두 인터페이스의 메소드를 호출 가능하지만, 특정 인터페이스의 객체를 생성했을 땐 인터페이스에 정의된 메소드만 호출 가능하다.

따라 특정 메소드를 호출하고 싶을 때, 각 클래스 객체를 여러 개 생성하지 않고 인터페이스 객체를 생성하여 프로그램을 실행할 때 결정지어 호출할 수 있도록 돕는 역할을 한다. (어떤 것이든 받아들일 수 있음)

3. 결합도를 낮춤 (의존성 역전)

public class UserService implements Login {

    private Login login;
		//private Login login = new KakaoLogin();
		//위 코드라면 경우 오직 카카오로그인밖에 할 수 없게 된다.

    public UserService(Login login) {
        this.login = login;
    }

    @Override
    public void login() {
        login.login();
    }
}

private 객체 login이 없다면 login() 메소드 역시 실행할 수 없기 때문에 메소드는 login에 의존하는 상태이다. 의존 객체를 주석 처리한 코드처럼 작성하게 되면 코드 작성자에 의해 결정된다. 하지만 생성자를 통해 의존성을 외부에 맡긴다면 여러 기능을 수행할 수 있게 된다. (의존도를 낮춘다.)

추상체와 결합하게 되면 결합도가 낮아진다. (구상체와 결합 : 주석 처리한 코드처럼 여러 구상체 객체 생성할 시.)

의존성을 외부로부터 전달 받음 → 의존성 주입 받음, Dependency Injection (DI)

클래스 → 여러 구상체 의존하게 되면 한 방향으로 의존하게 되지만,

클래스 → 추상체를 의존한다면, 추상체 ← 여러 구상체의 상속 구조를 가지게 된다. Dependency Inversion

Default Method 기능

자바의 기능 개선 → 인터페이스도 구상 메소드를 가질 수 있게 됨

public interface Login {
    void login();

    default void sayLogin(){
        System.out.println("Login");
    }
}

오버라이드 하지 않아도 호출 가능.

사용하는 이유

  • 필요한 메소드만 오버라이드하고 싶을 때, default 통하여 전부 오버라이드하지 않아도 됨.
  • 인터페이스 추가 상속만으로 기능 확장 가능 (구현 클래스에서 아무것도 작성하지 않아도 메소드 사용 가능)
  • static 메소드를 가질 수 있게 됨

함수형 인터페이스

정의 : 추상 메소드가 하나밖에 없는 메소드. (@FuntionalInterface) default, static 등의 메소드는 상관 없다.

번외 - 인터페이스 임시 생성

익명 클래스를 사용하여 인터페이스 인스턴스를 생성하고(new), 구현을 바로 정의한다.

new Login() {
	@Override
	public String supply() {
		return "Hello";
	}
}.supply();

이렇게 바로 호출 가능하다.

Login login = new Login() {
	@Override
	public String supply() {
			return "Hello";
	}
};

바로 인스턴스를 생성하고 구현부를 작성한다.

람다 표현식

//MyRunnable이 함수형 인터페이스일 때.
MyRunnable r1 = new MyRunnable() {
        @Override
        public void myRun() {
            System.out.println("Hello");
        }
    };

MyRunnable r2 = () -> System.out.println("Hello");

//return이 있을 때는 return 역시 생략

위 두 식은 같은 기능을 한다. (당연한 것을 생략)

객체를 MyRunnable 형으로 생성 → new MyRunnable 생략 가능

함수형 인터페이스 → 오버라이드 메소드 생략 가능

MyRunnable r2 = () { } 로 표현되는데, 이를 구분하기 위해 → 사용.

구현부가 한 줄뿐이므로 { } 생략하여 위와 같은 식 생성 가능.

람다 표현식의 정의 : 익명 메소드를 사용해서 간결한 인터페이스의 인스턴스 생성 방법. 오직 함수형 인터페이스에서만 가능하다.

인텔리제이에서 [ 빠른 수정 ] 기능을 통해 바로 람다 변환 가능하다.

메소드 레퍼런스

람다 표현식에서 입력되는 값을 변경 없이 바로 사용하는 경우, 최종으로 적용될 메소드 레퍼런스를 지정해주는 표현 방식.

MyMapper m = (str) -> str.length();
MyConsumer c = i -> System.out.println(i);

//람다 with method reference
MyMapper m = String::length;
MyConsumer c = System.out::println;

m : 매개변수로 str을 받고 String 클래스의 length()를 호출한다. 이때 매개변수를 바로 활용하므로 생략하여 표현할 수 있다.

c : 매개변수 i를 그대로 출력하기 때문에 생략한다.

단, 매개변수만 사용하는 경우가 아닐 경우 사용할 수 없다.

전부 인텔리제이 [ 빠른 수정 ] 기능으로 표현할 수 있다.

  • 입력값(매개변수)을 변경하지 말라는 표현 방식이기도 함.
  • 개발자의 개입 차단 → 안전성의 의미도 다소 포함.

제너릭

위와 같은 람다 표현식에서, 리턴 타입을 변경하고 싶을 때 하나하나 변경할 필요 없이 인터페이스에서 제너릭 타입을 지정하면 된다.

public interface MyMapper<IN, OUT> {
	OUT map (IN s);
}

MyMapper<String, Integer> m1 = String::length;
MyMapper<Integer, Integer> m2 = i -> i * i;
//MyMapper<Integer, String> m2 = i -> Integer.toHexString(i);
MyMapper<Integer, String> m2 = Integer::toHexString;
profile
기록용

0개의 댓글