스프링

스프링 핵심 원리 기본

Rudtjs 2022. 7. 6. 20:35

김영한 선생님 강의 자료

토이 프로젝트를 시작하기에 앞서 스프링을 써야 하는 이유와 정의, 객체지향 프로그래밍을 자세하게 알아보고 시작하려고 합니다.

 

스프링의 정의

스프링은 자바 언어 기반의 프레임워크로 좋은 객체 지향 애플리케이션을 개발할 수 있게 도와준다. 

 

프레임워크 : 어떤 프로그램을 만들기 위한 기본 틀(기능 확장 가능, 자체 수정 불가능)

 

객체 지향 프로그래밍(OOP)

사람들이 자바를 많이 쓰는 이유는 자바가 객체 지향 언어이기 때문이다. 그렇다면 이 객체 지향이란 건 뭘 뜻하는 걸까? 객체 지향 프로그래밍은 필요한 데이터를 추상화시켜 상태와 행위를 가진 객체를 만들고 그 객체들 간의 유기적인 상호작용을 통해 로직을 구상하는 방법이다. 

 

객체 지향 프로그래밍 키워드 5가지

  • 클래스 + 인스턴스(객체)
  • 추상화
  • 캡슐화
  • 상속
  • 다양화

클래스와 인스턴스

클래스는 어떤 문제를 해결하기 위한 데이터를 만들기 위해 추상화를 거쳐 속성과 행위를 변수와 메서드로 정의한 것 객체를 구현하기 위한 설계도이다.

객체: 클래스에서 정의한 것을 토대로 소프트웨어상에서 사용되는 데이터를 말합니다.

 

추상화

추상화는 공통의 속성이나 기능을 묶어 이름을 붙이는 것이다. 객체 지향적 관점에서 클래스를 정의하는 것을 추상화라고 할 수 있다.

 

캡슐화

  • 코드를 재수정 없이 재활용
  • 접근 제어자(public, private)를 통한 은닉

 

공통 메서드를 하나 작성하고 이걸 다양한곳에서 이용했다고 가정해보자. 그리고 이 공통 메서드를 수정한다고 치면 무슨 일이 일어날까? 이 메서드를 사용한 곳의 영향 범위를 예상하기 어려울 것이다. 그러나 객체 지향 프로그래밍에서는 접근 제어자를 통해 객체가 외부로 노출하는 것을 막을 수 있고, 코드의 수정이 일어났을 때 책임이 있는 객체만 수정하면 되기에 영향 범위를 예측하기 쉬워졌다

 

상속

상속은 기존의 클래스에 기능을 추가하거나 부모 클래스의 기능과 속성을 이어 받은 자식 클래스를 재정의 하는것을 말합니다. 

 

다향성 

하나의 변수명, 함수명이 상황에 따라 다른 의미로 해석될 수 있습니다.

  • 오버라이딩: 부모 클래스의 메서드와 같은 이름
  • 오버로딩: 같은 이름의 함수를 여러 개 정의하고, 매개변수의 타입과 개수를 다르게 하여 매개변수에 따라 다르게 호출할 수 있게 하는 것

김영한 선생님 pdf 자료

좋은 객체 지향 설계 5가지

SRP 단일 책임 원칙(Single responsibility principle)

  • 한 클래스하나의 책임만 가져야 한다.
  • 변경이 있을때 변화가 적어야 원칙을 잘 따른 것이다.

OCP 개방-폐쇄 원칙(open/closed principle)

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

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

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

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

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

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

  • 프로그래머는 "추상화에 의존해야지, 구체화에 의존하면 안 된다.
  • 구현 클래스에 의존하는 것 아닌, 인터페이스에 의존하라는 뜻

좋은 객체 지향을 목표로 하기 위해서는 이렇게 5가지를 지켜야 하는데 프로젝트를 하나 만들고 하나씩 적용해보도록 하겠습니다.

 

스프링 프로젝트

 

