GRASP 패턴: Controller (제어기)
Prof. Jong Min Lee이(가) 약 한달 전에 추가함
GRASP 패턴: Controller (제어기) - 간단한 설명과 예시¶
핵심 아이디어: 사용자 인터페이스(UI)로부터의 요청을 받아 애플리케이션 로직 객체에게 위임하거나 작업을 조정하는 책임을 할당합니다. UI 객체와 비즈니스 로직 객체 사이의 결합도를 낮추고 관심사를 분리하는 데 도움이 됩니다.
간단한 설명:
사용자와 시스템 간의 상호작용을 처리하는 객체는 UI와 직접적으로 비즈니스 로직을 수행하는 객체 사이의 중개자 역할을 하는 것이 좋습니다. Controller 패턴은 이러한 역할을 수행하는 클래스를 정의합니다. 컨트롤러는 UI로부터의 입력을 받아서 적절한 비즈니스 로직 객체에게 작업을 위임하거나, 여러 객체의 작업을 순서대로 조정하여 사용자의 요청을 처리합니다. 이를 통해 UI는 비즈니스 로직에 대한 직접적인 의존성을 갖지 않게 되고, 비즈니스 로직의 변경이 UI에 미치는 영향을 줄일 수 있습니다.
Controller의 종류:
- 유스케이스 컨트롤러 (Use-Case Controller): 하나의 유스케이스(사용자 시나리오) 전체 또는 관련된 액션들을 처리합니다. 예를 들어, "상품 구매" 유스케이스를 처리하는
PurchaseController
가 있을 수 있습니다. - 퍼사드 컨트롤러 (Facade Controller): 관련된 여러 액션들에 대한 진입점 역할을 하며, 하위 시스템의 복잡성을 숨깁니다.
예시:
온라인 쇼핑몰에서 사용자가 "상품 상세 정보 보기" 버튼을 클릭했을 때의 상황을 생각해 봅시다.
// 사용자 인터페이스 (UI) 클래스
class ProductDetailView {
private ProductService productService; // 직접적인 서비스 의존 (결합도 높음)
public ProductDetailView() {
this.productService = new ProductService();
}
public void displayProductDetails(String productId) {
Product product = productService.getProduct(productId);
// ... 상품 상세 정보 화면에 표시 ...
System.out.println("상품 ID: " + product.getId() + ", 이름: " + product.getName());
}
}
// 비즈니스 로직 클래스
class ProductService {
public Product getProduct(String productId) {
// 데이터베이스에서 상품 정보 조회 로직
System.out.println("데이터베이스에서 상품 " + productId + " 정보 조회");
return new Product(productId, "맛있는 사과", 2000);
}
}
// 도메인 객체
class Product {
private String id;
private String name;
private double price;
public Product(String id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
public class Client {
public static void main(String[] args) {
ProductDetailView view = new ProductDetailView();
view.displayProductDetails("P123");
}
}
위의 예시에서 ProductDetailView
는 ProductService
에 직접적으로 의존하고 있습니다. 만약 비즈니스 로직이 변경되거나 다른 서비스로 교체된다면 ProductDetailView
도 수정해야 할 가능성이 높습니다.
Controller 패턴을 적용하면 다음과 같이 설계를 변경할 수 있습니다.
// 사용자 인터페이스 (UI) 클래스
class ProductDetailView {
private ProductDetailController controller; // 컨트롤러에 의존
public ProductDetailView(ProductDetailController controller) {
this.controller = controller;
}
public void displayProduct(String productId) {
controller.showProductDetails(productId);
}
}
// 컨트롤러 클래스 (유스케이스 컨트롤러)
class ProductDetailController {
private ProductService productService;
public ProductDetailController(ProductService productService) {
this.productService = productService;
}
public void showProductDetails(String productId) {
Product product = productService.getProduct(productId);
// ... 필요한 경우 추가적인 로직 처리 ...
display(product); // UI에 표시를 위한 메서드 호출 (직접적인 UI 의존은 피하는 것이 좋음)
}
private void display(Product product) {
System.out.println("상품 ID: " + product.getId() + ", 이름: " + product.getName());
// 실제 UI 업데이트 로직 (예: View 객체에 데이터 전달)
}
}
// 비즈니스 로직 클래스 (동일)
class ProductService {
public Product getProduct(String productId) {
System.out.println("데이터베이스에서 상품 " + productId + " 정보 조회");
return new Product(productId, "맛있는 사과", 2000);
}
}
// 도메인 객체 (동일)
class Product {
private String id;
private String name;
private double price;
public Product(String id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
// ... getter 메서드 ...
}
public class Client {
public static void main(String[] args) {
ProductService productService = new ProductService();
ProductDetailController controller = new ProductDetailController(productService);
ProductDetailView view = new ProductDetailView(controller);
view.displayProduct("P123");
}
}
변경된 설계의 장점:
- 낮은 결합도:
ProductDetailView
는ProductService
에 직접적으로 의존하는 대신ProductDetailController
라는 추상화된 계층에 의존합니다. 비즈니스 로직이 변경되더라도 컨트롤러의 인터페이스가 유지된다면 UI 코드를 수정할 필요가 줄어듭니다. - 관심사 분리: UI는 사용자 입력 처리 및 화면 표시에 집중하고, 컨트롤러는 사용자 요청을 해석하고 비즈니스 로직을 호출하는 역할을 담당하여 관심사가 분리됩니다. 이는 코드의 유지보수성과 이해도를 높입니다.
- 재사용성: 컨트롤러는 여러 UI 컴포넌트에서 재사용될 수 있습니다.
- 테스트 용이성: UI와 비즈니스 로직이 분리되어 각 부분을 독립적으로 테스트하기 용이해집니다.
결론적으로, Controller 패턴은 UI와 비즈니스 로직 사이의 중개자 역할을 수행하여 시스템의 결합도를 낮추고 관심사를 분리하는 중요한 설계 원칙입니다. 적절한 컨트롤러의 도입은 애플리케이션을 더 유연하고 유지보수하기 쉽게 만들어줍니다.