[인프콘] 객체지향은 여전히 유용한가 - 조영호님 세미나 정리

sangcheol·2024년 8월 29일
0
post-thumbnail

[지금 무료] 인프콘 2024 다시보기 강의 | 인프런 - 인프런

객체지향은 여전히 유용한가? 라는 주제의 조영호님 세미나를 듣고 정리해보았다.

객체지향은 여전히 유용할까?

위의 질문보다 올바른 질문은 무엇일까? 더 건설적인 질문은 무엇일까?

먼저 객체지향과 절차지향 설계는 각각 어떤 상황에서 더 적합하며, 각 방식의 장단점은 무엇일지에 대해 예시를 통해 알아보자.

예제 도메인 설명

장바구니에 할인 프로모션 적용

장바구니에 할인을 적용할지 말지를 판단하고 조건을 충족하면 할인을 적용

기본 조건

Cart 전체 금액 ≥ Promotion 기준 금액

절차적인 설계

절차적인 방식

  • 데이터프로세스를 별도의 클래스로 구현하는 방식
  • PromotionCart 클래스는 데이터

Promotion - 데이터

public class Promotion {

    private Long cartId;
    private Long basePrice;

    public Money getBasePrice() {
        return basePrice;
    }

    public void setCart(Long cartId) {
        this.cartId = cartId;
    }
}

Cart - 데이터

public class Cart {

    private List<CartLineItem> items = new ArrayList<>();

    public Long getTotalPrice() {
        return items.stream().mapToLong(CartLineItem::getPrice).sum();
    }

    public int getTotalQuantity() {
        return items.stream().mapToInt(CartLineItem::getQuantity).sum();
    }
}

Promotion

  • basePrice라는 기준 값을 가지는 속성
  • 속성을 외부에 제공하는 Getter

Cart

  • CartLineItem을 리스트로 가짐
  • 토탈 금액 반환 Getter
  • 전체 수량 반환 Getter

위 두가지가 데이터이다.

절차적인 설계는 이 데이터를 가지고 어떤 로직을 처리할 때 그 로직을 데이터를 담고있지 않은 별도의 클래스에 구현하는 방식이다.

PromotionProcess - 프로세스

public class PromotionProcess {

    public void apply(Promotion promotion, Cart cart) {
        if (isApplicableTo(promotion, cart)) {
            promotion.setCart(cart.getId());
        }
    }

    private boolean isApplicableTo(Promotion promotion, Cart cart) {
        return cart.getTotalPrice() >= promotion.getBasePrice();
    }
}

isApplicableTo()

  • 메서드를 통해 카트의 전체 금액과 프로모션 기준 금액을 비교

apply()

  • isApplicableTo() 조건을 만족하면 프로모션에 카트를 세팅

객체지향적인 설계

객체지향적인 방식이란 Promotion, Cart 라는 데이터와 PromotionProcess라는 로직을 가지는 프로세스가 하나의 클래스에 같이 뭉쳐놓은 것을 말한다.

Promotion

public class Promotion {

    private Cart cart;
    private Long basePrice;

    public void apply(Cart cart) {
        if (cart.getTotalPrice() >= basePrice) {
            this.cart = cart;
        }
    }
}

Cart

public class Cart {

    private List<CartLineItem> items = new ArrayList<>();

    public Long getTotalPrice() {
        return items.stream().mapToLong(CartLineItem::getPrice).sum();
    }

    public int getTotalQuantity() {
        return items.stream().mapToInt(CartLineItem::getQuantity).sum();
    }
}

절차적인 설계와 객체지향 설계

첫 번째 요구사항 변경 - 데이터 변경

우리는 객체지향적인 방식과 절차지향적인 방식 중 어느 방식으로 설계할지 결정해야 한다.

변경이라는 키워드에 초점을 맞추어 요구사항이 변경 될 때 두 방식 중 어떤 방식이 좋은 지 판단해보자

할인 여부를 판단하는데 필요한 데이터를 변경한다면?