회원,상품 클래스 다이어그램

위에 사진에 나온 대로 만들고 TEST코드로 실행시켜 보겠습니다. 다른 클래스 코드들은 깃허브에서 확인해주시면 감사합니다.

// 할인 인터페이스
public interface DiscountPolicy {
 	int discount(Member member, int price);
}

// 정액 할인 정책 구현체
public class FixDiscountPolicy implements DiscountPolicy {

 private int discountFixAmount = 1000; //1000원 할인
 
 @Override
 public int discount(Member member, int price) {
 	if (member.getGrade() == Grade.VIP) {
 		return discountFixAmount;
	 } else {
		 return 0;
 	 }
   }
}
// 주문 서비스 인터페이스
public interface OrderService {
 Order createOrder(Long memberId, String itemName, int itemPrice);
}

// 주문 서비스 구현체
public class OrderServiceImpl implements OrderService {

 private final MemberRepository memberRepository = new MemoryMemberRepository();
 private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
 
 @Override
 public Order createOrder(Long memberId, String itemName, int itemPrice) {
 	Member member = memberRepository.findById(memberId);
 	int discountPrice = discountPolicy.discount(member, itemPrice);
    
 	return new Order(memberId, itemName, itemPrice, discountPrice);
  }
}

이런 식으로 어떤 물건을 사도 천원이 할인되는 코드를 짰다고 치고 실행해보면 

할인 금액이 잘 출력되는 것을 확인할 수 있다. 하지만 여기서 고정 금액 할인이 아닌 10% 할인 코드를 추가하면 어떻게 될까?

public class RateDiscountPolicy implements DiscountPolicy {

	 private int discountPercent = 10; //10% 할인
     
 	@Override
 	public int discount(Member member, int price) {
    
 		if (member.getGrade() == Grade.VIP) {
 		   return price * discountPercent / 100;
 	    } else {
 		   return 0;
	 }
   }
}

10%가 할인되게 하는 RateDiscountPlicy를 만들고 OrderServiceImpl 클래스에서 변경시켜주겠습니다. 객체지향이 잘 지켜졌는지 확인해보자. 

 

  1. OrderServiceImp는 DiscountPolicy 인터페이스에 의존하면서 DIP를 지킨 것 같지만 클래스 안을 보면 구현 클래스에도 의존하고 있어 DIP를 위반한다.
  2. 지금 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 줘서 OCP를 위반하게 된다.

 

해결 방법

public class OrderServiceImpl implements OrderService {
	private DiscountPolicy discountPolicy;
 }

인터 페이스에만 의존하도록 변경하고 생성자를 주입해주어야 잘 작동한다. 

 

AppConfig

애플리케이션의 사용 영역과  구현 객체를 생성하고 연결까지 해주는 클래스이다. 

public class AppConfig {
 	public MemberService memberService() {
		return new MemberServiceImpl(memberRepository());
	    }
        
 	public OrderService orderService() {
 		return new OrderServiceImpl(
			 memberRepository(),
			 discountPolicy());
 		}
        
	 public MemberRepository memberRepository() {
		 return new MemoryMemberRepository();
		}
        
	 public DiscountPolicy discountPolicy() {
 		return new FixDiscountPolicy();
		}
}

AppConfig는 구현 객체를 생성하고 생성자를 주입받는다.  MemberServiceImpl는 MemoryMemberRepository, OrderServiceImpl는 MemoryMemberRepository와 FixDiscountPolicy를 주입받는다. 

 

김영한 선생님 강의 PDF 자료

 

AppConfig가 객체를 생성하고 연결을 해주면 MemberServiceImpl은 MemberRepository인 추상에만 의존하면 되기 때문에 DIP를 지킬 수 있게 된다. 지금까지 순수 자바 코드로만 짜봤는데 다음 글에서는 스프링을 사용해서 AppConfig에 적용시켜 보겠습니다.

 

 

출처

김영한 선생님 스프링 강의