2022. 10. 20. 01:04ㆍJAVA
1. Interface의 기능
(1) 구현을 강제
모든 메소드가 추상메소드이므로, 구현을 강제한다.
(2) 다형성을 제공
login() 메소드를 강제하는 Login 인터페이스가 있고 KakaoLogin, NaverLogin 두 개의 구현체가 있는 상황이다.
public interface Login {
void login();
}
public class KakaoLogin implements Login{
@Override
public void login() {
System.out.println("카카오 로그인 진행");
}
}
public class NaverLogin implements Login{
@Override
public void login() {
System.out.println("네이버 로그인 진행");
}
}
public enum LoginType {
Kakao, Naver;
}
아래는 로그인 로직 코드인데, getLogin 메소드의 리턴타입을 보면 카카오 로그인, 네이버 로그인의 부모 타입인 Login인 것을 확인할 수 있다.
run() 메소드에서도 역시 Login 타입으로 변수를 선언하고, login() 메소드를 호출하는 모습이다.
public class LoginLogic {
public static void main(String[] args) {
new LoginLogic().run(LoginType.Kakao); // 호스트 코드
}
void run(LoginType loginType) {
//참조 변수의 타입을 Login으로 !
Login user = getLoginType(loginType);
user.login();
}
// 팩토리 패턴, Login 타입을 리턴한다.
private static Login getLoginType(LoginType loginType) {
if(loginType == LoginType.Kakao) return new KakaoLogin();
return new NaverLogin();
}
}
이렇게 코드를 작성하면, run() 메소드에서는 getLoginType에서 어떤 타입의 Login 구현체가 채택되는지 신경쓸 필요가 없다. 구현체에는 관심을 끊은 채로 Login이라는 추상화에 의존해서 정해진(인터페이스에 선언된 메소드) 작업만 하면 될 뿐이다.
구체적인 Login 타입을 정하는 것은 호스트 코드 부분에서 이루어진다. 코드가 추상화에만 의존하므로 호스트 코드 부분에서 구현체만 갈아끼우면 코드의 수정없이 정상적으로 작동한다.
(3) 결합도를 낮추는 효과 (의존성을 역전)
구체 클래스가 아닌 인터페이스를 의존하면 결합도가 낮아진다.
아래 LoginService에 생성자를 통해 주입된 구체클래스가 Login 타입이라면 무엇이든 주입되어 사용할 수 있으며 구체 클래스가 변경되어도 수정해야 할 코드는 없다.
public class LoginService implements Login{
//Login 인터페이스에 의존한다.
//만약 Login login = new KakaoLogin(); 처럼 구현체를 내부에서 결정한다면
//이 서비스 코드는 카카오로그인의 경우에만 사용할 수 있다 --> 따라서 생성자를 통해 외부에서 주입받자
private Login login; // 캡슐화
public LoginService(Login login) {
this.login = login;
}
@Override
public void login() {
login.login();
}
}
LoginService를 사용하는 쪽에서 구체적인 Login의 구현체를 주입한다.
public class Main {
public static void main(String[] args) {
LoginService loginService = new LoginService(new KakaoLogin());
loginService.login();
}
}
아래 Main 클래스를 보면 LoginService를 사용하면서 구체 클래스를 결정해 주입해주는 모습이다.
이처럼 의존성을 외부에 맡긴다면 의존도를 낮출 수 있다(의존성 주입, DI).
==> 요약: 구체 클래스를 직접 의존하는 것 보다 중간에 인터페이스라는 창을 두면 얻을 수 있는 이점이 훨씬 많다.
2. default Method
Java 8부터는 인터페이스가 static, default 메소드를 가질 수 있다.
즉, 인터페이스가 구현체를 가질 수 있게 된 것인데 기존의 문제점과 함께 static, default 메소드의 장점을 살펴보자.
문제점
원래 인터페이스는 추상 메소드의 집합으로, 구현이 반드시 필요했다.
따라서 인터페이스가 단 하나의 추상 메소드를 가지고 있어도 이를 사용하기 위해서 구현클래스를 제작해야 하는 비효율이 발생했다(이러한 단점의 개선은 함수형 인터페이스에서 다룰 예정이다).
뿐만아니라, 인터페이스의 추상 메소드 N개 중 단 하나의 메소드만 사용하고 싶은 경우에는
일단 N개의 추상 메소드를 전부 다 오버라이딩한 뒤에 필요한 메소드를 사용해야만 했다. 맨 위에서 말했듯 인터페이스는 구현을 강제하니까.
장점
하지만 인터페이스에 default 메소드로 선언한 메소드는 구현이 강제되지 않는다. N개의 메소드가 전부 default 메소드라면 N개의 메소드를 구현할 필요 없이 원하는 메소드를 바로 가져다 사용할 수 있다.
마지막으로, 인터페이스에 메소드를 추가할 때 추상메소드로 추가하면 모든 구체 클래스에서 해당 메소드를 구현해야 한다. default로 메소드를 선언해서 추가하면 구현할 필요 없으니 이런 번거로움도 사라진다.
진짜 마지막으로, static 메소드는 기능 추가가 쉽다. default 메소드와 마찬가지로 구체 클래스에서 구현할 필요도 없다.
public interface Ability {
static void sayHi() {
System.out.println("say Hi");
}
}
interface Swimmable{
void swim();
}
interface Walkable{
void walk();
}
interface flyable{
void fly();
}
Ability 인터페이스에 sayHi()로 static 메소드를 선언했다. static 메소드는 구현할 필요도 없이 바로 사용 가능하다.
class Duck implements Swimmable, Walkable{
@Override
public void swim() {
System.out.println("swim");
}
@Override
public void walk() {
System.out.println("walk");
}
}
public class Main {
public static void main(String[] args) {
new Duck().swim();
Ability.sayHi(); //한 방에 사용 가능하다.
}
}
'JAVA' 카테고리의 다른 글
Java - Calculator 과제 회고 (0) | 2022.10.30 |
---|---|
Java 숫자야구 (0) | 2022.10.21 |
Collection과 Iterator (0) | 2022.10.20 |
Java equals()와 hashCode() (0) | 2022.10.19 |
Optional<T> (0) | 2022.02.16 |