이전 기준: Cart 전체 금액 ≥ Promotion 기준 금액

변경된 기준: Promotion 최소 금액 ≥ Cart 전체 금액 ≤ Promotion의 최대 금액

절차적인 설계 - 데이터 변경

Promotion

public class Promotion {

    private Long cartId;
    private Long minPrice; // 변경
    private Long maxPrice; // 변경

	// 추가
    public Long getMinPrice() {
        return minPrice;
    }

	// 추가
    public Long getMaxPrice() {
        return maxPrice;
    }

    public void setCart(Long cartId) {
        this.cartId = cartId;
    }

}

PromotionProcess

public class PromotionProcess {

    public void apply(Promotion promotion, Cart cart) {
        if (isApplicableTo(promotion, cart)) {
            promotion.setCart(cart.getId());
        }
    }

    private boolean isApplicableTo(Promotion promotion, Cart cart) {
	    // 변경
        return cart.getTotalPrice() >= promotion.getMinPrice() && 
	             cart.getTotalPrice() <= promotion.getMaxPrice();
    }
}

절차적인 설계는 데이터와 그 데이터를 사용하는 프로세스가 별도의 클래스에 구현되어 있기 때문에 데이터를 바꾸면 그 데이터를 사용하는 프로세스도 같이 바뀌어야 한다.

⇒ 결합도가 높다!

객체지향 설계 - 데이터 변경

Promotion

public class Promotion {

    private Cart cart;
    private Long minPrice; // 변경
    private Long maxPrice; // 변경

    public void apply(Cart cart) {
	    // 변경
        if (cart.getTotalPrice() >= minPrice &&
            cart.getTotalPrice() <= maxPrice) {
            this.cart = cart;
        }
    }
} 

객체지향의 경우 데이터를 사용하는 로직이 동일한 클래스 안에 숨겨져 있다.

따라서 Promotion 클래스의 로직만 변경하면 된다.

데이터 변경에서의 객체지향, 절차지향 정리

절차지향: 데이터를 바꾸면 데이터를 사용하는 모든 프로세스들도 같이 바뀐다. ⇒ 결합도가 높다

객체지향: 캡슐화 덕분에 데이터의 요구사항이 변경이 되면 한 클래스만 바꾸면 된다. ⇒ 결합도가 낮다

데이터가 바뀔 때 절차적인 설계보다는 객체지향적인 설계가 더 좋다!

두 번째 요구사항 변경 - 새로운 할인 조건 추가

장바구니 총수량(quantity)을 기준으로 할인 여부를 판단하는 새로운 타입을 추가한다면?

기존 조건

Cart 전체 금액 ≥ Promotion 기준 금액

변경된 조건

Cart 전체 금액 ≥ Promotion 기준 금액

Cart 총수량 ≥ Promotion 총수량

절차적인 설계 - 새로운 할인 조건 추가

코드 리팩토링

Promotion

public class Promotion {
	// 추가
    public enum ConditionType {
        PRICE, QUANTITY
    }

    private ConditionType conditionType; // 추가

    private Long cartId;
    private Long basePrice;
    private int baseQuantity; // 추가

    public Long getBasePrice() {
        return basePrice;
    }

	// 추가
    public ConditionType getConditionType() {
        return conditionType;
    }

	// 추가
    public int getBaseQuantity() {
        return baseQuantity;
    }

    public void setCart(Long cartId) {
        this.cartId = cartId;
    }
}

PromotionProcess

public class PromotionProcess {

    public void apply(Promotion promotion, Cart cart) {
        if (isApplicableTo(promotion, cart)) {
            promotion.setCart(cart.getId());
        }
    }

    private boolean isApplicableTo(Promotion promotion, Cart cart) {
    	// 변경
        switch (promotion.getConditionType()) {
            case PRICE:
                return cart.getTotalPrice() >= promotion.getBasePrice();
            case QUANTITY:
                return cart.getTotalQuantity() >= promotion.getBaseQuantity();
        }
        return false;
    }
}

