728x90

인프런 김영한님의 강의를 참고했습니다.

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

 

스프링 프레임워크는 최근에 스프링을 편리하게 사용할 수 있도록 지원해주는 스프링 부트의 등장으로 더 쉽게 사용할 수 있게 되었고 요즘은 이 스프링 부트를 사용하는 것이 기본이 되었다.

 

스프링은 객체 지향 언어를 따라 개발할 수 있도록 도와주는 프레임 워크이다.

 

여기서 나오는 객체 지향 언어를 따른 다는 것은 좋은 객체 지향 프로그램을 만든 다는 것인데

 

일단 객체 지향 언어의 특징으로는 

추상화

캡슐화

상속

다형성

이 있고 이것들은 자바의 기초만 배워도 알 수 있는 내용들이다.

 

이 특징들을 이용한 좋은 객체 지향 설계의 5가지 원칙이 있다.

줄여서 SOLID라고 하고

SRP: 단일 책임 원칙(single responsibility principle)

한 클래스는 하나의 책임만 가져야 한다.

OCP: 개방-폐쇄 원칙(Open/Closed principle)

소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.

LSP: 리스코프 치환 원칙(Liskov subsititution principle)

프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.

ISP: 인터페이스 분리 원칙(Interface segregation principle)

특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.

DIP: 의존관계 역전 원칙(Dependency inversion principle)

프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다.

가 있다.

 

이 원칙들에 의해 한 번 프로그램을 만들어보자.

start.spring.io에서 Dependencies를 모두 제외하고 생성을 해주었다.

intellij로 프로젝트를 열고 우리가 만들 프로그램의 요구사항을 정리하고 설계하자.

 

비즈니스 요구사항으로는

회원

회원을 가입하고 조회할 수 있다.

등급은 일반과 VIP, VVIP로 3가지 등급이 있다.

회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.

주문과 할인 정책

회원은 상품을 주문할 수 있다.

회원 등급에 따라 할인 정책이 다르게 적용이 된다.

VIP는 500원, VVIP는 1000원을 할인해주는 고정 금액 할인이 적용된다.

할인 정책은 변경될 가능성이 높다.

 

할인 정책은 인터페이스로 구현을 하고 언제든지 갈아끼울 수 있도록 설계하면 된다.

 

고객 도메인 관계

 

클래스 다이어그램

 

고객 등급은 BASIC, VIP, VVIP 3가지가 있기에 Customer 패키지에 enum으로 만들어준다.

package hello.core.customer;

public enum Grade {
    BASIC,
    VIP,
    VVIP
}

 

고객 class를 만들자.

package hello.core.customer;

public class Customer {

    private Long id;
    private String name;
    private Grade grade;

    public Customer(Long id, String name, Grade grade) {
        this.id = id;
        this.name = name;
        this.grade = grade;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Grade getGrade() {
        return grade;
    }

    public void setGrade(Grade grade) {
        this.grade = grade;
    }
}

고객의 멤버에는 id, name, grade가 있을 것이며 생성자를 통해 넣어주고 Getter와 Setter를 만들어준다.

고객 객체에 대해 만들었으니 이제 이 객체를 저장할 저장소에 관하여 만들어보자.

 

아직 저장소가 선정되지 않았으니 인터페이스로 구현을 하고 Memory에 간단하게 저장할 수 있게 만든다.

package hello.core.customer;

public interface CustomerRepository {
    void save(Customer customer);

    Customer findById(Long id);
}

간단하게 이 인터페이스를 상속받아 메모리에 저장하는 리포지토리를 만들어둔다.

 

package hello.core.customer;

import java.util.HashMap;
import java.util.Map;

public class MemoryCustomerRepository implements CustomerRepository{

    private static Map<Long, Customer> store = new HashMap<>();

    @Override
    public void save(Customer customer) {
        store.put(customer.getId(), customer);
    }

    @Override
    public Customer findById(Long id) {
        return store.get(id);
    }
}

 

이 저장소를 사용하는 회원 서비스이다.

마찬가지로 인터페이스를 만들고 해당 인터페이스를 상속 받도록 만든다.

package hello.core.customer;

public interface CustomerService {
    void join(Customer customer);

    Customer findCustomer(Long id);
}

 

package hello.core.customer;

public class CustomerServiceImpl implements CustomerService{

    private final CustomerRepository customerRepository = new MemoryCustomerRepository();

    @Override
    public void join(Customer customer) {
        customerRepository.save(customer);
    }

