SOLID ์์น. ์ข์ ๊ฐ์ฒด์งํฅ ์ค๊ณ์ ๋ค์ฏ ๊ฐ์ง ๊ธฐ์ค. SRP·OCP·LSP·ISP·DIP๋ฅผ ์ํฐํจํด ์ฝ๋์ ๊ฐ์ ์์ ๋ก ์ ๋ฆฌํ๊ณ , ๋์์ธ ํจํด·์คํ๋ง๊ณผ ์ด๋ป๊ฒ ์ฐ๊ฒฐ๋๋์ง๊น์ง ์ง๋๋ค.
๋ค์ด๊ฐ๋ฉฐ
์ง๋ ๊ธ์์ ์ค๋ฌด์์ ์์ฃผ ์ฐ๋ ๋์์ธ ํจํด์ ์ ๋ฆฌํ๋ค. ๊ทธ๋ฐ๋ฐ ๋์์ธ ํจํด์ ํ๋์์ ๋จ์ด์ง ๊ฒ ์๋๋ค.
๊ทธ ๋ฐ์๋ SOLID ๋ผ๋ ๋ค์ฏ ๊ฐ์ง ์ค๊ณ ์์น์ด ๊น๋ ค ์๋ค.
์ ๋ต ํจํด์ด ์ ์ข์์ง, ์คํ๋ง์ด ์ ์์กด์ฑ ์ฃผ์
์ ์ฐ๋์ง๋ฅผ ์ค๋ช
ํ๋ ๊ทผ๊ฑฐ๊ฐ ๋ฐ๋ก SOLID๋ค.
SOLID๋ ๋ก๋ฒํธ ๋งํด(Robert C. Martin)์ด ์ ๋ฆฌํ ๊ฐ์ฒด์งํฅ ์ค๊ณ์ ๋ค์ฏ ์์น์ ๋จธ๋ฆฌ๊ธ์๋ค.
| ์ฝ์ | ์์น | ํ ์ค ์์ฝ |
| S | ๋จ์ผ ์ฑ ์ (SRP) | ํด๋์ค๋ ํ๋์ ์ด์ ๋ก๋ง ๋ณ๊ฒฝ๋ผ์ผ ํ๋ค |
| O | ๊ฐ๋ฐฉ-ํ์ (OCP) | ํ์ฅ์๋ ์ด๋ ค ์๊ณ , ๋ณ๊ฒฝ์๋ ๋ซํ ์์ด์ผ ํ๋ค |
| L | ๋ฆฌ์ค์ฝํ ์นํ (LSP) | ์์์ ๋ถ๋ชจ๋ฅผ ๋์ฒดํ ์ ์์ด์ผ ํ๋ค |
| I | ์ธํฐํ์ด์ค ๋ถ๋ฆฌ (ISP) | ์ ์ฐ๋ ๋ฉ์๋์ ์์กดํ๊ฒ ํ์ง ๋ง๋ผ |
| D | ์์กด์ฑ ์ญ์ (DIP) | ๊ตฌ์ฒด๊ฐ ์๋๋ผ ์ถ์์ ์์กดํ๋ผ |
SOLID๋ ๊ท์น์ด ์๋๋ผ ๋ฐฉํฅ์ด๋ค. ์ฝ๋๊ฐ ๋ณ๊ฒฝ์ ์ผ๋ง๋ ์ ์ฐํ์ง๋ฅผ ๋ณด๋ ๋ค์ฏ ๊ฐ์ง ๋ ์ฆ๋ค.
์ด ๊ธ์์๋ ๊ฐ ์์น์ ์ํฐํจํด ์ฝ๋์ ๊ฐ์ ์ฝ๋๋ก ๋น๊ตํ๋ฉฐ ์ ๋ฆฌํ๋ค. ์์๋ Java/Spring ๊ธฐ์ค์ด๋ค.
S — ๋จ์ผ ์ฑ ์ ์์น (Single Responsibility)
ํ๋์ ํด๋์ค๋ ํ๋์ ์ฑ ์๋ง ๊ฐ์ ธ์ผ ํ๋ค. ๋ค๋ฅด๊ฒ ๋งํ๋ฉด, ํด๋์ค๊ฐ ๋ณ๊ฒฝ๋๋ ์ด์ ๋ ๋จ ํ๋์ฌ์ผ ํ๋ค.
์๋ UserService๋ ์ฌ์ฉ์ ๋ฑ๋ก, ์ด๋ฉ์ผ ๋ฐ์ก, ๋ก๊ทธ ๊ธฐ๋ก๊น์ง ๋ค ํ๋ค.
// ์ํฐํจํด: ํ ํด๋์ค๊ฐ ์ฌ๋ฌ ์ฑ
์์ ์ง์ด์ง
public class UserService {
public void register(User user) {
// 1. ์ฌ์ฉ์ ์ ์ฅ (DB ์ฑ
์)
userRepository.save(user);
// 2. ํ์ ์ด๋ฉ์ผ ๋ฐ์ก (๋ฉ์ผ ์ฑ
์)
String html = "<h1>ํ์ํฉ๋๋ค " + user.getName() + "</h1>";
emailClient.send(user.getEmail(), html);
// 3. ๊ฐ์ฌ ๋ก๊ทธ ๊ธฐ๋ก (๋ก๊น
์ฑ
์)
Files.writeString(Path.of("audit.log"), "registered: " + user.getId());
}
}
์ด ํด๋์ค๋ ์ธ ๊ฐ์ง ์ด์ ๋ก ๋ฐ๋๋ค. ์ด๋ฉ์ผ ์์์ด ๋ฐ๋์ด๋, ๋ก๊ทธ ํ์์ด ๋ฐ๋์ด๋, DB๊ฐ ๋ฐ๋์ด๋ ์ฌ๊ธฐ๋ฅผ ๊ฑด๋๋ฆฐ๋ค.
์ฑ
์์ ๋ถ๋ฆฌํ๋ฉด ์ด๋ ๊ฒ ๋๋ค.
// ๊ฐ์ : ์ฑ
์๋ง๋ค ํด๋์ค๋ฅผ ๋๋๋ค
public class UserService {
private final UserRepository userRepository;
private final WelcomeEmailSender emailSender;
private final AuditLogger auditLogger;
public void register(User user) {
userRepository.save(user); // ์ ์ฅ๋ง
emailSender.send(user); // ์ด๋ฉ์ผ์ ์์
auditLogger.log("registered", user.getId()); // ๋ก๊น
๋ ์์
}
}
์ด์ ์ด๋ฉ์ผ ์์์ด ๋ฐ๋๋ฉด WelcomeEmailSender๋ง ๊ณ ์น๋ฉด ๋๋ค. ๊ฐ ํด๋์ค๊ฐ ์๊ธฐ ์ฑ ์์๋ง ์ง์คํ๋ค.
O — ๊ฐ๋ฐฉ-ํ์ ์์น (Open-Closed)
ํ์ฅ์๋ ์ด๋ ค ์๊ณ , ๋ณ๊ฒฝ์๋ ๋ซํ ์์ด์ผ ํ๋ค. ์ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ๋ ๊ธฐ์กด ์ฝ๋๋ฅผ ๊ณ ์น์ง ์๊ณ , ์ ์ฝ๋๋ฅผ ๋ํ๋ ๋ฐฉ์์ด์ด์ผ ํ๋ค.
๋ฑ๊ธ๋ณ ํ ์ธ์ ๊ณ์ฐํ๋ ์ฝ๋๋ฅผ ๋ณด์.
// ์ํฐํจํด: ๋ฑ๊ธ์ด ์ถ๊ฐ๋ ๋๋ง๋ค ์ด ๋ฉ์๋๋ฅผ ์์
public int discount(Grade grade, int price) {
if (grade == Grade.SILVER) return price * 5 / 100;
else if (grade == Grade.GOLD) return price * 10 / 100;
else if (grade == Grade.VIP) return price * 20 / 100;
// ์ ๋ฑ๊ธ ์ถ๊ฐ = ์ด ์ฝ๋ ์์ (๋ณ๊ฒฝ์ ์ด๋ ค ์์ → ์๋ฐ)
return 0;
}
์ ๋ฑ๊ธ์ด ์๊ธธ ๋๋ง๋ค ์ด ๋ฉ์๋๋ฅผ ์์ ํด์ผ ํ๋ค. ์ ๋ต ํจํด์ผ๋ก ์ถ์ํํ๋ฉด ๋ณ๊ฒฝ ์์ด ํ์ฅํ ์ ์๋ค.
// ๊ฐ์ : ์ ์ฑ
์ ์ธํฐํ์ด์ค๋ก ์ถ์ํ
public interface DiscountPolicy {
boolean supports(Grade grade);
int calculate(int price);
}
@Component
public class VipDiscountPolicy implements DiscountPolicy {
public boolean supports(Grade grade) { return grade == Grade.VIP; }
public int calculate(int price) { return price * 20 / 100; }
}
์ ๋ฑ๊ธ์ DiscountPolicy ๊ตฌํ์ฒด๋ฅผ ์ถ๊ฐํ๊ธฐ๋ง ํ๋ฉด ๋๋ค. ๊ธฐ์กด ์ฝ๋๋ ๋ซํ ์๋ค.
์ง๋ ๊ธ์ ์ ๋ต ํจํด์ด ๋ฐ๋ก OCP๋ฅผ ๊ตฌํํ๋ ๋๊ตฌ๋ค.
L — ๋ฆฌ์ค์ฝํ ์นํ ์์น (Liskov Substitution)
์์ ํ์ ์ ์ธ์ ๋ ๋ถ๋ชจ ํ์ ์ ๋์ฒดํ ์ ์์ด์ผ ํ๋ค. ๋ถ๋ชจ๋ฅผ ์ฐ๋ ์๋ฆฌ์ ์์์ ๋ฃ์์ ๋ ๋์์ด ๊นจ์ง๋ฉด ์ ๋๋ค.
์ ๋ช ํ ๋ฐ๋ก๊ฐ "์ง์ฌ๊ฐํ-์ ์ฌ๊ฐํ" ๋ฌธ์ ๋ค.
// ์ํฐํจํด: Square๊ฐ Rectangle์ ์์
public class Rectangle {
protected int width, height;
public void setWidth(int w) { this.width = w; }
public void setHeight(int h) { this.height = h; }
public int area() { return width * height; }
}
public class Square extends Rectangle {
// ์ ์ฌ๊ฐํ์ ๊ฐ๋ก=์ธ๋ก์ฌ์ผ ํ๋ฏ๋ก ๋ ๋ค ๋ฐ๊ฟ
public void setWidth(int w) { this.width = w; this.height = w; }
public void setHeight(int h) { this.width = h; this.height = h; }
}
๊ฒ๋ณด๊ธฐ์ "์ ์ฌ๊ฐํ์ ์ง์ฌ๊ฐํ์ ์ผ์ข "์ด๋ผ ๋ง์ ๋ณด์ธ๋ค. ํ์ง๋ง ์ด ์ฝ๋๋ ๊นจ์ง๋ค.
void resize(Rectangle r) {
r.setWidth(5);
r.setHeight(4);
assert r.area() == 20; // Rectangle์ด๋ฉด ํต๊ณผ, Square๋ฉด 16 → ์คํจ!
}
Rectangle์ ๊ธฐ๋ํ ์ฝ๋์ Square๋ฅผ ๋ฃ์ผ๋ฉด ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๋ค. ์นํ์ด ์ ๋๋ ๊ฒ์ด๋ค.
์์ ๊ด๊ณ๊ฐ "is-a"์ฒ๋ผ ๋ณด์ฌ๋, ํ์๊ฐ ์ผ๊ด๋์ง ์์ผ๋ฉด ์์ํ๋ฉด ์ ๋๋ค๋ ๊ฒ LSP์ ๊ตํ์ด๋ค.
์ด ๊ฒฝ์ฐ์ ์์ ๋์ ๋์ ๋ถ๋ฆฌํ๊ฑฐ๋ ๋ถ๋ณ ๊ฐ์ฒด๋ก ์ค๊ณํ๋ ๊ฒ ๋ง๋ค.
I — ์ธํฐํ์ด์ค ๋ถ๋ฆฌ ์์น (Interface Segregation)
ํด๋ผ์ด์ธํธ๋ ์์ ์ด ์ฐ์ง ์๋ ๋ฉ์๋์ ์์กดํ๋ฉด ์ ๋๋ค. ๊ฑฐ๋ํ ์ธํฐํ์ด์ค ํ๋๋ณด๋ค, ์๊ณ ๊ตฌ์ฒด์ ์ธ ์ธํฐํ์ด์ค ์ฌ๋ฌ ๊ฐ๊ฐ ๋ซ๋ค.
// ์ํฐํจํด: ๋ง๋ฅ ์ธํฐํ์ด์ค
public interface Machine {
void print(Document doc);
void scan(Document doc);
void fax(Document doc);
}
// ๊ตฌํ ํ๋ฆฐํฐ๋ ์ธ์๋ง ๋๋๋ฐ, ์ ์ฐ๋ ๋ฉ์๋๋ฅผ ์ต์ง๋ก ๊ตฌํ
public class OldPrinter implements Machine {
public void print(Document doc) { /* ์ ์ */ }
public void scan(Document doc) { throw new UnsupportedOperationException(); }
public void fax(Document doc) { throw new UnsupportedOperationException(); }
}
OldPrinter๋ ์ค์บ·ํฉ์ค๋ฅผ ๋ชป ํ๋๋ฐ๋ ๋น ๊ตฌํ์ด๋ ์์ธ๋ฅผ ๊ฐ์ ๋ก ๋ ์๋๋ค. ์ธํฐํ์ด์ค๋ฅผ ์๊ฒ ์ชผ๊ฐ๋ฉด ํด๊ฒฐ๋๋ค.
// ๊ฐ์ : ๊ธฐ๋ฅ๋ณ๋ก ์ธํฐํ์ด์ค๋ฅผ ๋ถ๋ฆฌ
public interface Printer { void print(Document doc); }
public interface Scanner { void scan(Document doc); }
public interface Fax { void fax(Document doc); }
// ํ์ํ ๊ฒ๋ง ๊ตฌํ
public class OldPrinter implements Printer {
public void print(Document doc) { /* ์ธ์๋ง */ }
}
// ๋ณตํฉ๊ธฐ๋ ์ฌ๋ฌ ๊ฐ๋ฅผ ๊ตฌํ
public class AllInOne implements Printer, Scanner, Fax {
public void print(Document doc) { /* ... */ }
public void scan(Document doc) { /* ... */ }
public void fax(Document doc) { /* ... */ }
}
๊ฐ ํด๋์ค๊ฐ ํ์ํ ๋ฅ๋ ฅ๋ง ๊ตฌํํ๋ค. ์ ์ฐ๋ ๋ฉ์๋์ ๋ฌถ์ด์ง ์๋๋ค.
D — ์์กด์ฑ ์ญ์ ์์น (Dependency Inversion)
๊ณ ์์ค ๋ชจ๋์ด ์ ์์ค ๋ชจ๋์ ์์กดํ๋ฉด ์ ๋๋ค. ๋ ๋ค ์ถ์(์ธํฐํ์ด์ค)์ ์์กดํด์ผ ํ๋ค. SOLID์ ๋ง์ง๋ง ๊ธ์์ด์, ์คํ๋ง์ ํต์ฌ์ด๋ค.
// ์ํฐํจํด: ๊ณ ์์ค(์ฃผ๋ฌธ)์ด ์ ์์ค(๊ตฌ์ฒด ๊ฒฐ์ ๊ตฌํ)์ ์ง์ ์์กด
public class OrderService {
private final KakaoPayClient payClient = new KakaoPayClient(); // ๊ตฌ์ฒด ํด๋์ค์ ๋ชป๋ฐํ
public void order() {
payClient.pay(); // ๊ฒฐ์ ์ฌ ๋ฐ๊พธ๋ฉด ์ด ์ฝ๋๋ ๋ฐ๋๋ค
}
}
OrderService๊ฐ KakaoPayClient๋ผ๋ ๊ตฌ์ฒด ํด๋์ค๋ฅผ ์ง์ ๋ง๋ ๋ค. ๊ฒฐ์ ์ฌ๋ฅผ ๋ฐ๊พธ๋ ค๋ฉด ์ฃผ๋ฌธ ๋ก์ง์ ๊ณ ์ณ์ผ ํ๋ค.
์ถ์์ ์์กดํ๋๋ก ๋ค์ง์ผ๋ฉด ์ด๋ ๊ฒ ๋๋ค.
// ๊ฐ์ : ์ถ์(์ธํฐํ์ด์ค)์ ์์กด
public interface PayClient {
void pay();
}
@Service
public class OrderService {
private final PayClient payClient; // ์ธํฐํ์ด์ค์๋ง ์์กด
// ๊ตฌํ์ฒด๋ ์ธ๋ถ์์ ์ฃผ์
๋ฐ๋๋ค (์คํ๋ง DI)
public OrderService(PayClient payClient) {
this.payClient = payClient;
}
public void order() {
payClient.pay(); // ์ด๋ค ๊ตฌํ์ฒด๋ ์๊ด์๋ค
}
}
OrderService๋ PayClient๋ผ๋ ์ถ์์๋ง ์์กดํ๊ณ , ์ค์ ๊ตฌํ์ฒด(KakaoPayClient, TossPayClient)๋ ์คํ๋ง์ด ์ฃผ์
ํ๋ค.
์คํ๋ง์ ์์กด์ฑ ์ฃผ์
(DI)์ด ๋ฐ๋ก DIP์ ๊ตฌํ์ฒด๋ค. ์ฐ๋ฆฌ๊ฐ ์์ฑ์ ์ฃผ์
์ ์ธ ๋๋ง๋ค ์ฌ์ค DIP๋ฅผ ์ค์ฒํ๊ณ ์๋ ์
์ด๋ค.
๋ง์น๋ฉฐ
SOLID๋ ์ํ์ ๋์ค๋ ์๊ธฐ ๊ณผ๋ชฉ์ด ์๋๋ผ, ์ฝ๋๋ฅผ ๋ณด๋ ๋ค์ฏ ๊ฐ์ง ์ง๋ฌธ์ ๊ฐ๊น๋ค.
- ์ด ํด๋์ค๋ ํ ๊ฐ์ง ์ด์ ๋ก๋ง ๋ฐ๋๋๊ฐ? (SRP)
- ๊ธฐ๋ฅ์ ์ถ๊ฐํ ๋ ๊ธฐ์กด ์ฝ๋๋ฅผ ์ ๊ณ ์น๊ณ ๋๋๊ฐ? (OCP)
- ์์์ ๋ถ๋ชจ ์๋ฆฌ์ ๋ฃ์ด๋ ์ ๊นจ์ง๋๊ฐ? (LSP)
- ์ ์ฐ๋ ๋ฉ์๋์ ๋ฌถ์ฌ ์์ง ์์๊ฐ? (ISP)
- ๊ตฌ์ฒด ํด๋์ค๊ฐ ์๋๋ผ ์ธํฐํ์ด์ค์ ์์กดํ๋๊ฐ? (DIP)
๊ทธ๋ฆฌ๊ณ SOLID๋ ๋์์ธ ํจํด๊ณผ ํ ๋ชธ์ด๋ค. OCP๋ฅผ ์งํค๋ ค๋ค ๋ณด๋ฉด ์ ๋ต ํจํด์ด ๋์ค๊ณ , DIP๋ฅผ ์งํค๋ ค๋ค ๋ณด๋ฉด ์์กด์ฑ ์ฃผ์
์ด ๋์จ๋ค.
ํจํด์ SOLID๋ฅผ ๊ตฌํํ๋ ๊ตฌ์ฒด์ ์ธ ๋ฐฉ๋ฒ์ด๊ณ , SOLID๋ ๊ทธ ํจํด๋ค์ด ์ ์ข์์ง๋ฅผ ์ค๋ช
ํ๋ ์๋ฆฌ๋ค.
๋ฌผ๋ก ๋ชจ๋ ์ฝ๋์ SOLID๋ฅผ 100% ์ ์ฉํ ํ์๋ ์๋ค. ๊ณผํ๊ฒ ์ถ์ํํ๋ฉด ์คํ๋ ค ์ฝ๊ธฐ ์ด๋ ค์ด ์ฝ๋๊ฐ ๋๋ค.
์ค์ํ ๊ฑด "์ง๊ธ ์ด ์ฝ๋๊ฐ ๋ณ๊ฒฝ์ ์ฝํ ์ง์ ์ ์ด๋์ธ๊ฐ"๋ฅผ ๋ฌป๊ณ , ๊ฑฐ๊ธฐ์๋ง ์์น์ ์ ์ฉํ๋ ๊ฐ๊ฐ์ด๋ค.
์ฐธ๊ณ ์ถ์ฒ
- Robert C. Martin, ใํด๋ฆฐ ์ํคํ ์ฒ(Clean Architecture)ใ
- Robert C. Martin, "The Principles of OOD" — http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
- Baeldung: SOLID Principles — https://www.baeldung.com/solid-principles
'๐ป Engineering Log > Architecture & Design' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| ์ค๋ฌด์์ ์์ฃผ ์ฐ๋ ๋์์ธ ํจํด 6๊ฐ์ง (0) | 2026.05.31 |
|---|