절차적인 방식은 데이터에 타입이 추가 되더라도 데이터를 구현하고 있는 Promotion 클래스와 이 데이터를 사용해서 로직을 처리하는 프로모션 프로세스가 무조건 같이 변경된다.

객체지향 설계 - 새로운 할인 조건 추가

객체지향 설계에서는 새로운 타입, 즉 동일한 일을 하지만 구현 방법이 다른 여러가지 타입을 구현하기 위해서는 다형성을 이용

가격을 통해 할인 여부를 판단하는 조건과 수량을 통해 할인 여부를 판단하는 조건이 프로모션 클래스 입장에서는 동일

둘다 할인이 가능해? 라는 판단

인터페이스 추가

DiscountCondition - 할인 가능 여부 추상화

public interface DiscountCondition {
    boolean isApplicableTo(Cart cart);
}

Promotion

public class Promotion {

    private Cart cart;
    private DiscountCondition condition; // 추가

    public void apply(Cart cart) {
	    // 변경
        if (condition.isApplicableTo(cart)) {
            this.cart = cart;
        }
    }
}

PriceCondition

public class PriceCondition implements DiscountCondition {

    private Long basePrice;

    @Override
    public boolean isApplicableTo(Cart cart) {
        return cart.getTotalPrice() >= basePrice;
    }
}

기존 코드에서는 Promotion 클래스 안에 basePrice 속성이 존재하였지만 수정된 코드에서는 basePrice가 다른 클래스로 이동했다.

객체지향적인 설계에서는 어떤 로직을 처리하려고 했을 때 그 로직에 사용되는 데이터들이 적합한 클래스로 분배가되고 결과적으로 분산이 된다.

QuantityCondition

public class QuantityCondition implements DiscountCondition {

    private int baseQuantity;

    @Override
    public boolean isApplicableTo(Cart cart) {
        return cart.getTotalQuantity() >= baseQuantity;
    }
}

새로운 할인 조건 추가에서의 객체지향, 절차지향 정리

절차지향 설계에 비해 객체지향 설계는 굉장히 큰 리팩토링이 필요하다는 것을 알 수 있다.

이러한 경우에는 절차지향 설계가 더욱 편리하다고 생각할 수 있다.

하지만 객체지향 설계에서는 만약 이 이후에 가격, 수량 이 외에 할인여부를 체크하는 로직이 추가된다고 해도 기존의 코드를 수정할 필요없이 새로운 클래스만 추가해주면 된다는 장점이 있다. 반면에 절차적인 설계는 내부의 코드를 무조건 수정해주어야 한다.

객체지향 설계는 할인 조건을 계속해서 확장하고 싶다면 DiscountCondition이라고 하는 인터페이스를 구현하도록 강제한다. 설계가 발전하고 진화할수록 동일한 구조로 계속 일관된 구조를 가지며 코드의 사이즈가 커질 수 있다는 점은 객체지향 설계의 가장 큰 장점이다.

반면에 절차지향적인 설계는 일관된 구조를 강제하기 어렵다.

쉽게 말해 객체지향적인 설계는 일관성 있는 설계가 가능하여 코드의 사이즈가 커지더라도 코드의 복잡성을 줄일 수 있다.

그렇다면 객체지향적인 설계가 무조건 절차지향적인 설계보다 좋을까?

세 번째 요구사항 변경 - 새로운 기능 추가

장바구니 항목을 이용해서 할인 여부를 판단하는 기능 추가
장바구니에 있는 특정한 상품 하나가 그 조건을 만족하면 할인을 적용할 수 있는 로직 추가

CartLineItem 금액 ≥ Promotion 기준 금액

*CartLineItem = 장바구니에 있는 특정한 상품 하나

왼쪽의 금액만 가지고 할인 여부를 판단했던 구조와 오른쪽의 금액과 수량을 가지고 할인여부를 판단하기 위해 인터페이스를 추가해서 코드를 리팩토링 했을 때의 변경의 영향이 다르다.

