14. 자바 인터페이스와 다형성 (1)
이 포스트에서는 인터페이스와 인터페이스로 다형성을 어떻게 구현하는지에 알아보도록 하겠다.
이전 포스트
목표
- 프로젝트 구조
- 인터페이스가 필요한 이유
- 인터페이스 (Interface)
- 상수 (Constant)
프로젝트 구조
JavaInterfaceTutorial
├── JavaInterfaceTutorial.iml
└── src
├── AgeUserValidator.java
├── Main.java
├── NameUserValidator.java
├── User.java
└── UserValidator.java
1 directory, 6 files
인터페이스가 필요한 이유
예를들어 아래처럼 User라는 클래스가 있다고하자. 우리는 인풋으로 name과 age를 입력받고, 입력받은 값이 올바른지 확인하고싶다.
public class User {
public String name;
public int age;
}
간단하게 이름은 0자 이상이어야하고 나이는 1~99세까지라고 해보자. 지금까지 알고있는 지식으로는 아래처럼 검사를 하는 코드를 짤 수 있다.
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
User user = new User();
System.out.print("## 이름 :");
user.name = new Scanner(System.in).next();
System.out.print("## 나이 :");
user.age = new Scanner(System.in).nextInt();
validateUser(user);
}
public static void validateUser(User user) {
if(user.name == null || user.name.isEmpty()) {
System.out.println("에러: 이름을 입력해주세요.");
}
if(user.age <= 0 || user.age > 99) {
System.out.println("에러: 올바른 나이를 입력해주세요.");
}
}
} //실행결과## 이름 :d ## 나이 :-1 에러: 올바른 나이를 입력해주세요.
위처럼 짤 수 있다. 하지만 만약에 유저에 10개의 멤버변수가 있고 각 멤버변수를 모두 검사해야 한다면 어떻게 하겠는가? 또는 다른 메서드에서는 name만 검사하고 age는 검사하지 않고 싶다면 어떻게 할것인가? 일단 이 메서드들을 나눠야한다.
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
User user = new User();
System.out.print("## 이름 :");
user.name = new Scanner(System.in).next();
System.out.print("## 나이 :");
user.age = new Scanner(System.in).nextInt();
validateName(user);
validateAge(user);
}
public static void validateName(User user) {
if(user.name == null || user.name.isEmpty()) {
System.out.println("에러: 이름을 입력해주세요.");
}
}
public static void validateAge(User user) {
if(user.age <= 0 || user.age > 99) {
System.out.println("에러: 올바른 나이를 입력해주세요.");
}
}
}
이렇게하면 원하는 멤버변수를 검사하고싶을 때마다 원하는 메서드를 부르면 된다. 이렇게 하는경우 10개의 validate메서드를 만들고 validate을 원하는곳에서 부를수있다. 하지만 이렇게 10개가 있으면 코드를 관리하기 매우 어려워진다. 또 유저뿐만 아니라 프로그램을 다른것들도 validate해야 할 수 있다. 그런경우 이렇게 한 클래스에서 메서드를 여러개 작성하는것은 개발자를 매우 헷갈리게 할 수 있다.
인터페이스 (Interface)
인터페이스는 추상클래스와 비슷하다. 단 변수를 가질 수 없으며 추상메서드만 있어야 한다. (자바 8에서는 default메서드를 제공하지만 이는 자바8에서 다루도록 한다.) 이 포스트에서는 Validator라는 인터페이스를 예를들어 설명할 것이니, User.java와 UserValidator.java를 만들어보도록 하자.
public interface UserValidator {
}
인터페이스는 위처럼 <접근제어자> interface <인터페이스_이름>으로 생성할 수 있다. 위에서 말했듯 인터페이스는 상수 또는 추상메서드만 가질 수 있다. 추상메서드는 추상 클래스에서 했으니 UserValidator에 validate이라는 추상메서드를 선언 해 보자.
public interface UserValidator {
void validate(User user);
}
이렇게 만든 인터페이스를 사용하는것을 '구현(implement)한다'라고한다. 이제 각 Name, Age를 검사하기위한 클래스를 각각 만들어보자.
NameUserValidator.java
public class NameUserValidator implements UserValidator {
@Override
public void validate(User user) {
if(user.name == null || user.name.isEmpty()) {
System.out.println("에러: 이름을 입력해주세요.");
}
}
}
구현은 클래스의 이름 다음에 implements <인터페이스이름>을 적어준다. 그리고나서 validate메서드를 Override해 구현해 주면 된다.
AgeUserValidator.java
public class AgeUserValidator implements UserValidator {
@Override
public void validate(User user) {
if(user.age <= 0 || user.age > 99) {
System.out.println("에러: 올바른 나이를 입력해주세요.");
}
}
}
이렇게 만든 Validator들을 어떻게 사용할까?
import java.util.Scanner;
public class Main {
static UserValidator[] userValidator = {new NameUserValidator(), new AgeUserValidator()};
public static void main(String[] args) {
User user = new User();
System.out.print("## 이름 :");
user.name = new Scanner(System.in).next();
System.out.print("## 나이 :");
user.age = new Scanner(System.in).nextInt();
for (int i = 0; i < userValidator.length; i++) {
userValidator[i].validate(user);
}
}
} //실행 결과 ## 이름 :d ## 나이 :-1 에러: 올바른 나이를 입력해주세요.
위 처럼 Main에서 UserValidator를 정의한후 위처럼 배열에 UserValidator가 있는 만큼 반복문을 돌려주면 10개의 메서드를 부르지 않아도 Validation을 할 수 있다. 이렇게하면 Validator가 몇개이든 상관없고, 나중에 또 다른 UserValidator를 추가하고싶을 때 배열에만 추가하면된다. 이렇게 하면 validate이라는 메서드를 부르지만 각 메서드의 구현부는 NameUserValidator, AgeUserValidator, ...등등 다르다.
상수 (Constant)
변수는 값이 변할 수 있는 것이다. 상수(Constant)는 맨 처음에 초기화한 후 값을 절대 바꿀수 없는 것이다. 상수를 어떻게 선언하는가? final이라는 키워드를 이용해 선언한다.
final int NUMBER = 3;
예를들어 위처럼 final을 변수 앞에 적어주면 앞으로 이 number안의 값은 절대 바꿀수 없다.
인터페이스가 상수만 가질수 있다는 것은 인터페이스 내에 선언된 모든 변수의 앞에 final이 붙어있다는 뜻이고 그 값을 아무도 변경할 수 없다는 뜻이다.
final은 간단해보이지만 레퍼런스 개념과 섞이면 헷갈리기 쉬우므로 다른 포스트에서 더 다루도록 하겠다.
끝
이번 포스트에서는 인터페이스를 정의하는 법과 사용하는 법에 대해서 알아보았다. 보통 검사를 하는 메서드에서는 에러메시지와 함께 Exception을 던진다. 이에 대해서는 다른 포스트에서 설명하도록 하겠다.
전체 코드는 https://gist.github.com/fsoftwareengineer/ee4c3eaeb4d33a2f55c39e818d161d0a 에서 확인할 수 있다.
다음 포스트: