오늘 수업시간에 리팩토링을 통해 enum, 익명 클래스, 람다(lamda)의 개념을 배울 수 있었습니다. 하지만 오늘 주제가 enum인 만큼 enum 위주로 정리해보도록 하겠습니다. 먼저 리팩토링 할 코드입니다.
import java.util.List;
public class FigureFactory {
Figure getInstance(List<Point> points) {
if (points.size() == 2) {
return new Line(points);
}
if (points.size() == 3) {
return new Triangle(points);
}
if (points.size() == 4) {
return new Rectangle(points);
}
throw new IllegalArgumentException("유효하지 않은 도형입니다.");
}
}
미션1 : enum 개념을 사용하여 상수 구현하라
구현해야 할 부분은 getInstance()
메서드 내에 points.size()
에 따라 2, 3, 4라는 상수가 사용되고 있습니다. 요구사항은 이 상수 부분은 enum개념을 활용하여 구현하는 것입니다.
이를 위해선 먼저 enum 개념에 대해 알아야겠죠.1
enum은 뭘까?
- 상수로 구성된 클래스
- enumerate('열거하다', '차례로 두다')의 준말로
class
예약어 대신enum
사용 - 서로 관련있는 상수들을 모아 심볼릭한 명칭의 집합으로 정의한 것
선언
- 하나의 자바 파일로 만들거나, Inner Class, 또는 클래스 밖에서 사용 가능
사용하기
public enum Type {
LINE,
TRIANGLE,
RECTANGLE
}
이렇게 사용되면 Type
타입으로 enum클래스의 상수값들을 활용할 수 있습니다. 예를들어 Line
클래스 그 도형의 정보를 Type
형으로 입력시키려면 다음과 같이 사용될 수 있습니다.
public class Line {
public String name;
public int countOfPoint;
public Type type;
public static void main(String[] args) {
Line line = new Line();
line.name = "직선";
line.countOfPoint = 2;
line.type = Type.LINE;
}
}
메소드
1) values() : 열거된 모든 원소를 배열에 담아 반환합니다.
public enum Type {
LINE,
TRIANGLE,
RECTANGLE
}
public static void main(String[] args) {
for (Type value : Type.values()) {
System.out.println(value);
}
}
호출시 다음과 같이 출력됩니다.
LINE
TRIANGLE
RECTANGLE
2) ordinal() : 원소에 열거된 순서를 정수값으로 반환
위 코드 9번째 줄에서 value
대신 value.ordinal()
로 바꾸면 다음과 같이 출력이 됩니다.
0
1
2
3) valueOf() : 매개변수로 주어진 String값과 같은 이름을 가진 원소를 반환(일치하는게 없을 경우 IllegalArgumentException 예외 발생)
위 enum클래스를 그대로 활용하여 main만 다음과 같이 바꿔주면 LINE
이 출력됨을 알 수 있습니다.
public static void main(String[] args) {
Type t1 = Type.valueOf("LINE");
System.out.println(t1);
}
열거형 상수를 다른 값과 연결하기
열거형 상수를 enum에서 생성자 같은 역할을 이용하여 다른 값과 연결할 수 있습니다. 객체지향적 관점에서 getter 메서드를 사용하는 것 보다는 matchNumOfPoint()
처럼 메시지를 보내서 값을 확인하는게 좋겠죠
public enum Type {
LINE(2),
TRIANGLE(3),
RECTANGLE(4);
private final int numOfPoint;
Type(int numOfPoint) { // enum에서 생성자와 같은 역할
this.numOfPoint = numOfPoint;
}
public int getNumOfPoint() { // 수를 가져오는 함수
return numOfPoint;
}
public boolean matchNumOfPoint(int num) { // 수가 일치하는지 확인하는 함수
return numOfPoint == num;
}
}
enum의 개념을 활용하여 FigureFactory
클래스를 리팩토링하면 다음과 같이 변화될 수 있습니다.
public class FigureFactory {
public enum Type {
LINE(2),
TRIANGLE(3),
RECTANGLE(4);
private final int numOfPoint;
Type(int numOfPoint) { // enum에서 생성자와 같은 역할
this.numOfPoint = numOfPoint;
}
public int getNumOfPoint() { // 수를 가져오는 함수
return numOfPoint;
}
public boolean matchNumOfPoint(int num) { // 수가 일치하는지 확인하는 함수
return numOfPoint == num;
}
}
static Figure getInstance(List<Point> points) {
if (Type.LINE.matchNumOfPoint(points.size())) {
return new Line(points);
}
if (Type.TRIANGLE.matchNumOfPoint(points.size())) {
return new Triangle(points);
}
if (Type.RECTANGLE.matchNumOfPoint(points.size())) {
return new Rectangle(points);
}
throw new IllegalArgumentException("유효하지 않은 도형입니다.");
}
}
미션2 : if문 중첩을 없애라
Map을 활용하면 비교적 쉽게 없앨 수 있습니다.
public class FigureFactory {
/* 위 내용 생략 */
public static Type getType(int numOfPoint) {
for (Type value : Type.values()) {
if (value.matchNumOfPoint(numOfPoint)) {
return value;
}
}
return null;
}
}
private static Map<Type, FigureCreator> figures = new HashMap<>();
static {
figures.put(Type.LINE, new LineGenerator());
figures.put(Type.TRIANGLE, new TriangleGenerator());
figures.put(Type.RECTANGLE, new RectangleGenerator());
}
static Figure getInstance(List<Point> points) {
try {
return figures.get(Type.getType(points.size())).create(points);
} catch(Exception e) {
throw new IllegalArgumentException("유효하지 않은 도형입니다.");
}
}
}
class LineGenerator implements FigureCreator {
@Override
public Figure create(List<Point> points) {
return new Line(points);
}
}
class TriangleGenerator implements FigureCreator {
@Override
public Figure create(List<Point> points) {
return new Triangle(points);
}
}
class RectangleGenerator implements FigureCreator {
@Override
public Figure create(List<Point> points) {
return new Rectangle(points);
}
}
if의 중첩을 없앨 수 있었지만 points를 전달하기 위해 팩토리 클래스가 필요하였습니다. 그 이유는 다음과 같습니다.
미션3 : 익명 클래스를 이용하여 팩토리 클래스를 없애라
익명 클래스를 사용하면서 세 개의 팩토리 클래스를 없앨 수 있었습니다. 이때까지 팩토리 클래스를 다 따로따로 만들어 클래스가 많아져 복잡했었는데요. 익명 클래스를 통해 단순해졌네요.
static {
figures.put(Type.LINE, new FigureCreator() {
@Override
public Figure create(List<Point> points) {
return new Line(points);
}
});
figures.put(Type.TRIANGLE, new FigureCreator() {
@Override
public Figure create(List<Point> points) {
return new Triangle(points);
}
});
figures.put(Type.RECTANGLE, new FigureCreator() {
@Override
public Figure create(List<Point> points) {
return new Rectangle(points);
}
});
}
익명클래스를 이용하면 다음과 같이 static 블록을 변경할 수 있습니다. 이 안에 팩토리가 다 담겨있는 것이죠. 익명 클래스를 보낼 때 헤매었던 부분이 있었는데요. new
부분에 재구현할 명세가 되는 상위 클래스가 와야한다는 것입니다. 이름이 없어서 익명 클래스인데 재구현할 클래스명을 써놓고 한동안 헤메었네요..
미션4 : 람다(lamda)를 이용하여 static 블록을 좀 더 단순화하여라
람다(lamda)를 이용하여 static블록을 아래와 같이 단순화할 수 있었습니다. 람다는 특정 조건일 때 사용할 수 있는데요. 람다로 구현할 클래스의 메서드가 1개만 있을 때 입니다. 한 개 밖에 없어야 컴파일러가 그 특정 메서드를 인식하기 때문입니다.만약 여러 개의 메서드가 있다면 컴파일러가 구별하지 못하겠죠.
static {
figures.put(Type.LINE, (List<Point> points) -> {
return new Line(points);
});
figures.put(Type.TRIANGLE, points -> {
return new Triangle(points);
});
figures.put(Type.RECTANGLE, Rectangle::new);
}
람다(lamda)는 아직 제대로 익히지 못해 이해가 부족한 부분이 있는데요. 좀 더 필요로 할 때 다시 정리해서 올릴려고 합니다.
추가로 오후에 포비에 자리로 와서 직접 리뷰를 해줬는데요. 그곳에서 전해들은 내용 정리하면 다음과 같습니다.
enum이 안전한 이유?
- 예를들어
Figures.get()
이 있고 여기 매개변수로int
값을 받아 enum에 있는 상수값을 전해준다고 생각해봅시다. 그런데 알다시피 int값의 범위는 엄청 작은 음수부터 양수까지 넓게 분포되어 있는데요. 이럴 경우 enum 상수에 등록된 값이 아니면 위에서 살펴보았듯이 예외처리 해버리기 때문에 안전할 수 있습니다.
- 예를들어
enum에서
values()
를 이용하여 for each 돌리는 것 이외에 따른 방법은 없나?- 포비가 잠깐 stream을 이용한 방법을 얘기하긴 했는데요. 이 부분은 다음 주에 어차피 배울거라 이런 방법만 귀띰해줬습니다. 이 부분은 다음 수업시간에 배우면 정리해보겠습니다.
'Language > Java' 카테고리의 다른 글
제네릭(Generic) (0) | 2018.11.23 |
---|---|
예외처리 (0) | 2018.10.25 |
인터페이스(Interface) (0) | 2018.10.15 |
상속(Inheritance) (3) | 2018.10.11 |
DTO와 Java Bean (2) | 2018.10.01 |