이 두가지를 가지고 비교해보자.

절차적인 설계 - 새로운 기능 추가 (금액만 가지고 할인 여부를 판단했던 구조)

Promotion

public class Promotion {

    private Long cartId;
    private Long basePrice;

    public Long getBasePrice() {
        return basePrice;
    }

    public void setCart(Long cartId) {
        this.cartId = cartId;
    }
}

PromotionProcess

public class PromotionProcess {

    public void apply(Promotion promotion, Cart cart) {
        if (isApplicableTo(promotion, cart)) {
            promotion.setCart(cart.getId());
        }
    }

    private boolean isApplicableTo(Promotion promotion, Cart cart) {
        return cart.getTotalPrice() >= promotion.getBasePrice();
    }
	
    // 추가
    public boolean isApplicableTo(Promotion promotion, CartLineItem item) {
        return item.getPrice() >= promotion.getBasePrice();
    }
}

단순히 장바구니 항목을 가지고 할인여부를 판단하는 메서드를 추가하면 된다.

절차지향 설계에서는 PromotionProcess의 메서드만 수정하면 된다.

객체지향 설계 - 새로운 기능 추가 (금액만 가지고 할인 여부를 판단했던 구조)

Cart

public class Cart {

    private List<CartLineItem> items = new ArrayList<>();

    public Long getTotalPrice() {
        return items.stream().mapToLong(CartLineItem::getPrice).sum();
    }

    public int getTotalQuantity() {
        return items.stream().mapToInt(CartLineItem::getQuantity).sum();
    }
}

Promotion

public class Promotion {

    private Cart cart;
    private Long basePrice;

    public void apply(Cart cart) {
        if (cart.getTotalPrice() >= basePrice) {
            this.cart = cart;
        }
    }
    
    // 추가
    public boolean isApplicableTo(CartLineItem item) {
        return item.getPrice() >= basePrice;
    }
}

객체지향의 경우 Promotion 안에 있는 새로운 메서드를 추가하면 된다.

즉, 절차적인 방식이나 객체지향적인 방식이나 영향도가 똑같다.

절차적인 설계 - 새로운 기능 추가 (금액, 수량을 가지고 할인 여부를 판단했던 구조)

Promotion

public class Promotion {
    public enum ConditionType {
        PRICE, QUANTITY
    }

    private ConditionType conditionType;

    private Long cartId;
    private Long basePrice;
    private int baseQuantity;

    public Long getBasePrice() {
        return basePrice;
    }

    public ConditionType getConditionType() {
        return conditionType;
    }

    public int getBaseQuantity() {
        return baseQuantity;
    }

    public void setCart(Long cartId) {
        this.cartId = cartId;
    }
}

PromotionProcess

public class PromotionProcess {

    public void apply(Promotion promotion, Cart cart) {

        if (isApplicableTo(promotion, cart)) {
            promotion.setCart(cart.getId());
        }
    }

    private boolean isApplicableTo(Promotion promotion, Cart cart) {
        switch (promotion.getConditionType()) {
            case PRICE:
                return cart.getTotalPrice() >= promotion.getBasePrice();
            case QUANTITY:
                return cart.getTotalQuantity() >= promotion.getBaseQuantity();
        }
        return false;
    }

	// 추가
    public boolean isApplicableTo(Promotion promotion, CartLineItem item) {
        switch (promotion.getConditionType()) {
            case PRICE:
                return item.getPrice() >= promotion.getBasePrice();
            case QUANTITY:
                return item.getQuantity() >= promotion.getBaseQuantity();
        }
        return false;
    }
}

금액, 수량을 가지고 할인 여부를 판단했던 구조에서 장바구니에 있는 특정한 상품 하나가 그 조건을 만족하면 할인을 적용할 수 있는 로직 추가할 경우에도 동일하게 메서드 하나만 추가하면 된다.

