태그 달린 클래스란 두가지 이상의 의미를 표현할 때 그 중 현재 표현하는 의미를 태그값으로 알려주는 클래스를 말한다.
태그 달린 클래스에는 여러 단점이 있다.
public class Figure {
enum Shape {RECTANGLE, CIRCLE}
final Shape shape;
// 태그필드 - 현재모양을 나타낸다.
double length;
double width;
// 필드 모양이 사각형(RECTANGLE)일때만 쓰인다.
double radius;
// 필드 모양이 원(CIRCLE)일때만 쓰인다.
public Figure(final double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
// 원 생성자
public Figure(final double length, final double width) {
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
// 사각형 생성자
double area() {
switch (shape) {
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError(shape);
}
}
}
열거 타입 선언, 태그 필드, switch문 등 쓸데없는 코드가 많다.
위에는 태그가 두개뿐이지만 태그가 많이지면 많이질 수록 코드의 가독성이 나빠질 것이 분명하다.
여러 구현이 한 클래스에 혼합되어 있고, 다른 의미를 위한 코드도 사용하므로 자원을 많이 사용한다.
쓰이지 않는 필드또한 초기화해야해서 불필요한 코드가 늘어난다.
또한 인스턴스 타입만으로 현재 나타내는 의미를 알 수 없다.
이런 태그 달린 클래스를 계층구조로 바꾸기 위해선
1. 루트가 될 추상클래스를 정의하고 추상화시키면 된다.
2. 그 후 루트 클래스를 확장한 구체 클래스를 의미별로 작성해면 된다.
abstract class HierarchyFigure {
abstract double area();
}
class Circle extends HierarchyFigure{
final double radius;
public Circle(final double radius) {
this.radius = radius;
}
@Override
double area() {
return Math.PI * (radius * radius);
}
}
class RECTANGLE extends HierarchyFigure{
final double width;
final double height;
public RECTANGLE(final double width, final double height) {
this.width = width;
this.height = height;
}
@Override
double area() {
return width * height;
}
}
책에서는 이런식으로 계층 구조로 변환했다.
내가 경험했던 프로젝트에선 클래스 상속과 내부 정적 클래스를 활용한 상속 계층구조, 빌더패턴을 이용해서 구현했었다.
@Getter
@ToString
public abstract class CommandParameter extends Parameter {
public enum CommandTypes {
CD, CHGRP, CHMOD, CHMTIME, CHOWN, EXIT, LSTAT, LS, MKDIR, PWD, QUIT,
READFILE, READLINK, RMDIR, RM, RENAME, STAT, LN, STATVFS, WRITEFILE
}
protected final CommandTypes commandType;
public CommandParameter(String sessionId, CommandTypes commandType) {
super(MessageTypes.A, sessionId);
this.commandType = commandType;
}
public abstract String toCommandString();
@Getter
@ToString
public static class ChangeDirectory extends CommandParameter {
private final String path;
@Builder
public ChangeDirectory(String sessionId, String path) {
super(sessionId, CommandTypes.CD);
this.path = path;
}
@Override
public String toCommandString() {
return String.format("cd %s", path);
}
}
//////
......
}
public static은 내부 정적 클래스를 선언하는데 사용되는 한정자이다.
내부 정적 클래스란 외부 클래스의 인스턴스와 독립적으로 존재하며, 외부 클래스의 멤버에 직접 접근할 수 있는 클래스이다.
내부 정적 클래스는 외부 클래스의 인스턴스에 대한 참조를 가지지 않으므로, 내부 정적 클래스의 인스턴스를 생성할 때 외부 클래스의 인스턴스를 먼저 생성할 필요가 없다.
따라서, pulbic static을 붙인 이유는 내부 정적 클래스를 외부 클래스와 독립적으로 사용하기 위해서다.
public staitc을 붙이지 않으면 CommandParameter.ChangeDirectory 로 접근할 수 없다.
CommandParameter 클래스 내부에서만 사용할 수 있다.
1. 태그 달린 클래스를 사용하는 것보다 클래스 계층구조를 활용해야 하는 이유는 무엇인가요?
태그 달린 클래스 ( 매개변수로 여러가지로 나눠지는 클래스 ) 는 하나의 클래스에서 여러 동작을 수행하는데 사용되고,
이로 인해 코드의 가독성이 떨어질 수 있다.
계층구조를 사용하면 각각의 클래스가 자신의 동작에만 집중하게 되고, 코드가 단순화되고 가독성이 올라간다.
새로운 동작이 추가될때도 기존 동작을 수정할 필요 없이 계층 클래스를 생성하면 되기 때문에 유지보수에도 이점이 있다.
2.클래스 계층구조를 활용하면 어떤 장점이 있나요?
클래스 계층 구조를 활용하면 각 클래스가 자신에게 필요한 필드와 메서드를 가지기 때문에 응집도가 높아진다.
위에 설명했듯이 새로운 동작을 추가할 때 기존 클래스를 수정하는 대신 새로운 클래스를 추가하면 되기 때문에
코드 유지보수와 확장에 용이하다.
여러 동작을 수행하는 클래스를 나누는 방법은 이 외에도 여러가지가 있지만,
적어도 다수의 동작이 있는 클래스를 if문이나 switch문을 범벅하여 하나의 클래스로 만드는 짓은 하지 않도록 하자.
'JAVA > 이펙티브 자바' 카테고리의 다른 글
25. 톱레벨 클래스는 한 파일에 하나만 담으라 (0) | 2023.07.10 |
---|---|
24. 멤버 클래스는 되도록 static으로 만들라 ( 멤버 클래스,어댑터 패턴 ) (0) | 2023.07.10 |
22. 인터페이스는 타입을 정의하는 용도로만 사용하라 ( 상수 공개 ) (0) | 2023.07.06 |
21. 인터페이스는 구현하는 쪽을 고려해 설계해라( default 메서드) (0) | 2023.07.06 |
20. 추상 클래스보다는 인터페이스를 우선하라(템플릿 메서드 패턴) (0) | 2023.07.03 |