run() 메서드의 복잡도는 아직 상당히 높다.
public void run() {
log.debug("New Client Connect! Connected IP : {}, Port : {}", connection.getInetAddress(),
connection.getPort());
try (InputStream in = connection.getInputStream(); OutputStream out = connection.getOutputStream()) {
HttpRequest request = new HttpRequest(in);
String requestURL = request.getPath();
HttpResponse response = new HttpResponse(out);
if( requestURL.contains("/user/create")) {
/* 생략 */
} else if( requestURL.equals("/user/login")) {
/* 생략 */
} else if( requestURL.equals("/user/list")) {
/* 생략 */
} else {
response.forward(requestURL);
}
- run() 메서드의 가장 큰 문제점은 기능이 추가될 때마다 새로운 else if 절이 추가되는 구조로 구현되어 있다.
- 이는, OCP(개방폐쇄의 원칙. Open-Closed Principle) 원칙을 위반하고 있다.
OCP(개방폐쇄의 원칙. Open-Closed Principle) 원칙이란
- 객체지향 설계 원칙 중 요구사항의 변경이나 추가사항이 발생하더라도, 기존 구성요소는 수정이 일어나지 말아야 하며, 기존 구성요소를 쉽게 확장해서 재사용할 수 있어야 한다는 원칙
우선, 메서드 리팩토링을 하자.
1. 따라서, 각 분기문 구현을 별도의 메서드로 분리하는 리팩토링(Extract Method 리팩토링)을 진행해야 한다.
public void run() {
log.debug("New Client Connect! Connected IP : {}, Port : {}", connection.getInetAddress(),
connection.getPort());
try (InputStream in = connection.getInputStream(); OutputStream out = connection.getOutputStream()) {
HttpRequest request = new HttpRequest(in);
String requestURL = request.getPath();
HttpResponse response = new HttpResponse(out);
if( requestURL.contains("/user/create")) {
createUser(request, response);
} else if( requestURL.equals("/user/login")) {
login(request, response);
} else if( requestURL.equals("/user/list")) {
listUser(request, response);
} else {
response.forward(requestURL);
}
[ ..생략.. ]
private void listUser( HttpRequest request, HttpResponse response ) {
[ ..생략.. ]
}
private void login( HttpRequest request, HttpResponse response ) {
[ ..생략.. ]
}
private void createUser( HttpRequest request, HttpResponse response ) {
[ ..생략.. ]
}
메서드 원형이 같으니 인터페이스(interface)로 추출하자.
2. 리팩토링을 진행하니, 각 메서드는 앞의 리팩토링 과정에서 추가한 HttpRequest, HttpResponse만 인자로 받는 것을 확인할 수 있다. 이와 같이 메서드 원형이 같기 때문에 인터페이스(interface)로 추출하는 것이 가능하다.
public interface Controller {
void service( HttpRequest request, HttpResponse response );
}
분기문에서 처리했던 메서드의 구현 코드를 Controller 인터페이스에 대한 구현 클래스를 생성한다.
public class CreateUserController implements Controller {
public void service( HttpRequest request, HttpResponse response ) {
[..생략..]
}
}
요청 URL에 맞는 Controller를 반환하는 RequestMapping 클래스 생성
각 요청 URL과 URL에 대응하는 Controller를 연결하는 RequestMapping이라는 새로운 클래스를 생성한다. 요청 URL에 해당하는 Controller를 반환하는 역할을 한다.
public class RequestMapping {
private static Map<String, Controller> controllers =
new HashMap<String, Controller>();
static {
controllers.put("/user/create", new UserCreateController());
controllers.put("/user/login", new UserLoginController());
controllers.put("/user/list", new UserListController());
}
public static Controller getController( String requestUrl ) {
return controllers.get(requestUrl);
}
}
앞으로 새로운 기능이 추가되어도, run() 메서드는 더 이상 수정할 필요가 없어진다.
Controller controller =
RequestMapping.getController(request.getPath());
if( controller == null ) {
String path = getDefaultPath(request.getPath());
response.forward(path);
} else {
controller.service(request, response);
}
메서드에 따른 처리를 할 수 있는 추상 클래스 생성
여기서 한 발 더 나아가 각 HTTP 메서드(GET, POST)에 따라 다른 처리를 할 수 있도록 추상 클래스를 추가할 수 있다.
public abstract class AbstractCotroller implements Controller {
@Override
public void service(HttpRequest request, HttpResponse response) {
String method = request.getMethod();
if( method.equals("POST")) {
doPost(request, response);
} else {
doGet(request, response);
}
}
protected void doPost(HttpRequest request, HttpResponse response) {
}
protected void doGet(HttpRequest request, HttpResponse response) {
}
추상 클래스를 상속받아 메서드 오버라이딩
각 Controller는 Controller 인터페이스를 직접 구현하는 것이 아닌 AbstractController를 상속해 각 HTTP 메서드에 맞는 메서드를 오버라이드 하도록 구현할 수 있다.
public class UserCreateController extends AbstractCotroller {
public void doPost(HttpRequest request, HttpResponse response) {
User user = new User(
request.getParameter("userId"),
request.getParameter("password"),
request.getParameter("name"),
request.getParameter("email") );
DataBase.addUser(user);
response.sendRedirect("/index.html");
}
}
'Java' 카테고리의 다른 글
[Java] static 블록 / 초기화 블럭 (0) | 2023.03.21 |
---|---|
[Java] 세션(HttpSession) 구현 / 세션은 HTTP 쿠키를 기반으로 동작 (0) | 2023.03.21 |
[Java] 응답 데이터를 처리하는 로직을 HttpResponse 클래스로 분리 및 리팩토링 (0) | 2023.03.19 |
[Java] 요청 데이터 처리 로직을 HttpRequest 클래스로 분리 및 리팩토링 (0) | 2023.03.19 |
[Java] HTTP 웹 서버 구현 / CSS 지원하기 (0) | 2023.03.17 |
댓글