즉, 절차적인 설계에서는 새로운 기능을 추가할 때 단순히 메서드 하나만 추가하면 된다.

대신, 여기에 새로운 할인 타입이 추가되면 설계가 굉장히 어려워지게 된다. 왜냐하면 여러군데에 있는 케이스문을 모두 수정해주어야 하기 때문이다.

하지만 새로운 할인 타입이 추가되지않고 단순히 기능만 추가한다고 하면 위의 절차적인 설계는 충분히 좋은 설계라고 말할 수 있다.

객체지향적인 설계 - 새로운 기능 추가 (금액, 수량을 가지고 할인 여부를 판단했던 구조)

DiscountCondition

public interface DiscountCondition {

    boolean isApplicableTo(Cart cart);
    boolean isApplicableTo(CartLineItem item); // 추가
}

인터페이스에 새로운 오퍼레이션 추가

PriceCondition

public class PriceCondition implements DiscountCondition {

    private Long basePrice;

    @Override
    public boolean isApplicableTo(Cart cart) {
        return cart.getTotalPrice() >= basePrice;
    }

	// 추가
    @Override
    public boolean isApplicableTo(CartLineItem item) {
        return item.getPrice() >= basePrice;
    }
}

QuantityCondition

public class QuantityCondition implements DiscountCondition {

    private int baseQuantity;

    @Override
    public boolean isApplicableTo(Cart cart) {
        return cart.getTotalQuantity() >= baseQuantity;
    }

	// 추가
    @Override
    public boolean isApplicableTo(CartLineItem item) {
        return item.getQuantity() >= baseQuantity;
    }
}

인터페이스의 오퍼레이션을 추가했으므로 그 구현체인 PriceConditionQuantityCondition의 코드의 수정이 필요하다.

객체지향적인 설계는 다형성을 사용해서 클래스의 계층 구조를 구현해놨을 때

타입이 확장되는 데는 굉장히 유리하지만,

그 타입에 새로운 오퍼레이션이 추가되면 전체적으로 코드의 수정이 일어난다.

이런 경우에 절차적인 설계가 객체지향적인 설계보다 더 좋다고 판단하면 된다.

정리

현재 변경하는 것들이 타입이 확장되는 경우가 더 많은 지 아니면 타입은 거의 고정이고 그 타입의 종류를 사용하는 기능이 추가되는 경우가 많은 지에 따라서 절차적인 설계가 좋을 수 있고 객체지향적인 설계가 좋을 수 있다.

데이터의 변환

지금까지는 시스템의 상태 변경에 대해서만 이야기를 하였다.

이번에는 데이터를 변환하는 것에 상황에 대해 이야기해보자.

입력: Cart, Promotion

출력: CartWithPromotion

CartPromotion이라고 하는 입력데이터를 잘 조합해서 시스템에 넣어둔 후 두개의 데이터를 가공하여 CartWithPromotion이라는 새로운 데이터를 출력해주는 로직을 새로 구현한다고 가정해보자.

절차적인 설계 - 데이터 변환

Promotion

public class Promotion {
    public enum ConditionType {
        PRICE, QUANTITY
    }

    private ConditionType conditionType;

    private Long cartId;
    private Long basePrice;
    private int baseQuantity;
    
    // Getter, Setter ...
}

PromotionProcess

public class PromotionProcess {

	// 추가
    public CartWithPromotion convertCartWithPromotion(Promotion promotion, 
                                                      Cart cart) {
        CartWithPromotion result = new CartWithPromotion();

        result.setTotalPrice(cart.getTotalPrice());
        result.setTotalQuantity(cart.getTotalQuantity());
        result.setPromotionBasePrice(promotion.getBasePrice());
        result.setPromotionBaseQuantity(promotion.getBaseQuantity());

        return result;
    }
}

절차적인 설계에서 데이터의 변환은 굉장히 쉽다.

