์ธํ๋ฐ ๊น์ํ๋์ ์คํ๋ง ํต์ฌ ์๋ฆฌ-๊ธฐ๋ณธํธ ๊ฐ์ ๋ด์ฉ์ ๋ฐํ์ผ๋ก ์์ฑํ ๊ธ์ ๋๋ค.
์ด์ ํฌ์คํ ์์ ๊ฐ์ฒด ์งํฅ ์ค๊ณ๋ฅผ ์คํํ๊ธฐ ์ํด์๋ ๋คํ์ฑ์ ์ ์ง์ผ์ผ ํ๋ค๋ ์ด์ผ๊ธฐ๋ฅผ ํ๋ค. ํ์ง๋ง ๊ฐ์ฒด ์งํฅ ์ค๊ณ์ ๋ํด ์ ๋๋ก ์ดํดํ๋ ค๋ฉด, ๋คํ์ฑ ์ธ์๋ ์๊ธ์๋ฅผ ๋ฐ์ SOLID๋ผ ๋ถ๋ฆฌ๋ ๊ฐ์ฒด ์งํฅ ์ค๊ณ์ 5๊ฐ์ง ์์น์ ๋ํด ์ดํดํด์ผ ํ๋ค.
SRP: ๋จ์ผ ์ฑ ์ ์์น (Single Responsibility Principle)
ํ๋์ ํด๋์ค๋ ํ๋์ ์ฑ ์๋ง ๊ฐ์ ธ์ผ ํ๋ค. ํ๋์ ์ฑ ์์ด๋ผ๋ ๋ง์ ๋ชจํธํ๊ธฐ ๋๋ฌธ์, ์ด ํด๋์ค๊ฐ ์ฑ ์์ด ํ๋์ธ์ง ์ฌ๋ฌ๊ฐ์ธ์ง ํท๊ฐ๋ฆด ์ ์๋ค.
์ด๋ฐ ๊ฒฝ์ฐ, ํด๋์ค๋ฅผ ๋ณ๊ฒฝํ์ ๋์ ํ๊ธ ํจ๊ณผ๋ฅผ ๋ณด๋ฉด SRP๋ฅผ ์ ์ง์ผฐ๋์ง ํ์ธํ ์ ์๋ค. ์๋ฅผ ๋ค์ด, ์ฌ๋ฃ ๊ธฐ๋ฐ ๋ ์ํผ ๊ฒ์ ์๋น์ค์์ ์ฌ๋ฃ ํด๋์ค๋ฅผ ๋ณ๊ฒฝํ์ ๋ฟ์ธ๋ฐ ํ์ ํด๋์ค๊น์ง ๋ณ๊ฒฝํด์ผ ํ๋ค๋ฉด ์ด๋ ์๋ชป๋ ์ค๊ณ์ด๋ค.
class Employee {
static int MIN_PAY= 9620;
int overtimeHours; // ์ด๊ณผ๊ทผ๋ฌด ์๊ฐ
// ์ด๊ณผ๊ทผ๋ฌด ์๊ฐ์ ๊ธฐ๋ฐ์ผ๋ก ์ด๊ณผ๊ทผ๋ฌด ์๋น์ ๊ณ์ฐํ๋ ํจ์
int calculatePay() {
return overtimeHours * MIN_PAY;
}
// ์ด๊ณผ๊ทผ๋ฌด ์๊ฐ์ ๋ณด๊ณ ํ๋ ํจ์
int reportHours() {
return overtimeHours;
}
์์ ๊ฐ์ ์ฝ๋๋ฅผ ์๊ฐํด๋ณด์. ์ฌ๋ฌดํ์์๋ calculatePay()
๋ฅผ ํตํด ์ด๊ณผ๊ทผ๋ฌด ์๋น์ ๋ณด๊ณ ๋ฐ๊ณ , ์ธ์ฌํ์์๋ reportHours()
๋ฅผ ํตํด ์ด๊ณผ๊ทผ๋ฌด ์๊ฐ์ ๋ณด๊ณ ๋ฐ๋๋ค. ๊ทธ๋ฌ๋ ์์ค์, ์ฃผ 52์๊ฐ ๊ทผ๋ฌด๋ฅผ ์ฒ ์ ํ ์งํค๊ธฐ ์ํด ์ด๊ณผ๊ทผ๋ฌด๋ฅผ ์๊ฐ ๋จ์๊ฐ ์๋๋ผ ๋ถ ๋จ์๋ก ์ฒดํฌํ๊ฒ ๋์๋ค๊ณ ๊ฐ์ ํ์.
์ธ์ฌํ์์๋ Employee
ํด๋์ค์ overtimeHours
๋ฅผ overtimeMinutes
๋ก ๋ณ๊ฒฝํ๋ค. ๊ทธ๋ฌ๋ฉด ์ฌ๋ฌดํ์์๋ ๋ ๋๋ฆฌ๊ฐ ๋ ๊ฒ์ด๋ค. 3์๊ฐ 20๋ถ์ ์ด๊ณผ๊ทผ๋ฌดํ ์ฌ๋์ ์๋น์ด 3*9620์์ด ์๋๋ผ, 200*9620์์ผ๋ก ๊ณ์ฐ๋๊ธฐ ๋๋ฌธ์ด๋ค. ๊ฒฐ๋ก ์ ์ผ๋ก ์ธ์ฌํ์ ์์ฒญ ํ๋ฒ์ ์ฌ๋ฌดํ๋ ๊ผผ์ง์์ด ๋ก์ง์ ์์ ํด์ผ ํ๋ ์ํฉ์ด ๋์๋ค.
ํน์ ํด๋ผ์ด์ธํธ(์ธ์ฌํ)์ ๋ง๊ฒ ํด๋์ค๋ฅผ ์์ ํ๋ฉด ๋ค๋ฅธ ํด๋ผ์ด์ธํธ(์ฌ๋ฌดํ)๋ ์ํฅ์ ๋ฐ๋ ์ํฉ์ด๋ค. ์ด๋ Employee
ํด๋์ค์ ๋๋ฌด ๋ง์ ์ฑ
์์ด ์๊ธฐ ๋๋ฌธ์ ๋ฐ์ํ๋ค. ์ธ์ฌํ๊ณผ ์ฌ๋ฌดํ์ ๋ ๋ค Employee
ํด๋์ค์ ์์กดํ์ง๋ง ์๋ก ๋ค๋ฅธ ํจ์๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค. ์ด ๊ฒฝ์ฐ ์ธ์ฌํ์ด ์ฌ์ฉํ๋ ๋ถ๋ถ๊ณผ ์ฌ๋ฌดํ์ด ์ฌ์ฉํ๋ ๋ถ๋ถ์ ๋ณ๋์ ์ธํฐํ์ด์ค๋ก ๋ถ๋ฆฌํ์ฌ SRP๋ฅผ ์งํฌ ์ ์๋ค. SRP๋ฅผ ์งํค๋ฉด, ๋ณ๊ฒฝ ์ฌํญ์ด ๋ฐ์ํ์ ๋ ์์ ํด์ผ ํ ํด๋์ค๊ฐ ๋ช
ํํด์ง๋ค.
OCP: ๊ฐ๋ฐฉ-ํ์ ์์น (Open-Closed Principle)
์ํํธ์จ์ด ์์๋ ํ์ฅ์๋ ์ด๋ ค ์์ง๋ง ๋ณ๊ฒฝ์๋ ๋ซํ ์์ด์ผ ํ๋ค. ์ฝ๊ฒ ๋งํด, ๊ธฐ์กด์ ์ฝ๋๋ฅผ ๋ณ๊ฒฝํ์ง ์์ผ๋ฉด์ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ์ ์์ด์ผ ํ๋ค๋ ์๋ฆฌ์ด๋ค. ์ด๊ฒ ๋ฌด์จ ์๋ฆฌ์ผ๊น? ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ ค๋ฉด ๋น์ฐํ ์ฝ๋๋ฅผ ๋ณ๊ฒฝํด์ผ ํ๋ ๊ฒ ์๋๊ฐ? ์ถ์ ์ ์๋ค. ํ์ง๋ง ๋คํ์ฑ์ ์ด์ฉํ๋ฉด ๋ถํ ๊ฐ์๋ผ์ฐ๋ฏ์ด ๊ธฐ๋ฅ์ ๋ณ๊ฒฝํ ์ ์๊ณ , ์ ๋ถํ์ ์ถ๊ฐํ๋ ๊ฒ์ฒ๋ผ ๊ธฐ๋ฅ์ ํ์ฅํ ์ ์๋ค.
class Pokemon {
String name;
String growl() {
if(name.equals("pikachu") {
return "pika!";
else if (name.equals("lizard") {
return "liza!";
}
}
์์ ๊ฐ์ ์ฝ๋๊ฐ ์๋ค๊ณ ๊ฐ์ ํด๋ณด์. ์๋ก์ด ํฌ์ผ๋ชฌ์ด ๋ฐ๊ฒฌ๋ ๋๋ง๋ค growl()
์ else-if๋ฌธ์ ์ถ๊ฐํด์ฃผ์ด์ผ ํ๋ ๋งค์ฐ ๋ฒ๊ฑฐ๋ก์ด ์ฝ๋์ด๋ค. ๊ธฐ๋ฅ์ด ํ์ฅ๋ ๋๋ง๋ค ๊ธฐ์กด์ ์ฝ๋๊ฐ ๋ณ๊ฒฝ๋๋ฏ๋ก OCP ์๋ฐ์ด๋ค.
interface Pokemon {
String growl();
}
class Pikachu implements Pokemon {
String growl() {
return "pika!";
}
}
class Lizard implements Pokemon {
String growl() {
return "liza!";
}
}
์์ ๊ฐ์ด ์ถ์ํ๋ฅผ ํตํด ์ญํ ๊ณผ ๊ตฌํ์ ๋ถ๋ฆฌํ๋ฉด, ์๋ก์ด ํฌ์ผ๋ชฌ์ด ์ถ๊ฐ๋๋๋ผ๋ ๊ธฐ์กด์ ์ฝ๋๋ฅผ ์์ ํ ํ์ ์์ด ์๋ก์ด ํด๋์ค๋ฅผ ์ถ๊ฐํด์ฃผ๊ธฐ๋ง ํ๋ฉด ๋๋ค.
๊ทธ๋ ๋ค๋ฉด ๋คํ์ฑ๋ง์ผ๋ก ์ ๋ง OCP๋ฅผ ์งํฌ ์ ์์๊น?
public class MemberService {
// private MemberRepository memberRepository = new MemoryMemberRepository(); // ๊ธฐ์กด ์ฝ๋
private MemberRepository memberRepository = new JdbcMemberRepository(); // ๋ณ๊ฒฝ๋ ์ฝ๋
}
๊ทธ๋ ์ง ์๋ค. ์์ ์ฝ๋๋ ๋ถ๋ช
๋คํ์ฑ์ ์ด์ฉํด ์ญํ MemberRepository
์ ๊ตฌํ MemoryMemberRepository
, JdbcMemberRepository
๋ฅผ ์ ๋ถ๋ฆฌํ๋ค. ํ์ง๋ง MemberRepository
์ ๊ตฌํ์ฒด๊ฐ ๋ณ๊ฒฝ๋์ ํด๋ผ์ด์ธํธ์ธ MemberService
์ ์ฝ๋๋ ํจ๊ป ์์ ๋์๋ค.
๋ฌธ์ ๋ ๋ฐ๋ก MemberService
๊ฐ ์ง์ ๊ตฌํ์ฒด๋ฅผ ์ ํํ๊ณ ์๋ค๋ ์ ์ด๋ค. ๊ทธ๋ ๋ค๊ณ new~~
๋ถ๋ถ์ ์ง์ฐ๋ฉด ์ธํฐํ์ด์ค๋ง์ผ๋ก๋ ์๋ฌด๊ฒ๋ ํ ์ ์๋ค. ๋ฐ๋ผ์ new
๋ฅผ ํตํด ๊ตฌํ์ฒด๋ฅผ ์์ฑํ๊ณ ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งบ์ด์ฃผ๋ ๋ณ๋์ ์กฐ๋ฆฝ ๋ด๋น์ด ํ์ํ๋ค. ์ด์ฒ๋ผ ๋คํ์ฑ๋ง์ผ๋ก๋ OCP๋ฅผ ์งํฌ ์ ์๊ธฐ ๋๋ฌธ์, ์คํ๋ง์์๋ ์ถ๊ฐ์ ์ผ๋ก DI์ IoC๋ฅผ ํตํด OCP๋ฅผ ์งํจ๋ค.
LSP: ๋ฆฌ์ค์ฝํ ์นํ ์์น (Liskov Substitution Principle)
ํ๋ก๊ทธ๋จ์ ๊ฐ์ฒด๋ ํ๋ก๊ทธ๋จ์ ์ ํ์ฑ์ ๊นจ๋จ๋ฆฌ์ง ์์ผ๋ฉด์ ํ์ ํ์ ์ ์ธ์คํด์ค๋ก ๋ฐ๊ฟ ์ ์์ด์ผ ํ๋ค. ์ฝ๊ฒ ๋งํ๋ฉด ๊ตฌํ์ฒด๋ ์ธํฐํ์ด์ค์ ๊ท์ฝ์ ์ง์ผ์ผ ํ๋ค๋ ์ด์ผ๊ธฐ์ด๋ค. LSP๋ฅผ ์ง์ผ์ผ ํด๋ผ์ด์ธํธ๋ ๊ตฌํ์ฒด๋ฅผ ๋ฏฟ๊ณ ์ฌ์ฉํ ์ ์๋ค.
๋ง์ฐ์ค๋ฅผ ์์๋ก ๋ค์ด๋ณด์. ๋ชจ๋ ๋ง์ฐ์ค๋ ์ผ์ชฝ ๋ฒํผ์ ๋๋ฅด๋ฉด ํด๋ฆญ์ด ๋๊ณ , ์ค๋ฅธ์ชฝ ๋ฒํผ์ ๋๋ฅด๋ฉด ์ปจํ ์คํธ ๋ฉ๋ด๊ฐ ๋ฌ๋ค. ์ด๊ฒ ๋ฐ๋ก ๋ง์ฐ์ค ์ธํฐํ์ด์ค์ ๊ท์ฝ์ด๋ค.
๊ทธ๋ฐ๋ฐ ์ด๋ ๋ , ์ผ์ชฝ ๋ฒํผ์ ๋๋ฅด๋ฉด ์ปจํ ์คํธ ๋ฉ๋ด๊ฐ ๋จ๊ณ ์ค๋ฅธ์ชฝ ๋ฒํผ์ ๋๋ฌ์ผ ํด๋ฆญ์ด ๋๋ ๋ง์ฐ์ค๊ฐ ์ถ์๋๋ค๊ณ ํด๋ณด์. ์ด ๋ง์ฐ์ค๋ ์ฌ์ฉ์๋ค์๊ฒ ํผ๋์ ๋ถ๋ฌ์ฌ ๊ฒ์ด๊ณ , ์ฌ์ฉ์๋ค์ ๋ ์ด์ ๋ง์ฐ์ค๋ฅผ ๋ฏฟ๊ณ ์ฌ์ฉํ ์ ์์ ๊ฒ์ด๋ค. ์ด๊ฒ ๋ฐ๋ก LSP ์๋ฐ์ด๋ค.
ISP: ์ธํฐํ์ด์ค ๋ถ๋ฆฌ ์์น (Interface Segregation Principle)
ํน์ ํด๋ผ์ด์ธํธ๋ฅผ ์ํ ์ธํฐํ์ด์ค ์ฌ๋ฌ ๊ฐ๊ฐ ๋ฒ์ฉ ์ธํฐํ์ด์ค๋ณด๋ค ๋ซ๋ค. ์ธํฐํ์ด์ค๋ ํด๋ผ์ด์ธํธ๊ฐ ์ฌ์ฉํ๋ ๊ธฐ๋ฅ๋ง ์ ๊ณตํด์ผ ํ๋ค๋ ์๋ฏธ์ด๋ค.
interface Player {
void playAudio();
void playVideo();
}
class MediaPlayer implements Player {
@Override
void playAudio() {
...
}
@Override
void playVideo() {
...
}
}
class MP3Player implements Player {
@Override
void playAudio() {
...
}
@Override
void playVideo() {
System.out.println("์ง์ํ์ง ์๋ ๊ธฐ๋ฅ์
๋๋ค.");
return;
}
}
์์ ๊ฐ์ ์ฝ๋๊ฐ ์๋ค๊ณ ๊ฐ์ ํด๋ณด์. MP3Player
์ ํ์ํ ๊ธฐ๋ฅ์ playAudio()
๋ฟ์ด์ง๋ง playVideo()
๊น์ง ํจ๊ป ๊ตฌํํด์ฃผ์ด์ผ ํ๋ค๋ ๋ฌธ์ ๊ฐ ์๊ธด๋ค. ๋ง์ฝ playVideo()
๋ฉ์๋์ ๋งค๊ฐ๋ณ์๊ฐ ์ถ๊ฐ๋๊ฑฐ๋, ๋ฐํํ์
์ด ๋ณ๊ฒฝ๋๋ค๋ฉด MP3Player
ํจ์๋ ์ฌ์ฉํ์ง๋ ์๋ ๋ฉ์๋ ๋๋ฌธ์ ์ฝ๋๋ฅผ ์์ ํด์ผ ํ๋ ๊ฒ์ด๋ค.
์ด์ฒ๋ผ ISP๋ฅผ ์งํค์ง ์๋ ๊ฒฝ์ฐ, ๊ธฐ๋ฅ ๋ณ๊ฒฝ์ ์ฌํ๊ฐ ์ปค์ง๋ค๋ ๊ฒ์ ์ ์ ์๋ค. ๊ทธ๋ ๋ค๋ฉด Player
์ธํฐํ์ด์ค๋ฅผ AudioPlayer
์ VideoPlayer
๋ก ๋ถ๋ฆฌํ๋๊ฑด ์ด๋จ๊น?
interface AudioPlayer {
void playVideo();
}
interface VideoPlayer {
void playVideo();
}
class MediaPlayer implements AudioPlayer, VideoPlayer {
@Override
void playAudio() {
...
}
@Override
void playVideo() {
...
}
}
class MP3Player implements AudioPlayer {
@Override
void playAudio() {
...
}
}
์ด์ ๋ playVideo()
ํจ์์ ์ ์ธ๋ถ๋ฅผ ๋ณ๊ฒฝํด๋ MP3Player
ํด๋์ค์๋ ์ํฅ์ด ๊ฐ์ง ์๋๋ค.
์ด์ฒ๋ผ ISP๋ฅผ ์ ์งํค๋ฉด ์ธํฐํ์ด์ค๊ฐ ๋ช
ํํด์ง๊ณ , ๋์ฒด ๊ฐ๋ฅ์ฑ์ด ๋์์ง๋ค. ๊ทธ๋ฆฌ๊ณ ํน์ ์ธํฐํ์ด์ค๋ฅผ ๋ณ๊ฒฝํด๋ ๋ค๋ฅธ ์ธํฐํ์ด์ค์๋ ์ํฅ์ ์ฃผ์ง ์๋๋ค๋ ์ฅ์ ์ด ์๋ค.
DIP: ์์กด๊ด๊ณ ์ญ์ ์์น(Dependency Inversion Principle)
ํ๋ก๊ทธ๋๋จธ๋ ์ถ์ํ์ ์์กดํด์ผ์ง, ๊ตฌ์ฒดํ์ ์์กดํด์๋ ์๋๋ค. ์ฝ๊ฒ ๋งํด, ๊ตฌํ ํด๋์ค์ ์์กดํ์ง ๋ง๊ณ ์ธํฐํ์ด์ค์ ์์กดํด์ผ ํ๋ค๋ ๋ป์ด๋ค.
์๋ฅผ ๋ค์ด, ์ด์ ์๋ ์๋์ฐจ์ ๋ํด ์๊ณ ์์ผ๋ฉด(=์ด์ ๋ฉดํ๋ง ๊ฐ๊ณ ์์ผ๋ฉด) K3๋ ์ ๋ค์์ค๋ ์ด์ ํ ์ ์๋ค. ์ฐ๋น๋ ์ผ๋ง์ธ์ง, ์ต๊ณ ์๋๋ ์ผ๋ง์ธ์ง ๋ฑ์ ๋ํด์๋ ๋ชฐ๋ผ๋ ๋๋ค. ํด๋ผ์ด์ธํธ(์ด์ ์)๊ฐ ๊ตฌํ์ฒด(K3)๊ฐ ์๋, ์ธํฐํ์ด์ค(์๋์ฐจ)์ ์์กดํ๊ธฐ ๋๋ฌธ์ K3๋ฅผ ์ ๋ค์์ค๋ก ๋ฐ๊ฟ๋ ์ด์ ํ ์ ์๋ ๊ฒ์ฒ๋ผ, ํด๋ผ์ด์ธํธ๋ ์ธํฐํ์ด์ค์ ์์กดํด์ผ ๊ตฌํ์ฒด๋ฅผ ์ ์ฐํ๊ฒ ๋ณ๊ฒฝํ ์ ์๋ค.
public class MemberService {
// private MemberRepository memberRepository = new MemoryMemberRepository(); // ๊ธฐ์กด ์ฝ๋
private MemberRepository memberRepository = new JdbcMemberRepository(); // ๋ณ๊ฒฝ๋ ์ฝ๋
}
์์ ์ฝ๋์์, MemberService
๋ MemberRepository
๋ผ๋ ์ธํฐํ์ด์ค์ ์์กดํ๋ ๋์์ MemoryMemberRepository
๋ผ๋ ๊ตฌํ์ฒด์๋ ์์กดํ๊ณ ์๋ค. ํด๋ผ์ด์ธํธ๊ฐ ๊ตฌํ์ฒด์ ์์กดํ๊ธฐ ๋๋ฌธ์ ๊ตฌํ์ฒด๋ฅผ JdbcMemberRepository
๋ก ๋ณ๊ฒฝํ๋ ค๋ฉด ํด๋ผ์ด์ธํธ์ ์ฝ๋๋ ์์ ํด ์ฃผ์ด์ผ ํ๋ค. ์ด์ ๊ฐ์ ์ฝ๋๋ DIP ์๋ฐ์ด๋ค.
MemberService
๊ฐ ์ง์ ๊ตฌํ์ฒด๋ฅผ ์ ํํ๊ณ ์๊ธฐ ๋๋ฌธ์, DIP๋ฅผ ์งํฌ ์ ์๊ฒ ๋์๋ค. ์ด์ฒ๋ผ ๋คํ์ฑ๋ง์ผ๋ก๋ OCP, DIP๋ฅผ ์งํฌ ์ ์๋ค. ๊ทธ๋ ๋ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น? MemberService
๊ฐ MemberRepository
๋ง ์๊ฒ ๋ง๋ค๋ฉด ๋๋ค. ๊ทธ๋ผ new MemoryMemberRepository()
๋ ๋๊ฐ ํด์ค๊น?
์คํ๋ง์ ๊ฐ๋ฐํ ์ฌ๋๋ค๋ ๋๊ฐ์ ๊ณ ๋ฏผ์ ํ๋ค. ๊ทธ ๊ฒฐ๊ณผ, ์คํ๋ง์ ๊ฐ์ฒด๋ค์ ์ปจํ ์ด๋ ์์ ๋ฃ์ ๋ค์ ๊ฐ์ฒด ๊ฐ์ ์์กด๊ด๊ณ๋ฅผ ์ค์ ํด์ฃผ๋ DI ์ปจํ ์ด๋๋ฅผ ์ ๊ณตํ๊ฒ ๋์๋ค. ์คํ๋ง์ด ์ ๊ณตํ๋ ์ปจํ ์ด๋ ๋๋ถ์ ๊ฐ๋ฐ์๋ค์ ๋คํ์ฑ, OCP, DIP๋ฅผ ๋ชจ๋ ์งํจ ์ฝ๋๋ฅผ ์งค ์ ์๊ฒ ๋ ๊ฒ์ด๋ค.
'๐๐ฉ๐ซ๐ข๐ง๐ > ๐๐๐ฌ๐ข๐' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
์ปดํฌ๋ํธ ์ค์บ (0) | 2023.09.02 |
---|---|
์คํ๋ง์์์ ์ฑ๊ธํค ํจํด (0) | 2023.09.02 |
์คํ๋ง ์ปจํ ์ด๋๋? (0) | 2023.09.02 |
์ ์ด์ ์ญ์ (IoC)๊ณผ ์์กด๊ด๊ณ ์ฃผ์ (DI)์ด๋? (0) | 2023.08.28 |
Spring๊ณผ ๊ฐ์ฒด์งํฅ์ ๋ํ์ฌ (0) | 2023.08.28 |