๐ง Composition vs Inheritance โ ์ธ์ ์จ์ผ ํ๋?
โ 1. ๊ธฐ๋ณธ ๊ฐ๋ ์ ๋ฆฌ
๐ Inheritance (์์)
- "is-a" ๊ด๊ณ์ผ ๋ ์ฌ์ฉ
- ํ ํด๋์ค๊ฐ ๋ค๋ฅธ ํด๋์ค์ ํ๋์ ๋ฉ์๋๋ฅผ ์์๋ฐ์ ํ์ฅ
- ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ํ์ฑํ๋ฉฐ ์ฝ๋ ์ฌ์ฌ์ฉ์ ์ ๋ฆฌ
class Animal {
void eat() { System.out.println("eating..."); }
}
class Dog extends Animal {
void bark() { System.out.println("bark!"); }
}
โ
Dog is-a Animal
โ ์ฌ๋ฐ๋ฅธ ์์
๐ Composition (๊ตฌ์ฑ)
- "has-a" ๊ด๊ณ์ผ ๋ ์ฌ์ฉ
- ํด๋์ค ๋ด๋ถ์ ๋ค๋ฅธ ํด๋์ค์ ์ธ์คํด์ค๋ฅผ ํ๋๋ก ํฌํจ์์ผ ์ฌ์ฉ
- ์์๋ณด๋ค ์ ์ฐํ๋ฉฐ, ์ญํ ๋ถ๋ฆฌ์ ๊ธฐ๋ฅ ์กฐํฉ์ ์ ๋ฆฌ
class Engine {
void run() { System.out.println("Engine running"); }
}
class Car {
private Engine engine = new Engine();
void start() {
engine.run(); // Engine์ ๊ธฐ๋ฅ์ ์ฌ์ฉ
}
}
โ
Car has-a Engine
โ ๊ตฌ์ฑ ๊ด๊ณ
โ 2. ์ฐจ์ด์ ์์ฝ
ํญ๋ชฉ | Inheritance (์์) | Composition (๊ตฌ์ฑ) |
---|---|---|
๊ด๊ณ | is-a | has-a |
๊ฒฐํฉ๋ | ๋์ | ๋ฎ์ |
์ ์ฐ์ฑ | ๋ฎ์ | ๋์ |
ํ ์คํธ ์ฉ์ด์ฑ | ๋ฎ์ | ๋์ |
๋ณ๊ฒฝ ์ํฅ | ํผ (๋ถ๋ชจ ๋ณ๊ฒฝ ์ ์์ ์ํฅ) | ์์ (ํ์ํ ๊ธฐ๋ฅ๋ง ํฌํจ) |
์ฌ์ฉ ์ | ํ์ ํ์ฅ | ๊ธฐ๋ฅ ์กฐํฉ, ์์ |
โ 3. โ ์ธ์ ์์์ ํผํด์ผ ํ ๊น? + โ ์ธ์ ์์์ ์จ์ผ ํ ๊น?
โ ํผํด์ผ ํ ์ํฉ๋ค
โ ์1: ๋จ์ํ ๊ธฐ๋ฅ ์ฌ์ฌ์ฉ ๋ชฉ์ ์ผ๋ก๋ง ์์ํ ๋
class FileLogger {
void log(String message) {
System.out.println("LOG: " + message);
}
}
class User extends FileLogger { // โ User is-a FileLogger?
String name;
}
๐ User
๋ ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๊ธฐ ์ํด ์์์ ๋ฐ์์ง๋ง User is-a FileLogger
๋ ๋ง์ด ์ ๋จ
โก๏ธ ์๋ชป๋ ์์
โ ์ฌ๋ฐ๋ฅธ ๋ฐฉ์ (Composition):
class User {
String name;
FileLogger logger = new FileLogger();
void doSomething() {
logger.log("User did something");
}
}
โ ์2: ๋ถ๋ชจ ๋ณ๊ฒฝ ์ ์์๊น์ง ์ํฅ ๋ฐ๋ ๊ฒฝ์ฐ
class Animal {
void move() {
System.out.println("Animal moves");
}
}
class Snake extends Animal {
// Snake๋ ๋ถ๋ชจ ๋ฉ์๋๋ง ์ฌ์ฉ ์ค
}
๋ถ๋ชจ ์์ :
class Animal {
void move() {
// ์ด๋ ์ ๋ก๊น
์ถ๊ฐ๋จ
System.out.println("LOG: animal moving...");
}
}
๐ ์์์น ๋ชปํ ๋ณ๊ฒฝ์ด ์์ ํด๋์ค ์ ์ฒด์ ์ํฅ์ ๋ฏธ์นจ
โก๏ธ ์บก์ํ ๊นจ์ง
๊ทธ ๊ตฌ์กฐ๋ฅผ ์์ ๋์ ๊ตฌ์ฑ(Composition)์ผ๋ก ๋ฐ๊ฟจ์ ๋ ์ด๋ค ์์ผ๋ก ๋๋์ง
โ ์์ ๊ตฌ์กฐ (๋ฌธ์ ์ํฉ ๋ค์ ๋ณต์ต)
1. ์์ ์ ๋ถ๋ชจ ํด๋์ค ๋ณ๊ฒฝ โ ์์ ํด๋์ค ์ํฅ ๋ฐ๋ ์ด์
๐ ๊ฐ๋ ์ค๋ช
์์์ ๋ถ๋ชจ์ ๋ชจ๋ ํ๋์ ๋ฉ์๋๋ฅผ ์์ ํด๋์ค๊ฐ ๊ทธ๋๋ก "๋ฌผ๋ ค๋ฐ๋" ๊ตฌ์กฐ์
๋๋ค.
์ด๋ ๋งค์ฐ ๊ฐ๋ ฅํ์ง๋ง, ๋ถ๋ชจ๊ฐ ๋ณ๊ฒฝ๋๋ฉด ์์์ด ์๋์ผ๋ก ์ํฅ์ ๋ฐ๊ธฐ ๋๋ฌธ์ ์ทจ์ฝํฉ๋๋ค.
์ด๊ฑธ ๊ฐํ ๊ฒฐํฉ(high coupling) ์ด๋ผ๊ณ ํด์.
โ ๋ฌธ์ ์์ฝ
๋ถ๋ชจ ํด๋์ค์ ๋ด๋ถ ๊ตฌํ์ด ๋ฐ๋๋ฉด,
์์ ํด๋์ค๋ ์์ง ๋ชปํ ์ฑ ๊ทธ ์ํฅ์ ๊ทธ๋๋ก ๋ฐ๋๋ค.
๐ก ์์ ์ค๋ช
class Animal {
void move() {
System.out.println("Animal moves");
}
}
class Snake extends Animal {
// ์๋ฌด ๊ฒ๋ ์ค๋ฒ๋ผ์ด๋ฉ ์ ํจ โ Animal์ move() ์ฌ์ฉ
}
๐ฅ ๊ทธ๋ฐ๋ฐ Animal์ ๋ด๋ถ๊ฐ ๋ณ๊ฒฝ๋๋ฉด ...
class Animal {
void move() {
logGPS(); // ์: ์ถ๊ฐ๋ ๋ก๊น
๊ธฐ๋ฅ (์ฑ๋ฅ ์ ํ ๊ฐ๋ฅ์ฑ)
System.out.println("Animal moves");
}
}
โ ๋ฌธ์
Snake s = new Snake();
s.move(); // ์ถ๋ ฅ: Animal moves with tracking
// โ ์๋์น ์๊ฒ GPS ๋ก๊ทธ ๊ธฐ๋ฅ๊น์ง ๋ฐ๋ผ์ด
- Snake๋ move() ๋ด๋ถ๊ฐ ๋ณ๊ฒฝ๋ ์ฌ์ค๋ ๋ชจ๋ฅด๊ณ ๋ฐ๋ผ์ด
- ํ ์คํธ๋ ์์ธ ์์ด ์ํฅ๋ฐ๊ณ , ์ฑ๋ฅ ๋ฌธ์ ๋ฐ์ ๊ฐ๋ฅ
๐ ์์ฝ: ์์์ ๋ด๋ถ ๊ตฌํ๊น์ง ๊ฐํ๊ฒ ๊ฒฐํฉ๋๋ฏ๋ก, ๋ถ๋ชจ ๋ณ๊ฒฝ์ด ์์์๊ฒ ๊ทธ๋๋ก ์ ๋ฌ๋๋ค.
โก๏ธ ์ด๋ฐ ์ํฉ์ ๋ฐฉ์งํ๋ ค๋ฉด ๊ตฌ์ฑ(Composition)์ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์์ ํ๋ค.
โ ๊ตฌ์ฑ(Composition) ๊ตฌ์กฐ๋ก ๋ฆฌํฉํ ๋งํ๋ฉด?
class AnimalMover {
void move() {
System.out.println("Animal moves");
}
}
class Snake {
private AnimalMover mover = new AnimalMover();
void move() {
mover.move();
}
}
๐ ์ด๋ ๊ฒ ํ๋ฉดโฆ
- Snake๋ AnimalMover ๊ฐ์ฒด๋ฅผ "๊ฐ์ง๊ณ " ์์ด์ (has-a ๊ด๊ณ)
- AnimalMover๊ฐ ๋ณ๊ฒฝ๋๋๋ผ๋, Snake๋ ์ ํ์ ์ผ๋ก ๋์ํ ์ ์์ด์
โ ๊ตฌ์ฑ์ ํต์ฌ ์ฅ์ : ์ ์ฐํ๊ฒ ๊ต์ฒด ๊ฐ๋ฅ!
class AnimalMoverWithLogging extends AnimalMover {
@Override
void move() {
logGPS(); // ์ฑ๋ฅ ๋น์ฉ์ด ํฐ ๊ธฐ๋ฅ
super.move();
}
}
Snake snake = new Snake();
snake.setMover(new AnimalMoverWithLogging()); // ์ ๋ต ๋ณ๊ฒฝ ๊ฐ๋ฅ
โก๏ธ ๋ฐํ์์ ์ด๋ค mover๋ฅผ ์ธ์ง ๊ต์ฒด ๊ฐ๋ฅ โ ์ ๋ต ํจํด ์์ฉ ๊ฐ๋ฅ
โ ์ ์ฒด ์ฝ๋ ์์ (๋ฆฌํฉํ ๋ง ๋ฒ์ )
// ์์ง์์ ๋ด๋นํ๋ ๊ตฌ์ฑ ์์
class AnimalMover {
void move() {
System.out.println("Animal moves");
}
}
// ๋ถ๊ฐ ๊ธฐ๋ฅ์ด ์ถ๊ฐ๋ ์ ๋ต
class AnimalMoverWithLogging extends AnimalMover {
@Override
void move() {
logGPS();
super.move();
}
private void logGPS() {
System.out.println("[GPS ๋ก๊ทธ]: ์์น ๊ธฐ๋ก๋จ");
}
}
// Snake๋ Animal์ด ์๋ ๋ณ๋ ํด๋์ค โ ๊ตฌ์ฑ ์ฌ์ฉ
class Snake {
private AnimalMover mover;
Snake(AnimalMover mover) {
this.mover = mover;
}
void move() {
mover.move(); // ๋์ ์์
}
void setMover(AnimalMover newMover) {
this.mover = newMover; // ์ ๋ต ๊ต์ฒด๋ ๊ฐ๋ฅ
}
}
โ ์คํ ์์
public class Main {
public static void main(String[] args) {
Snake snake = new Snake(new AnimalMover());
snake.move();
// ์ถ๋ ฅ: Animal moves
// ๋์ ์ ๋ต ๊ต์ฒด
snake.setMover(new AnimalMoverWithLogging());
snake.move();
// ์ถ๋ ฅ:
// [GPS ๋ก๊ทธ]: ์์น ๊ธฐ๋ก๋จ
// Animal moves
}
}
โ ๊ตฌ์ฑ์ผ๋ก ๋ฐ๊พธ๋ฉด ์๊ธฐ๋ ์ฅ์
ํญ๋ชฉ | ์ค๋ช |
์ ์ฐ์ฑ | ํ์ํ ๋ ๋์์ ๊ต์ฒด ๊ฐ๋ฅ (์: ํ ์คํธ์ฉ, ์ฑ๋ฅ ์ ํ์ฉ ๋ฑ) |
ํ ์คํธ์ฑ | ๊ฐ์ง mover ๋ฃ์ด์ ํ ์คํธ ๊ฐ๋ฅ (MockMover) |
๊ฒฐํฉ๋ ๋ฎ์ | Snake๋ mover์ ์ธํฐํ์ด์ค์๋ง ์์กด |
์ฌ์ฌ์ฉ์ฑ | AnimalMover๋ฅผ ๋ค๋ฅธ ํด๋์ค์์๋ ์ฌ์ฌ์ฉ ๊ฐ๋ฅ |
๋ณ๊ฒฝ ์์ ์ฑ | ๋ถ๋ชจ ํด๋์ค ๋ณํ์ ์๋์ผ๋ก ์ํฅ๋ฐ์ง ์์ |
๐ง ๊ฒฐ๋ก
์์ ๊ตฌ์กฐ์์๋ ๋ถ๋ชจ ํด๋์ค ๋ณ๊ฒฝ์ด ์์์๊ฒ ์๋ ์ ํ๋์ด ์ํํ์ง๋ง,
๊ตฌ์ฑ์ ์ฌ์ฉํ๋ฉด ๊ธฐ๋ฅ์ ๊ฐ์ฒด๋ก ๋ถ๋ฆฌํด ์ ํ์ ์ผ๋ก ํฌํจํ๊ฑฐ๋ ๊ต์ฒด ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์
ํจ์ฌ ์์ ์ ์ด๊ณ ์ ์ฐํ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค.
โ ์3: ํ ์คํธ ์ด๋ ค์ (์์ ๊น์ด๋ก ์ธํด)
class UserService extends DatabaseService {
void registerUser() {
// DB ์ฐ๊ฒฐ๋ถํฐ ๊ฐ์ ๋ก ํฌํจ๋จ
}
}
โ ๊ตฌ์ฑ์ผ๋ก ๋์ฒด:
class UserService {
private DatabaseService db;
UserService(DatabaseService db) {
this.db = db; // ์์กด์ฑ ์ฃผ์
}
}
โก๏ธ ์ ๋ ํ ์คํธ ๊ฐ๋ฅ, ์ ์ฐ์ฑ โ
โ ๊ทธ๋ผ ์ธ์ ์์์ ์จ์ผ ํ ๊น?
โ ์1: ์ง์ง is-a ๊ด๊ณ์ผ ๋
class Vehicle {
void move() { System.out.println("Vehicle moving"); }
}
class Bus extends Vehicle {
void openDoors() { System.out.println("Doors opened"); }
}
โก๏ธ Bus is-a Vehicle
โ ๋
ผ๋ฆฌ์ ํ์
ํ์ฅ์ผ๋ก ์ ์
โ ์2: ๊ธฐ๋ณธ ๋์์ ํ์ฅํ๊ณ ์ถ์ ๋
class Button {
void click() {
System.out.println("Button clicked");
}
}
class AnimatedButton extends Button {
@Override
void click() {
super.click();
System.out.println("...with animation");
}
}
โก๏ธ ๋ถ๋ชจ ๊ธฐ๋ณธ ๋์์ ํ์ฅํด์ ์ฐ๋ ๊ฒฝ์ฐ๋ ์์์ด ์ ํฉ
โ 4. ์ธ์ Composition์ ์จ์ผ ํ ๊น?
โ ์1: ๊ธฐ๋ฅ ์กฐํฉ์ด ๋ชฉ์ ์ผ ๋
class NotificationSender {
void send(String msg) {
System.out.println("Sending: " + msg);
}
}
class OrderService {
NotificationSender sender = new NotificationSender();
void completeOrder() {
sender.send("Order completed!");
}
}
โก๏ธ OrderService is-a Sender
๋ ์๋ โ ๊ธฐ๋ฅ๋ง ํฌํจํด์ ์ฌ์ฉ
โ ์2: ๋์์ ์ ์ฐํ๊ฒ ๊ต์ฒดํด์ผ ํ ๋ (์ ๋ต ํจํด)
interface PayStrategy {
void pay(int amount);
}
class KakaoPay implements PayStrategy {
public void pay(int amount) {
System.out.println("Paid with KakaoPay");
}
}
class PaymentProcessor {
PayStrategy strategy;
PaymentProcessor(PayStrategy strategy) {
this.strategy = strategy;
}
void process(int amount) {
strategy.pay(amount);
}
}
โก๏ธ ๋ฐํ์์ ์ ๋ต(๊ฒฐ์ ์๋จ) ๊ต์ฒด ๊ฐ๋ฅ โ ๊ตฌ์ฑ ๋ฐฉ์์ ๋ํ์ ์ฅ์
โ ์ ๋ต ํจํด์ ํตํด ๊ตฌ์ฑ(Composition)์ผ๋ก ๋์์ ์ ์ฐํ๊ฒ ๊ต์ฒดํ๋ ๋ฐฉ์
๐ ๊ฐ๋ ์ค๋ช
๋๋๋ก ์ฐ๋ฆฌ๋ ๊ฐ์ ๋์์ ์ํฉ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ์ฒ๋ฆฌํ๊ณ ์ถ์ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด:
- ์ค๋์ ์นด์นด์คํ์ด๋ก ๊ฒฐ์
- ๋ด์ผ์ ์ ์ฉ์นด๋๋ก ๊ฒฐ์
- ํ ์คํธ ํ๊ฒฝ์์๋ ๊ฐ์ง ๊ฒฐ์ ๋ก ์ฒ๋ฆฌ
์ด๋ ์์์ผ๋ก๋ ๋์ ๊ต์ฒด๊ฐ ์ด๋ ต๊ณ ,
์ธํฐํ์ด์ค + ๊ตฌ์ฑ(Composition) ์ ํ์ฉํ๋ฉด ์ ์ฐํ๊ฒ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
โก๏ธ ์ด ๋ฐฉ์์ด ์ ๋ต ํจํด (Strategy Pattern) ์ ๋๋ค.
๐ฏ ์ ๋ต ํจํด ๊ฐ๋ ์์ฝ
์์ | ์ญํ |
์ธํฐํ์ด์ค | ๊ณตํต ๋์์ "๊ณ์ฝ" ์ ์ (ex. pay) |
๊ตฌํ ํด๋์ค | ๊ฐ๊ฐ์ ์ ๋ต์ ์ค์ ๋ก ๊ตฌํ (ex. KakaoPay, CardPay) |
Context ํด๋์ค | ๋์์ ์คํํ์ง๋ง ์ ๋ต์ "๋ฐ๊นฅ์์ ์ฃผ์ "๋ฐ์ |
โ ์์ ์ฝ๋
1) ๊ณตํต ๋์ ์ ์ (์ธํฐํ์ด์ค)
interface PayStrategy {
void pay(int amount);
}
2) ์ ๋ต ๊ตฌํ์ฒด (์ฌ๋ฌ ๊ฐ)
class KakaoPay implements PayStrategy {
public void pay(int amount) {
System.out.println("Paid " + amount + " with KakaoPay");
}
}
class CardPay implements PayStrategy {
public void pay(int amount) {
System.out.println("Paid " + amount + " with Credit Card");
}
}
3) ์คํ ํด๋์ค (์ ๋ต์ ๋ฐ์์ ์์)
class PaymentProcessor {
private PayStrategy strategy;
public PaymentProcessor(PayStrategy strategy) {
this.strategy = strategy;
}
public void process(int amount) {
strategy.pay(amount); // ์ ๋ต์๊ฒ ์คํ ์์
}
}
๐งช ์ฌ์ฉ ์์
PayStrategy strategy = new KakaoPay(); // ์ ๋ต ์ ํ
PaymentProcessor processor = new PaymentProcessor(strategy);
processor.process(10000); // ์ถ๋ ฅ: Paid 10000 with KakaoPay
// ์ ๋ต ๊ต์ฒด
processor = new PaymentProcessor(new CardPay());
processor.process(10000); // ์ถ๋ ฅ: Paid 10000 with Credit Card
๐ก ์ด ๊ตฌ์กฐ์ ์ฅ์
์ฅ์ | ์ค๋ช |
์ ์ฐ์ฑ | ์คํ ์ค ์ ๋ต(๋์)์ ์ฝ๊ฒ ๊ต์ฒด ๊ฐ๋ฅ |
ํ ์คํธ ์ฉ์ด | ๋ชจ์ ์ ๋ต(MockStrategy)์ผ๋ก ํ ์คํธ ๊ฐ๋ฅ |
์ฌ์ฌ์ฉ์ฑ | ์ ๋ต ๊ตฌํ์ฒด๋ค์ ๋ค๋ฅธ ๊ณณ์์๋ ํ์ฉ ๊ฐ๋ฅ |
๊ฒฐํฉ๋ ๋ฎ์ | ์คํ ํด๋์ค๋ PayStrategy์๋ง ์์กดํ๊ณ , ๊ตฌ์ฒด ๊ตฌํ์ฒด๋ ๋ชฐ๋ผ๋ ๋จ |
๐ ์ ๋ฆฌ: ์ ๋ต ํจํด์ด ํ์ํ ์ํฉ
- ํน์ ๊ธฐ๋ฅ(์: ๊ฒฐ์ , ์ ๋ ฌ, ์ถ๋ ฅ)์ ๋ค์ํ ๋ฐฉ์์ผ๋ก ๊ตฌํํด์ผ ํ ๋
- ์คํ ์ค์ ์ ๋ต์ ๋ฐ๊พธ๊ณ ์ถ์ ๋
- ํ ์คํธ ์ ์ค์ ์ ๋ต ๋์ ๊ฐ์ง(Mock)๋ฅผ ์ฐ๊ณ ์ถ์ ๋
- ์์๋ณด๋ค ๋ ์ ์ฐํ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค๊ณ ์ถ์ ๋
โ ๊ฒฐ๋ก ์์ฝ
ํญ๋ชฉ | ์์ (Inheritance) | ๊ตฌ์ฑ (Composition) |
๊ตฌ์กฐ | ๊ณ์ธต์ (is-a) | ์กฐํฉ์ (has-a, uses-a) |
๋ณ๊ฒฝ์ ๋ํ ๊ฐ๊ฑดํจ | ์ฝํจ (๋ถ๋ชจ ๋ณ๊ฒฝ โ ์์ ์ํฅ) | ๊ฐํจ (๋ด๋ถ ๊ตฌ์ฑ๋ง ๊ต์ฒด) |
๋์ ํ์ฅ | ์์ or ์ค๋ฒ๋ผ์ด๋ฉ | ์ธํฐํ์ด์ค ๊ธฐ๋ฐ ์ ๋ต ๊ต์ฒด |
์ ์ฐ์ฑ | ๋ฎ์ | โ ๋์ |
ํ ์คํธ ์ฉ์ด์ฑ | ์ด๋ ค์ | โ ๋์ |
โ ์3: ๋จ์ ํ ์คํธ๊ฐ ํ์ํ ๊ฒฝ์ฐ
class ReportGenerator {
FileWriter writer;
ReportGenerator(FileWriter writer) {
this.writer = writer;
}
void generate() {
writer.write("Report...");
}
}
โก๏ธ ์ค์ ์คํ์์๋ FileWriter
, ํ
์คํธ์์๋ MockWriter
์ฌ์ฉ ๊ฐ๋ฅ
๐ฏ ๊ฒฐ๋ก ์์ฝ: ์ธ์ ์ด๋ค ๊ฑธ ์จ์ผ ํ๋?
์ํฉ | ์์ (Inheritance) | ๊ตฌ์ฑ (Composition) |
---|---|---|
์ง์ง๋ก is-a ๊ด๊ณ์ผ ๋ | โ ์ฌ์ฉ | โ ๋ถํ์ |
๊ธฐ๋ฅ ์ฌ์ฌ์ฉ์ด ๋ชฉ์ ์ผ ๋ | โ ์ํ | โ ์ ๋ฆฌ |
์ ์ฐํ๊ฒ ๊ต์ฒด ๊ฐ๋ฅํด์ผ ํ ๋ | โ ๋ถํธ | โ ๊ฐ๋ฅ |
ํ ์คํธ, ํ์ฅ ๊ณ ๋ ค ์ | โ ๋จ์ ๋ง์ | โ ์ฅ์ ๋ง์ |
๊ด๊ณ๊ฐ has-a / uses-a | โ ๋ถ์ ์ | โ ์ ์ |
๋ํ ์ ์ฉ ํจํด | ํ ํ๋ฆฟ ๋ฉ์๋ ํจํด | ์ ๋ต ํจํด, ๋ฐ์ฝ๋ ์ดํฐ, DI |
๐ง ํต์ฌ ์ฒ ํ
โ "๊ฐ๋ฅํ ํ ์์๋ณด๋ค ๊ตฌ์ฑ์ ์ ํธํ๋ผ (Favor composition over inheritance)"
โ Effective Java, Joshua Bloch
๋๊ธ