왜냐하면 절차적인 설계에서는 이미 데이터가 분리되어 있다. 또한 데이터는 노골적으로 이 데이터를 가져다가 쓰세요 라고 자기의 타입을 제공해준다. 따라서 PromotionProcess에서는 CartWithPromotion 객체를 생성하여 거기에 데이터에 있는 것들을 빼서 넣어주면 된다.

이렇게 되면 굉장히 심플하게 데이터의 변환이 된다.

객체지향 설계 - 데이터 변환

객체지향의 경우에는 굉장히 어렵다.

Promotion

public class Promotion {

    private Cart cart;
    private DiscountCondition condition;
	
    // 추가
    public CartWithPromotion convertCartWithPromotion() {
        CartWithPromotion result = new CartWithPromotion();

        result.setTotalPrice(cart.getTotalPrice());
        result.setTotalQuantity(cart.getTotalQuantity());

        if (condition instanceof PriceCondition) {
            result.setPromotionBasePrice(
                ((PriceCondition)condition).getBasePrice());
        }

        if (condition instanceof QuantityCondition) {
            result.setPromotionBaseQuantity(
                ((QuantityCondition)condition).getBaseQuantity());
        }

        return result;
    }
}

객체지향은 동일한 할인 여부를 판단하는 여러 개의 타입들이 있다. 따라서 각각의 타입들마다 사용하는 데이터들이 개별적인 클래스로 분배가 된다.

PriceCondition

public class PriceCondition implements DiscountCondition {

    private Long basePrice;
}

기준 금액은 PriceCondition 클래스에 들어있다.

QuantityCondition

public class QuantityCondition implements DiscountCondition {

    private int baseQuantity;
}

기준 수량은 QuantityCondition 클래스에 들어있다.

객체지향적인 설계에서 데이터를 변환하기 위해서는 여러가지 타입에서 그 타입에 적합한 데이터를 뿌려주어야 한다. 따라서 현재의 객체가 어떤 타입인지를 판단하여 적절한 타입으로 타입 캐스팅한 후 필요한 데이터를 끄집어내는 로직이 필요하다.

절차적인 설계가 유리한 경우

타입이 확장이 되지 않거나 그 기능이 추가가 되고 그 데이터를 처리하는 로직인 경우에는 객체지향적인 설계보다 절차적인 설계가 훨씬 좋다.

절차적인 설계 vs 객체지향 설계

절차적인 설계

  • 포맷 변경을 위한 데이터 변환
  • 데이터 중심
  • 데이터 노출
  • 기능 추가에 유리

객체지향 설계

  • 규칙에 기반한 상태 변경
  • 행동 중심
  • 데이터 캡슐화
  • 타입 확장에 유리

객체지향 설계는 데이터의 응집도 단위로 클래스를 분해한게 아니라 행위의 응집도 단위로 클래스를 분리했기 때문에 데이터 변환이 필요할 때 instanceof가 필요할 수 밖에 없다.

절차적인 설계는 같이 돌아다니는 데이터를 한 군데로 모은다. 따라서 데이터 중심적인 설계에서는 절차지향적인 설계가 좋다. 반면 객체지향적인 설계는 데이터를 캡슐화한다.

레이어 아키텍처에서의 객체지향과 절차지향

객체지향적인 설계와 절차지향적인 설계를 어디에 적합한지를 판단하여 적절히 섞어 사용해야 한다.

레이어 아키텍처

레이어 아키텍처로 예를 들어보자.

도메인 레이어

규칙 기반의 상태 변경

도메인 레이어는 대부분의 경우 시스템의 상태 변경을 담당한다. 위의 예시의 모든 클래스들은 도메인 로직을 담당한다. 여기에서는 대부분의 경우 특별한 로직을 특별한 규칙을 기반으로 시스템의 상태 변경을 야기하는 로직이 도메인 레이어에 들어가게 된다.

도메인 로직의 가장 큰 목적은 시스템을 A라는 상태에서 B라는 상태로 변경하는 것이다. A라는 상태에서 B라는 상태로 바뀔 땐 대부분 특별한 룰들이 추가되게 된다. 또한 이것들은 비즈니스가 바뀔 때 마다 계속 급격하게 바뀌게 된다.

