본문 바로가기
Java

[Java] 다형성을 활용해 클라이언트 요청 URL에 대한 분기 처리를 제거

by bkuk 2023. 3. 20.

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");
	}
}

댓글