    @Override
    public Customer findCustomer(Long id) {
        return customerRepository.findById(id);
    }
}

 

일단 이렇게 회원 가입은 만들었으니, Junit을 통해 테스트를 해보자.

package hello.core.customer;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class CustomerServiceTest {

    CustomerService customerService = new CustomerServiceImpl();

    @Test
    void join(){
        //given
        Customer customer = new Customer(1L, "Seungkyu", Grade.VIP);

        //when
        customerService.join(customer);
        Customer findCustomer = customerService.findCustomer(1L);

        //then
        Assertions.assertEquals(customer, findCustomer);
    }
}

이렇게 테스트를 작성하고 Run 해보면

녹색불이 정상적으로 들어오는 것을 볼 수 있다.

 

회원가입은 구현을 했으니 이번에는 주문에 관련한 설계이다.

주문 도메인이다.

당연히 각 역할은 인터페이스로 먼저 구현을 할 예정이다.

 

그렇게 만든 클래스 다이어그램이다.

할인 정책에 관한 인터페이스이다.

package hello.core.Discount;

import hello.core.customer.Customer;

public interface DiscountPolicy {

    //return 이 할인 대상 금액
    int discount(Customer customer, int price);
}

 

일단 1000원, 500원으로 정해진 금액만 할인해 주고 있기 때문에

FixDiscountPolicy를 만든다.

package hello.core.Discount;

import hello.core.customer.Customer;
import hello.core.customer.Grade;

public class FixDiscountPolicy implements DiscountPolicy{

    private int VIPDiscountFixAmount = 500; // 할인되는 금액
    private int VVIPDiscountFixAmount = 1000; //할인되는 금액

    @Override
    public int discount(Customer customer, int price){
        if(customer.getGrade() == Grade.VVIP){
            return VVIPDiscountFixAmount;
        }
        else if(customer.getGrade() == Grade.VIP){
            return VIPDiscountFixAmount;
        }
        else{
            return 0;
        }
    }
}

 할인 정책에 관한 부분을 모두 만들었다.

 

이번엔 주문에 관련된 부분이다.

일단 주문 결과를 주문 class로 넘겨주어야 하기 때문에 주문 class를 만든다.

주문 클래스의 멤버로는 주문한 고객의 id, 주문한 상품의 이름, 가격, 할인된 가격이 포함된다.

package hello.core.order;

public class Order {

    private Long id;
    private String itemName;
    private int itemPrice;
    private int discountPrice;

    public Order(Long id, String itemName, int itemPrice, int discountPrice) {
        this.id = id;
        this.itemName = itemName;
        this.itemPrice = itemPrice;
        this.discountPrice = discountPrice;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getItemName() {
        return itemName;
    }

    public void setItemName(String itemName) {
        this.itemName = itemName;
    }

    public int getItemPrice() {
        return itemPrice;
    }

    public void setItemPrice(int itemPrice) {
        this.itemPrice = itemPrice;
    }

    public int getDiscountPrice() {
        return discountPrice;
    }

    public void setDiscountPrice(int discountPrice) {
        this.discountPrice = discountPrice;
    }

    @Override
    public String toString() {
        return "Order{" +
                "id=" + id +
                ", itemName='" + itemName + '\'' +
                ", itemPrice=" + itemPrice +
                ", discountPrice=" + discountPrice +
                '}';
    }
}

 

이제 주문을 만들어주는 주문과 관련된 인터페이스와 그 인터페이스를 상속받는 class이다.

package hello.core.order;

public interface OrderService {

    Order createOrder(Long id, String itemName, int itemPrice);

}
package hello.core.order;

import hello.core.Discount.DiscountPolicy;
import hello.core.Discount.FixDiscountPolicy;
import hello.core.customer.Customer;
import hello.core.customer.CustomerRepository;
import hello.core.customer.MemoryCustomerRepository;

public class OrderServiceImpl implements OrderService{

    private final CustomerRepository customerRepository = new MemoryCustomerRepository();
    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();

    @Override
    public Order createOrder(Long id, String itemName, int itemPrice) {
        Customer customer = customerRepository.findById(id);
        int discountPrice = discountPolicy.discount(customer, itemPrice);

        return new Order(id, itemName, itemPrice, discountPrice);
    }
}

 

당연히 모두 만들었으면 Test를 진행해본다.

package hello.core.order;

import hello.core.customer.Customer;
import hello.core.customer.CustomerService;
import hello.core.customer.CustomerServiceImpl;
import hello.core.customer.Grade;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class OrderServiceTest {

    CustomerService customerService = new CustomerServiceImpl();
    OrderService orderService = new OrderServiceImpl();

    @Test
    void createOrder(){
        Long id = 1L;
        Customer customer = new Customer(id, "Seungkyu", Grade.VIP);
        customerService.join(customer);

        Order order = orderService.createOrder(id, "Latte", 10000);
        Assertions.assertEquals(500, order.getDiscountPrice());
    }
}

 이렇게 VIP등급에 해당하는 고객은 500원이 할인되어야 하고 

하지만 이렇게 만든 프로그램은 좋은 객체 지향 프로그램이라고 할 수 없다.

그렇기에 다음 시간에 좋은 객체 지향 프로그램으로 바꾸어 보도록 하자.

'백엔드 > 스프링' 카테고리의 다른 글

스프링 7일차  (0) 2023.02.07
스프링 6일차  (0) 2023.02.06
스프링 4일차  (0) 2023.02.03
스프링 3일차  (0) 2023.02.01
스프링 2일차  (0) 2023.01.18

+ Recent posts