따라서 도메인 레이어는 객체지향적으로 짜는 것이 유리하다.

프레젠테이션 레이어

프리젠테이션 레이어는 데이터의 변환을 담당한다.

PromotionController

@RestController
public class PromotionController {

    private PromotionService promotionService;

    @PutMapping("/promotions/{promotionId}/apply/{cartId}")
    public PromotionResponse apply(@PathVariable("promotionId") Long promotionId, 
                                   @PathVariable("cartId") Long cartId) {
        Promotion promotion = promotionService.apply(promotionId, cartId);
        return new PromotionResponse(promotion);
    }
}

프리젠테이션 레이어가 하는 일은 대부분 외부의 불확실한 데이터를 변환하거나 검증하거나 외부에 아웃풋을 내보내는 일이다. 따라서 프리젠테이션 레이어는 절차적으로 짜는 것이 유리하다.

서비스 레이어

서비스 레이어는 애플리케이션의 플로우를 처리한다. 말 그대로 절차이다.

PromotionService

@Service
public class PromotionService {

    private PromotionRepository promotionRepository;
    private CartRepository cartRepository;

    public Promotion apply(Long promotionId, Long cartId) {

        Promotion promotion = promotionRepository.findById(promotionId);
        Cart cart = cartRepository.findById(cartId);

        promotion.apply(cart);

        return promotion;
    }
}

DB에서 데이터를 읽고 그 객체가 어떤 일을 하라고 요청을 보내고 그 결과를 통해 response를 만든다. 어떤 특정한 알고리즘에 따라 어떤 일을 해야 됩니다 라고 하는 룰을 순서대로 정의한다.

따라서 서비스 레이어도 절차적으로 짜고 있다.

퍼시스턴스 레이어

퍼시스턴스 레이어도 마찬가지로 절차적으로 짜고 있다.

PromotionRepository

@Repository
public class PromotionRepository {

    private JdbcClient jdbcClient;

    public Promotion findById(Long promotionId) {
        jdbcClient.sql(
            "select from promotion where id = :promotionId")
            .param("promotionId", promotionId)
            .query((rs, rowNum) -> new Promotion())
            .single();
    }
}

퍼시스턴스 레이어는 DB에 있는 데이터를 읽어서 객체로 변환하거나 데이터베이스에 저장하거나 업데이트 해야할 객체를 데이터에 SQL이나 또는 특정한 형태로 변환하는게 퍼시스턴스 레이어의 역할이다.

따라서 우리는 퍼시스턴스 레이어도 절차적으로 코드를 작성하고 있다.

결론

즉, 이미 우리는 객체지향적인 설계와 절차적인 설계를 혼합하여 사용하고 있다.

만약 데이터를 조회하는 로직이 있다고 하면 여기는 객체지향적인 설계가 낄 여지가 전혀 없다. DB에서 데이터를 읽어와서 그냥 화면에 보내는 로직은 어떠한 룰이 전혀 필요없기 때문이다. 레이어를 오고가는 데이터는 모든 레이어가 다 공유한다.

다시말해, 실제로 애플리케이션을 짤 때 객체지향적으로 짜는 비율보다 절차지향적으로 짜는 비율이 훨씬 많다.

객체지향은 여전히 유용한가요?

라는 질문 보다는

객체지향은 언제 유용한가요?

라는 질문이 더욱 올바른 질문이다.

만약 객체지향이 유용하지 않다고 생각하는 느낀다면, 현재 작업하는 영역 또는 기능이 데이터를 처리하는 것이거나 룰이 급격하게 변하지 않는 경우일 가능성이 높다.

시간이 지나서 그 영역이 굉장히 복잡해지고 룰들이 추가되어야 한다면 그때는 객체지향을 적용하는 것이 좋다.

profile
백엔드 개발자

0개의 댓글