스프링 데이터 : 데이터베이스를 사용하는 데 도움을 주는 기술 (가장 많이 쓰는게 Spring Data JPA)
스프링 세션 : 세션 기능을 편리하게 사용할 수 있도록 도움을 주는 기술
스프링 시큐리티 : 보안
스프링 Rest Docs : API 문서와 테스트를 엮어 편리하게 도움을 주는 기술
스프링 배치 : 배치 처리에 특화된 기술
스프링 클라우드 : 클라우드에 특화된 기술
등등
☘️ 스프링 프레임 워크
핵심 기술: 스프링 DI 컨테이너, AOP, 이벤트, 기타 (강의의 초점!!)
웹 기술: 스프링 MVC, 스프링 WebFlux
데이터 접근 기술: 트랜잭션, JDBC, ORM 기원, XML 지원
기술 통합: 캐시, 이메일, 원격접근, 스케쥴링
테스트: 스프링 기반 테스트 지원
언어: 코틀린, 그루비
최근에는 스프링 부트를 통해서 스프링 프레임원크의 기술들을 편리하게 사용
☘️스프링 부트
스프링을 편리하게 사용할 수 있도록 지원, 최근에는 기본으로 사용
단독으로 실행할 수 있는 스프링 애플리케이션을 쉽게 생성
Tomcat 같은 웹 서버를 내장해서 별도의 웹 서버를 설치하지 않아도 됨
손쉬운 빌드 구성을 위한 starter 종속성 제공
스프링과 3rd party(외부) 라이브러리 자동 구성, 의존성 버전 관리
메트릭, 상태 확인, 외부 구성 같은 프로덕션 준비 기능 제공 (모니터링 제공)
관례에 의한 간결한 설정
스프링 부트는 스프링과 별도로 사용가능한 것이 아니다! 스프링 프레임워크 위에서 편리하게 기능을 사용할 수 있도록 돕는다!
☘️스프링 단어?
스프링이라는 단어는 문맥에 따라 다르게 사용된다.
스프링 DI 컨테이너 기술
스프링 프레임워크
스프링 부트, 스프링 프레임워크 등을 모두 포함한 스프링 생태계
☘️스프링 왜 만들었나요?
핵심 개념: 이 기술을 왜 만들었는가? 이 기술의 핵심 컨셉은? (로드존슨이랑 유겐 힐러가 왜 만들었을까!?!?!)
❓웹 애플리케이션 만들고, DB 접근 편리하게 해주는 기술?
❓전자정부 프레임워크?
❓웹 서버도 자동으로 띄워주고?
❓클라우드, 마이크로 서비스?
인걸까?? ❌ 이건 다 결과물일 뿐이다
* 스프링의 진짜 핵심
스프링은 자바 언어 기반의 프레임워크
자바 언어의 가장 큰 특징 - 객체 지향 언어
스프링은 객체 지향 언어가 가진 강력한 특징을 살려내는 프레임워크
스프링은 좋은 객체 지향 애플리케이션을 개발할 수 있게 도와주는 프레임워크
사람들이 스프링 등장에 열광했던 건 스프링 이전의 EJB는 객체 지향의 장점을 망치는 기술이었기 때문에 이걸 살릴 수 있었던 기술에 열광했던 거다!!
좋은 객체 지향 프로그래밍이란?
☘️객체 지향 특징
추상화
캡슐화
상속
다형성
☘️객체 지향 프로그래밍
객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다. 각자의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다. (협력)
혼자 있는 객체는 없다.
객체끼리 뿐만 아니라 수많은 객체 클라이언트와 객체 서버는 서로 협력 관계를 가진다. (요청과 응답하면서)
서버끼리도, 시스템끼리도!
객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다.
유연하고, 변경이 용이하다?
객체 단위로 개발하기 때문에
레고 블럭 조립, 키보드 마우스 교체, 컴퓨터 부품 교체 처럼
컴포넌트를 쉽고 유연하게 변경하면서 개발할 수 있는 방법
이런 궁극의 유연함을 가질 수 있게 하는 객체 지향의 핵심! 다형성(Polymorphism) 이다.
☘️다형성(Polymorphism)
자동차라는 역할이 존재하고 이 자동차 역할에 그 어떤 종류에 자동차가 구현(K3든, 아반떼든, 테슬라든) 되어도 운전자는 자동차를 운전할 수 있다. 운전자는 자동차 역할이라는 인터페이스에 의존하고 있기 때문에 구현이 어떤게 와도 상관없다.
이는 운전자를 위한 것이다.
자동차 내부 동작 방식이 바뀌어도 운전자에게는 영향이 없기 때문에 쓰던 방식 그대로 쓸 수 있다. 운전자는 새로 뭔가를 배울 필요가 없다. 또한 구현체를 바꿔버려도 운전이 그대로 가능하니까 자동차 역할만 지키면 새로운 구현체도 들어올 수 있어 확장성이 생긴다.
이게 가능한 것이 역할과 구현을 나눠놨기 때문이다. (연극에서 하나의 역할을 여러사람이 할 수 있는 것도)
☘️ 역할과 구현을 분리
역할과 구현으로 구분하면 세상이 단순해지고, 유연해지며 변경도 편리
장점
클라이언트는 대상의 역할(인터페이스)만 의존
클라이언트는 구현 내부 몰라도 된다. 변경도 몰라도 되며, 심지어 구현 대상 자체가 바뀌어도 영향 X
프로그래밍 언어에서도 이러한 장점을 받아들이게 된다.
☘️ 자바 언어에서는 이걸 어떻게 받아들였을 까?
자바 언어의 다형성을 활용한 방식은 객체 설계시 역할과 구현을 명확하게 분리했다.
객체 설계시 역할(인터페이스)을 먼저 부여하고, 그 역할을 수행하는 구현 객체 만드는 방식으로 차용
역할 = 인터페이스
구현 = 인터페이스를 구현한 클래스, 구현 객체
역할 > 구현. 역할이 더 중요하다
오버라이딩 된 메서드가 실행될 때 다형성으로 인터페이스를 구현한 객체를 실행 시점에 유연하게 변경 할 수 있다.
상속 관계에서도 다형성, 오버라이딩 적용 가능
☘️ 다형성의 본질
인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경 할 수 있다.
다형성의 본질을 이해하려면 협력이라는 객체사이의 관계에서 시작해야한다.
클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다.
☘️ 역할과 구현을 분리 : 정리
역할과 구현이라는 컨셉을 다형성을 통해 객체화 할 수 있다.
유연하고 변경이 용이
확장 가능
클라이언트에 영향을 주지 않는 변경이 가능
따라서, 인터페이스를 안정적으로 잘 설계하는 것이 중요하다.
☘️ 하지만 한계도 있다
역할(인터페이스)이 변하면 클라이언트, 서버 모두 변경이 발생한다. 그래서 더더욱 안정적인 인터페이스가 필요하다.
☘️ 스프링과 객체 지향
객체지향의 꽃은 다형성
스프링은 이런 다형성을 극대화해서 이용이 가능하게 한다.
스프링에서 말하는 제어의 역전(IoC), 의존관계 주입(DI)은 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원해주는 역할을 해준다!
그래서 결론! 스프링을 사용하면 블럭 조립하든 구현을 편리하게 변경할 수 있다!
다형성이 스프링에 굉장히 중요한데 여기서 한가지가 더 있어야 완전히 스프링을 설명할 수 있다.
그게 바로 좋은 객체 지향 설계의 5가지 원칙, 소위 SOLID 라고 불리는 원칙이다.
좋은 객체 지향 설계의 5가지 원칙 (SOLID)
☘️ SRP (단일 책임의 원칙 / Single reponsibility principle)
한 클래스는 하나의 책임만 가져야 한다는 원칙. 하지만 하나의 책임이라는 게 모호하고 문맥과 상황따라 다를 수 있는 거라 중요한건 변경을 기준으로 한다. 변경의 이유를 하나만 가질 것 변경의 이유가 여러가지가 생겼다면 한가지 이상의 책임을 맡고 있는 것이다. 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것이다.
☘️ OCP (개방-폐쇄 원칙 / Open-Closed principle)
소프트웨어 요소는 확장에는 열려있고, 변경에는 닫혀있어야 한다.
다형성을 활용해 이 원칙을 지킬수가 있다. 인터페이스를 구현한 새로운 클래스를 만들어 새로운 기능을 구현하면 확장에는 열려있다. 그리고 이 변화가 외부의 기존 코드에 변경을 주지 않는다. 변경에는 닫혀있는 것이다.
그런데, 개발 폐쇄 원칙은 문제점이 있다.
만약 MemberRepository m = new MemoryMemberRepository(); 를
MemberRepository m = new JdbcMemberRepository();로 구현 객체를 변경할 경우
클라이언트가 코드에서 변경을 해야한다. 다형성을 사용했으나 OCP 원칙을 지킬 수 없는데 이 문제를 해결하기 위해 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요하다.
이 역할을 스프링의 컨테이너가 해준다! 이 원칙을 지키기위해 IoC(제어의 역전)도 DI(의존성 주입)도 필요한 것!
프로그램 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
이것은 단순히 코드에 상위 타입이 하위타입의 인스턴스로 교체를 성공하여 컴파일이 성공하는 걸 의미하지 않는다.
상위 타입의 객체를 하위 타입의 객체로 치환했다고 하더라도 상위 타입으로 동작하던 기존 프로그램은 정상적으로 동작해야하는 원칙이다. 상위의 엑셀 기능이 하위 타입으로 교체했을 때 후진 기능이 되버리면 리스코프 치환 원칙에 위배된 것이다. 하위 클래스는 인터페이스 규약을 다 지켜야한다. 그래서 다형성을 지원하기 위해 필요한 원칙으로 인터페이스를 구현한 구현체를 믿고 사용하려면 이 원칙이 필요하다.
☘️ ISP (인터페이스 분리 원칙 / Interface segregation principle)
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
자동차 인터페이스 > 운전 인터페이스, 정비 인터페이스 로 분리
사용자 클라이언트 > 운전자 클라이언트, 정비사 클라이언트로 분리
이런식으로 분리하면 정비 인터페이스를 변경해도 정비사 클라이언트만 영향을 주지 운전자 클라이언트는 영향을 주지않는다. 기능을 적당한 크기로 자르는 것이 중요. 인터페이스(역할)이 명확해지고, 하나의 큰 역할이 아닌 보다 작은 역할들로 잘라낸 것이라 대체 가능성이 높아진다.
만약 A가 올바른 괄호 문자열이라면, (A), [A], {A} 도 올바른 괄호 문자열입니다. 예를 들어, [] 가 올바른 괄호 문자열이므로, ([]) 도 올바른 괄호 문자열입니다.
만약 A, B가 올바른 괄호 문자열이라면, AB 도 올바른 괄호 문자열입니다. 예를 들어, {} 와 ([]) 가 올바른 괄호 문자열이므로, {}([]) 도 올바른 괄호 문자열입니다.
대괄호, 중괄호, 그리고 소괄호로 이루어진 문자열 s가 매개변수로 주어집니다. 이 s를 왼쪽으로 x (0 ≤ x < (s의 길이)) 칸만큼 회전시켰을 때 s가 올바른 괄호 문자열이 되게 하는 x의 개수를 return 하도록 solution 함수를 완성해주세요.
📌제한 사항
s의 길이는 1 이상 1,000 이하입니다.
📌입출력 예
추측)
괄호짝을 배열로 만들어 준비한다.
문자열 s를 리스트로 만들어 맨앞에 것을 pop해 뒤로 append해주는 방식으로 회전을 시키고 문자열로 합쳐준다 (처음 회전이 필요없는 경우는 그냥)
그리고 그 문자열에서 괄호짝 배열 for문을 돌려 짝을 찾으면 삭제하는 방식을 반복한다.
대신 3개의 괄호짝이 없을 경우 각각 flag를 0으로 바꾸어주고 모두 0이 되면 카운팅 없이 나오기
text길이가 0이되면 모두 지워냈으므로 올바른 괄호문자열이라 카운팅한다
이를 반복해서 마지막에 카운트를 반환
소스코드)
* 1차) 실패 : 정확도 78.6
def solution(s):
answer = 0
bracket = ['()', '[]', '{}']
s_list = list(s)
for i in range(len(s)):
text = ""
if i != 0:
s_list.append(s_list.pop(0))
text = ''.join(one for one in s_list)
else :
text = s
flag_list = [1, 1, 1]
flag = True
while flag:
if len(text) == 0 :
answer += 1
break
if flag_list == [0, 0, 0]:
break
for x in range(len(bracket)) :
if bracket[x] in text:
text = text.replace(bracket[x], "")
else:
flag_list[x] = 0
pass
return answer
* 2차) 실패 : 정확도 92.9 😭 악 단하나의 케이스가
def solution(s):
answer = 0
bracket = ['()', '[]', '{}']
s_list = list(s)
for i in range(len(s)):
text = ""
if i != 0:
s_list.append(s_list.pop(0))
text = ''.join(one for one in s_list)
else :
text = s
flag_list = [1, 1, 1]
flag = True
while flag:
if len(text) == 0 :
answer += 1
break
if flag_list == [0, 0, 0]:
if text.find(bracket[0]) and text.find(bracket[1]) and text.find(bracket[2]):
break
for x in range(len(bracket)) :
if bracket[x] in text:
text = text.replace(bracket[x], "")
else:
flag_list[x] = 0
pass
return answer
* 3차) 성공
def solution(s):
answer = 0
bracket = ['(',')', '[',']', '{','}']
s_list = list(s)
for i in range(len(s)):
if i != 0:
s_list.append(s_list.pop(0))
stack = []
for one in s_list:
stack.append(one)
stack_len = len(stack)
if stack_len >= 2:
for x in range(0,len(bracket),2) :
if stack[stack_len-2:stack_len] == bracket[x:x+2]:
stack = stack[:stack_len-2]
if len(stack) == 0 :
answer += 1
return answer
리뷰)
1차)
플래그를 세울기점을 잘 생각해봐야 할듯하다.
왜냐면 지금 내 로직대로는 만약 ({}) 괄호 경우 () 차례에서 이걸 못찾아서 플래그를 0으로 넣고 {} 이걸 지우고 나서야 ()를 판별할 수 있는데 이미 플래그는 0이 됬기 때문
2차)
통과하지 못한 1번 케이스 뭐지.. 1번 케이스로 고민하다가 전에 이거랑 비슷한 문제를 풀었던게 생각나서 햄버거 문제에서 썼던 stack 방식을 응용하니 바로 3차에서 성공 오오오!!!! 이런 유형이 바로 스택 유형!
길이가 같은 배열 A, B 두개가 있습니다. 각 배열은 자연수로 이루어져 있습니다. 배열 A, B에서 각각 한 개의 숫자를 뽑아 두 수를 곱합니다. 이러한 과정을 배열의 길이만큼 반복하며, 두 수를 곱한 값을 누적하여 더합니다. 이때 최종적으로 누적된 값이 최소가 되도록 만드는 것이 목표입니다. (단, 각 배열에서 k번째 숫자를 뽑았다면 다음에 k번째 숫자는 다시 뽑을 수 없습니다.)
예를 들어 A = [1, 4, 2] , B = [5, 4, 4] 라면
A에서 첫번째 숫자인 1, B에서 첫번째 숫자인 5를 뽑아 곱하여 더합니다. (누적된 값 : 0 + 5(1x5) = 5)
A에서 두번째 숫자인 4, B에서 세번째 숫자인 4를 뽑아 곱하여 더합니다. (누적된 값 : 5 + 16(4x4) = 21)
A에서 세번째 숫자인 2, B에서 두번째 숫자인 4를 뽑아 곱하여 더합니다. (누적된 값 : 21 + 8(2x4) = 29) 즉, 이 경우가 최소가 되므로 29를 return 합니다.
배열 A, B가 주어질 때 최종적으로 누적된 최솟값을 return 하는 solution 함수를 완성해 주세요.
📌제한 사항
배열 A, B의 크기 : 1,000 이하의 자연수
배열 A, B의 원소의 크기 : 1,000 이하의 자연수
📌입출력 예
추측)
가장 최솟값을 만들려면 가장 작은값과 가장큰 값을 곱해서 누적해야 할 듯하다. A도 B도 정렬을 하되 하나는 역순 정렬을 하고 for문으로 같은 인덱스값 곱해줘서 누적시키고 누적값을 리턴!
소스코드)
* 1차) 성공!
def solution(A,B):
answer = 0
A.sort()
B.sort(reverse = True)
for i in range(len(A)):
answer += A[i] * B[i]
return answer
리뷰)
다른 사람들의 풀이를 보니 한줄 for문과 리스트 표현식에 sum 함수로 한줄로 정리도 된다 와우!