OOP 원칙

OOP 원칙

주제: Programming 레슨: 5 of 16 μ„ μˆ˜μ§€μ‹: ν΄λž˜μŠ€μ™€ 객체에 λŒ€ν•œ κΈ°λ³Έ 이해, 객체지ν–₯ μ–Έμ–΄ ν•˜λ‚˜ 이상 μˆ™μ§€ λͺ©ν‘œ: OOP의 λ„€ κ°€μ§€ κΈ°λ‘₯(μΊ‘μŠν™”, 좔상화, 상속, λ‹€ν˜•μ„±)을 λ§ˆμŠ€ν„°ν•˜κ³  SOLID 원칙을 μ μš©ν•˜μ—¬ μœ μ§€λ³΄μˆ˜ κ°€λŠ₯ν•˜κ³  ν™•μž₯ κ°€λŠ₯ν•œ μ½”λ“œ μž‘μ„±

μ†Œκ°œ

객체지ν–₯ ν”„λ‘œκ·Έλž˜λ°(OOP, Object-Oriented Programming)은 μ½”λ“œλ₯Ό κ΅¬μ‘°ν™”ν•˜λŠ” 방법을 μ•ˆλ‚΄ν•˜λŠ” κΈ°λ³Έ 원칙듀 μœ„μ— κ΅¬μΆ•λ©λ‹ˆλ‹€. μ΄λŸ¬ν•œ 원칙듀을 ν”Όμƒμ μœΌλ‘œκ°€ μ•„λ‹ˆλΌ 깊이 μ΄ν•΄ν•˜λŠ” 것은 μœ μ§€λ³΄μˆ˜ κ°€λŠ₯ν•˜κ³  ν™•μž₯ κ°€λŠ₯ν•œ μ†Œν”„νŠΈμ›¨μ–΄λ₯Ό μž‘μ„±ν•˜λŠ” 데 ν•„μˆ˜μ μž…λ‹ˆλ‹€. 이 λ ˆμŠ¨μ—μ„œλŠ” OOP의 λ„€ κ°€μ§€ κΈ°λ‘₯, SOLID 원칙, 그리고 각 κ°œλ…μ„ μ–Έμ œ μ μš©ν•˜κ³ (ν˜Ήμ€ ν”Όν•΄μ•Ό) ν•˜λŠ”μ§€ νƒκ΅¬ν•©λ‹ˆλ‹€.

OOP의 λ„€ κ°€μ§€ κΈ°λ‘₯

1. μΊ‘μŠν™”(Encapsulation)

μΊ‘μŠν™”λŠ” 데이터와 κ·Έ 데이터λ₯Ό μ‘°μž‘ν•˜λŠ” λ©”μ„œλ“œλ₯Ό 단일 λ‹¨μœ„(클래슀) 내에 λ¬ΆλŠ” 것이며, 객체의 일뢀 μ»΄ν¬λ„ŒνŠΈμ— λŒ€ν•œ 직접 접근을 μ œν•œν•©λ‹ˆλ‹€.

데이터 은닉과 μ ‘κ·Ό μ œν•œμž(Access Modifiers)

μ–Έμ–΄λ§ˆλ‹€ μΊ‘μŠν™”λ₯Ό λ‹€λ₯΄κ²Œ κ΅¬ν˜„ν•©λ‹ˆλ‹€:

Java:

public class BankAccount {
    private double balance;  // Private: only accessible within this class
    private String accountNumber;

    // Public interface for controlled access
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    public boolean withdraw(double amount) {
        if (amount > 0 && balance >= amount) {
            balance -= amount;
            return true;
        }
        return false;
    }

    public double getBalance() {
        return balance;
    }
}

Python:

class BankAccount:
    def __init__(self, account_number):
        self.__balance = 0.0  # Name mangling (weak privacy)
        self.__account_number = account_number

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount

    def withdraw(self, amount):
        if amount > 0 and self.__balance >= amount:
            self.__balance -= amount
            return True
        return False

    @property
    def balance(self):
        return self.__balance

C++:

class BankAccount {
private:
    double balance;
    std::string accountNumber;

public:
    BankAccount(const std::string& num) : balance(0.0), accountNumber(num) {}

    void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    bool withdraw(double amount) {
        if (amount > 0 && balance >= amount) {
            balance -= amount;
            return true;
        }
        return false;
    }

    double getBalance() const {
        return balance;
    }
};

정보 은닉 원칙(Information Hiding Principle)

핡심 아이디어: κ΅¬ν˜„ 세뢀사항을 숨기고 ν•„μš”ν•œ κ²ƒλ§Œ λ…ΈμΆœν•©λ‹ˆλ‹€. 이λ₯Ό 톡해 클래슀λ₯Ό μ‚¬μš©ν•˜λŠ” μ½”λ“œλ₯Ό κΉ¨λœ¨λ¦¬μ§€ μ•Šκ³  λ‚΄λΆ€ κ΅¬ν˜„μ„ λ³€κ²½ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ‚˜μœ 예 (μΊ‘μŠν™” μœ„λ°˜):

public class User {
    public String name;  // Direct access
    public List<String> permissions;  // Can be modified directly
}

// Client code
user.permissions.add("ADMIN");  // Bypasses any validation

쒋은 예:

public class User {
    private String name;
    private Set<String> permissions;  // Use Set to prevent duplicates

    public void grantPermission(String permission) {
        if (isValidPermission(permission)) {
            permissions.add(permission);
            auditLog.log("Permission granted: " + permission);
        }
    }

    public boolean hasPermission(String permission) {
        return permissions.contains(permission);
    }

    private boolean isValidPermission(String permission) {
        // Validation logic
        return true;
    }
}

2. 좔상화(Abstraction)

μΆ”μƒν™”λŠ” λ³΅μž‘μ„±μ„ 숨기고 ν•„μˆ˜μ μΈ νŠΉμ§•λ§Œ λ³΄μ—¬μ£ΌλŠ” 것을 μ˜λ―Έν•©λ‹ˆλ‹€. λ³΅μž‘ν•œ κ΅¬ν˜„μ„ μˆ¨κΈ°λŠ” λ‹¨μˆœν•œ μΈν„°νŽ˜μ΄μŠ€λ₯Ό λ§Œλ“œλŠ” κ²ƒμž…λ‹ˆλ‹€.

좔상 클래슀 vs μΈν„°νŽ˜μ΄μŠ€

좔상 클래슀 예제 (Java):

public abstract class Shape {
    protected String color;

    public abstract double calculateArea();
    public abstract double calculatePerimeter();

    // Concrete method shared by all shapes
    public void setColor(String color) {
        this.color = color;
    }
}

public class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }

    @Override
    public double calculatePerimeter() {
        return 2 * Math.PI * radius;
    }
}

public class Rectangle extends Shape {
    private double width, height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double calculateArea() {
        return width * height;
    }

    @Override
    public double calculatePerimeter() {
        return 2 * (width + height);
    }
}

μΈν„°νŽ˜μ΄μŠ€ 예제 (C++):

// Pure abstract interface
class Drawable {
public:
    virtual void draw() = 0;  // Pure virtual function
    virtual ~Drawable() = default;
};

class Movable {
public:
    virtual void move(int x, int y) = 0;
    virtual ~Movable() = default;
};

// Class implementing multiple interfaces
class GameCharacter : public Drawable, public Movable {
private:
    int x, y;

public:
    void draw() override {
        std::cout << "Drawing character at (" << x << ", " << y << ")\n";
    }

    void move(int newX, int newY) override {
        x = newX;
        y = newY;
    }
};

3. 상속(Inheritance)

상속은 ν΄λž˜μŠ€κ°€ λ‹€λ₯Έ ν΄λž˜μŠ€λ‘œλΆ€ν„° 속성과 λ©”μ„œλ“œλ₯Ό νšλ“ν•˜μ—¬ "is-a" 관계λ₯Ό 톡해 μ½”λ“œ μž¬μ‚¬μš©μ„ μ΄‰μ§„ν•©λ‹ˆλ‹€.

단일 상속(Single Inheritance)

Python:

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        raise NotImplementedError("Subclass must implement speak()")

    def sleep(self):
        print(f"{self.name} is sleeping")

class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

dog = Dog("Buddy")
print(dog.speak())  # Buddy says Woof!
dog.sleep()         # Buddy is sleeping

닀이아λͺ¬λ“œ 문제(Diamond Problem, 닀쀑 상속)

Python (닀쀑 상속 지원):

class A:
    def method(self):
        print("A")

class B(A):
    def method(self):
        print("B")

class C(A):
    def method(self):
        print("C")

class D(B, C):  # Diamond inheritance
    pass

d = D()
d.method()  # Prints "B" (MRO: D -> B -> C -> A)
print(D.__mro__)  # Shows Method Resolution Order

C++ (잠재적 λ¬Έμ œκ°€ μžˆλŠ” 닀쀑 상속 ν—ˆμš©):

class Device {
protected:
    std::string name;
};

class Printer : public Device {
public:
    void print() { std::cout << "Printing...\n"; }
};

class Scanner : public Device {
public:
    void scan() { std::cout << "Scanning...\n"; }
};

// Diamond problem: two copies of Device
class MultiFunctionPrinter : public Printer, public Scanner {
    // Ambiguity: which 'name' to use?
};

// Solution: Virtual inheritance
class PrinterV : virtual public Device { };
class ScannerV : virtual public Device { };
class MultiFunctionPrinterV : public PrinterV, public ScannerV { };

상속을 μ‚¬μš©ν•˜μ§€ 말아야 ν•  λ•Œ

μ•ˆν‹°νŒ¨ν„΄:

// Inheritance used for code reuse (wrong reason)
class Stack extends ArrayList<Object> {
    public void push(Object item) {
        add(item);
    }

    public Object pop() {
        return remove(size() - 1);
    }
}

// Problem: Stack inherits all ArrayList methods
stack.add(0, "item");  // Can insert at arbitrary position - breaks stack contract!

더 λ‚˜μ€ 방법 (μ»΄ν¬μ§€μ…˜):

class Stack {
    private List<Object> elements = new ArrayList<>();

    public void push(Object item) {
        elements.add(item);
    }

    public Object pop() {
        if (elements.isEmpty()) {
            throw new EmptyStackException();
        }
        return elements.remove(elements.size() - 1);
    }

    // Only expose stack operations
}

4. λ‹€ν˜•μ„±(Polymorphism)

λ‹€ν˜•μ„±μ€ "λ§Žμ€ ν˜•νƒœ"λ₯Ό μ˜λ―Έν•˜λ©°, ν•˜λ‚˜μ˜ μΈν„°νŽ˜μ΄μŠ€μ— μ—¬λŸ¬ κ΅¬ν˜„μ΄ μžˆλŠ” κ²ƒμž…λ‹ˆλ‹€.

컴파일 νƒ€μž„ λ‹€ν˜•μ„±(Compile-Time Polymorphism, λ©”μ„œλ“œ μ˜€λ²„λ‘œλ”©)

Java:

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    public double add(double a, double b) {
        return a + b;
    }

    public int add(int a, int b, int c) {
        return a + b + c;
    }
}

Calculator calc = new Calculator();
calc.add(5, 3);        // Calls int version
calc.add(5.5, 3.2);    // Calls double version
calc.add(1, 2, 3);     // Calls three-argument version

λŸ°νƒ€μž„ λ‹€ν˜•μ„±(Runtime Polymorphism, λ©”μ„œλ“œ μ˜€λ²„λΌμ΄λ”©)

C++:

class PaymentProcessor {
public:
    virtual void processPayment(double amount) {
        std::cout << "Processing payment: $" << amount << "\n";
    }

    virtual ~PaymentProcessor() = default;
};

class CreditCardProcessor : public PaymentProcessor {
public:
    void processPayment(double amount) override {
        std::cout << "Processing credit card payment: $" << amount << "\n";
        // Credit card-specific logic
    }
};

class PayPalProcessor : public PaymentProcessor {
public:
    void processPayment(double amount) override {
        std::cout << "Processing PayPal payment: $" << amount << "\n";
        // PayPal-specific logic
    }
};

void checkout(PaymentProcessor* processor, double amount) {
    processor->processPayment(amount);  // Runtime polymorphism
}

// Usage
CreditCardProcessor creditCard;
PayPalProcessor paypal;

checkout(&creditCard, 99.99);  // Uses CreditCardProcessor
checkout(&paypal, 49.99);       // Uses PayPalProcessor

덕 타이핑(Duck Typing, 동적 μ–Έμ–΄)

Python:

# "If it walks like a duck and quacks like a duck, it's a duck"
class Duck:
    def quack(self):
        print("Quack!")

    def fly(self):
        print("Flying!")

class Person:
    def quack(self):
        print("I'm imitating a duck!")

    def fly(self):
        print("I'm flapping my arms!")

def make_it_quack(duck):
    duck.quack()  # No type checking needed

make_it_quack(Duck())    # Works
make_it_quack(Person())  # Also works!

상속보닀 μ»΄ν¬μ§€μ…˜(Composition Over Inheritance)

ν˜„λŒ€ μ„€κ³„λŠ” 상속(is-a)보닀 μ»΄ν¬μ§€μ…˜(has-a)을 μ„ ν˜Έν•˜λŠ”λ°, 더 μœ μ—°ν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

상속 (경직됨):

class Bird {
    void fly() { }
}

class Penguin extends Bird {
    @Override
    void fly() {
        throw new UnsupportedOperationException("Penguins can't fly!");
    }
}

μ»΄ν¬μ§€μ…˜ (μœ μ—°ν•¨):

interface FlyBehavior {
    void fly();
}

class CanFly implements FlyBehavior {
    public void fly() {
        System.out.println("Flying!");
    }
}

class CannotFly implements FlyBehavior {
    public void fly() {
        System.out.println("Can't fly");
    }
}

class Bird {
    private FlyBehavior flyBehavior;

    public Bird(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    public void performFly() {
        flyBehavior.fly();
    }

    // Can change behavior at runtime
    public void setFlyBehavior(FlyBehavior fb) {
        this.flyBehavior = fb;
    }
}

Bird eagle = new Bird(new CanFly());
Bird penguin = new Bird(new CannotFly());

SOLID 원칙

S: 단일 μ±…μž„ 원칙(Single Responsibility Principle, SRP)

ν΄λž˜μŠ€λŠ” λ³€κ²½ν•΄μ•Ό ν•  μ΄μœ κ°€ ν•˜λ‚˜λ§Œ μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€.

μœ„λ°˜:

class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email

    def save_to_database(self):
        # Database logic (reason 1 to change)
        pass

    def send_welcome_email(self):
        # Email logic (reason 2 to change)
        pass

    def generate_report(self):
        # Reporting logic (reason 3 to change)
        pass

μˆ˜μ •:

class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email

class UserRepository:
    def save(self, user):
        # Database logic only
        pass

class EmailService:
    def send_welcome_email(self, user):
        # Email logic only
        pass

class UserReportGenerator:
    def generate(self, user):
        # Reporting logic only
        pass

O: 개방-폐쇄 원칙(Open/Closed Principle, OCP)

μ†Œν”„νŠΈμ›¨μ–΄ μ—”ν‹°ν‹°λŠ” ν™•μž₯μ—λŠ” μ—΄λ € μžˆμ–΄μ•Ό ν•˜μ§€λ§Œ μˆ˜μ •μ—λŠ” λ‹«ν˜€ μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€.

μœ„λ°˜:

class AreaCalculator {
    public double calculateArea(Object shape) {
        if (shape instanceof Circle) {
            Circle circle = (Circle) shape;
            return Math.PI * circle.radius * circle.radius;
        } else if (shape instanceof Rectangle) {
            Rectangle rect = (Rectangle) shape;
            return rect.width * rect.height;
        }
        // Must modify this method to add new shapes!
        return 0;
    }
}

μˆ˜μ •:

interface Shape {
    double calculateArea();
}

class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

class Rectangle implements Shape {
    private double width, height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double calculateArea() {
        return width * height;
    }
}

class AreaCalculator {
    public double calculateArea(Shape shape) {
        return shape.calculateArea();  // No modification needed for new shapes
    }
}

// Adding Triangle requires no changes to AreaCalculator
class Triangle implements Shape {
    private double base, height;

    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }

    @Override
    public double calculateArea() {
        return 0.5 * base * height;
    }
}

L: λ¦¬μŠ€μ½”ν”„ μΉ˜ν™˜ 원칙(Liskov Substitution Principle, LSP)

μ„œλΈŒνƒ€μž…μ€ ν”„λ‘œκ·Έλž¨ 정확성을 ν•΄μΉ˜μ§€ μ•Šκ³  κΈ°λ³Έ νƒ€μž…μœΌλ‘œ λŒ€μ²΄ κ°€λŠ₯ν•΄μ•Ό ν•©λ‹ˆλ‹€.

μœ„λ°˜:

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def set_width(self, width):
        self.width = width

    def set_height(self, height):
        self.height = height

    def area(self):
        return self.width * self.height

class Square(Rectangle):
    def set_width(self, width):
        self.width = width
        self.height = width  # Maintains square invariant

    def set_height(self, height):
        self.width = height
        self.height = height

# LSP violation
def test_rectangle(rect):
    rect.set_width(5)
    rect.set_height(4)
    assert rect.area() == 20  # Fails for Square!

rectangle = Rectangle(0, 0)
test_rectangle(rectangle)  # Pass

square = Square(0, 0)
test_rectangle(square)  # Fail! area() returns 16, not 20

μˆ˜μ •:

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side * self.side

# No longer substitutable, which is correctβ€”they're different shapes

I: μΈν„°νŽ˜μ΄μŠ€ 뢄리 원칙(Interface Segregation Principle, ISP)

ν΄λΌμ΄μ–ΈνŠΈλ³„ μ„ΈλΆ„ν™”λœ μΈν„°νŽ˜μ΄μŠ€κ°€ λ²”μš© μΈν„°νŽ˜μ΄μŠ€λ³΄λ‹€ λ‚«μŠ΅λ‹ˆλ‹€.

μœ„λ°˜:

interface Worker {
    void work();
    void eat();
    void sleep();
}

class HumanWorker implements Worker {
    public void work() { /* ... */ }
    public void eat() { /* ... */ }
    public void sleep() { /* ... */ }
}

class RobotWorker implements Worker {
    public void work() { /* ... */ }
    public void eat() { /* Robot doesn't eat! */ }
    public void sleep() { /* Robot doesn't sleep! */ }
}

μˆ˜μ •:

interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

interface Sleepable {
    void sleep();
}

class HumanWorker implements Workable, Eatable, Sleepable {
    public void work() { /* ... */ }
    public void eat() { /* ... */ }
    public void sleep() { /* ... */ }
}

class RobotWorker implements Workable {
    public void work() { /* ... */ }
    // Only implements what it needs
}

D: μ˜μ‘΄μ„± μ—­μ „ 원칙(Dependency Inversion Principle, DIP)

좔상화에 μ˜μ‘΄ν•˜κ³  ꡬ체화에 μ˜μ‘΄ν•˜μ§€ λ§ˆμ„Έμš”. μƒμœ„ μˆ˜μ€€ λͺ¨λ“ˆμ€ ν•˜μœ„ μˆ˜μ€€ λͺ¨λ“ˆμ— μ˜μ‘΄ν•˜μ§€ μ•Šμ•„μ•Ό ν•©λ‹ˆλ‹€.

μœ„λ°˜:

class MySQLDatabase {
public:
    void save(const std::string& data) {
        std::cout << "Saving to MySQL: " << data << "\n";
    }
};

class UserService {
private:
    MySQLDatabase database;  // Tight coupling to concrete class

public:
    void createUser(const std::string& username) {
        database.save(username);  // Can't switch databases
    }
};

μˆ˜μ •:

// Abstraction
class Database {
public:
    virtual void save(const std::string& data) = 0;
    virtual ~Database() = default;
};

// Concrete implementations
class MySQLDatabase : public Database {
public:
    void save(const std::string& data) override {
        std::cout << "Saving to MySQL: " << data << "\n";
    }
};

class PostgreSQLDatabase : public Database {
public:
    void save(const std::string& data) override {
        std::cout << "Saving to PostgreSQL: " << data << "\n";
    }
};

// High-level module depends on abstraction
class UserService {
private:
    Database* database;  // Depends on abstraction

public:
    UserService(Database* db) : database(db) {}

    void createUser(const std::string& username) {
        database->save(username);  // Works with any Database implementation
    }
};

// Usage
MySQLDatabase mysql;
UserService service1(&mysql);
service1.createUser("alice");

PostgreSQLDatabase postgres;
UserService service2(&postgres);
service2.createUser("bob");

λ°λ©”ν…Œλ₯΄μ˜ 법칙(Law of Demeter, μ΅œμ†Œ 지식 원칙)

"λ‚―μ„  μ΄μ—κ²Œ λ§ν•˜μ§€ λ§ˆμ„Έμš”." κ°μ²΄λŠ” λ‹€μŒμ—λ§Œ λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•΄μ•Ό ν•©λ‹ˆλ‹€: 1. 자기 μžμ‹  2. νŒŒλΌλ―Έν„°λ‘œ μ „λ‹¬λœ 객체 3. μžμ‹ μ΄ μƒμ„±ν•œ 객체 4. 직접 ꡬ성 μš”μ†ŒμΈ 객체

μœ„λ°˜:

class Car {
    private Engine engine;

    public Engine getEngine() {
        return engine;
    }
}

class Engine {
    private FuelPump fuelPump;

    public FuelPump getFuelPump() {
        return fuelPump;
    }
}

class FuelPump {
    public void pump() { }
}

// Client code
car.getEngine().getFuelPump().pump();  // Chains through three objects!

μˆ˜μ •:

class Car {
    private Engine engine;

    public void refuel() {
        engine.refuel();  // Car tells Engine, doesn't reach into it
    }
}

class Engine {
    private FuelPump fuelPump;

    public void refuel() {
        fuelPump.pump();  // Engine tells FuelPump
    }
}

class FuelPump {
    public void pump() { }
}

// Client code
car.refuel();  // Simple and decoupled

μ‹€μ „ 예제

μ™„μ „ν•œ SOLID 예제 (JavaScript)

// S: Single Responsibility
class Product {
    constructor(name, price) {
        this.name = name;
        this.price = price;
    }
}

class ShoppingCart {
    constructor() {
        this.items = [];
    }

    addItem(product, quantity) {
        this.items.push({ product, quantity });
    }

    removeItem(productName) {
        this.items = this.items.filter(item => item.product.name !== productName);
    }

    getItems() {
        return this.items;
    }
}

// O: Open/Closed - discount strategies
class DiscountStrategy {
    calculate(total) {
        return total;
    }
}

class NoDiscount extends DiscountStrategy {
    calculate(total) {
        return total;
    }
}

class PercentageDiscount extends DiscountStrategy {
    constructor(percentage) {
        super();
        this.percentage = percentage;
    }

    calculate(total) {
        return total * (1 - this.percentage / 100);
    }
}

class FixedDiscount extends DiscountStrategy {
    constructor(amount) {
        super();
        this.amount = amount;
    }

    calculate(total) {
        return Math.max(0, total - this.amount);
    }
}

// D: Dependency Inversion
class OrderService {
    constructor(paymentProcessor, discountStrategy) {
        this.paymentProcessor = paymentProcessor;  // Inject dependency
        this.discountStrategy = discountStrategy;
    }

    checkout(cart) {
        const total = cart.getItems().reduce((sum, item) =>
            sum + item.product.price * item.quantity, 0);

        const discounted = this.discountStrategy.calculate(total);
        return this.paymentProcessor.process(discounted);
    }
}

// Usage
const cart = new ShoppingCart();
cart.addItem(new Product("Laptop", 1000), 1);
cart.addItem(new Product("Mouse", 25), 2);

const discount = new PercentageDiscount(10);
const payment = new CreditCardProcessor();
const orderService = new OrderService(payment, discount);

orderService.checkout(cart);

μš”μ•½

λ„€ κ°€μ§€ κΈ°λ‘₯κ³Ό SOLID 원칙은 ν•¨κ»˜ μž‘λ™ν•©λ‹ˆλ‹€:

원칙 λ°©μ§€ν•˜λŠ” 것 μ£Όμš” 이점
μΊ‘μŠν™”(Encapsulation) 데이터 손상 데이터 무결성
좔상화(Abstraction) λΆˆν•„μš”ν•œ λ³΅μž‘μ„± λ‹¨μˆœμ„±
상속(Inheritance) μ½”λ“œ 쀑볡 μž¬μ‚¬μš©μ„±
λ‹€ν˜•μ„±(Polymorphism) 경직된 μ½”λ“œ μœ μ—°μ„±
SRP μ‹  클래슀(God classes) μœ μ§€λ³΄μˆ˜μ„±
OCP μˆ˜μ • νŒŒκΈ‰ μ•ˆμ •μ„±
LSP κΉ¨μ§„ 계측 ꡬ쑰 μ •ν™•μ„±
ISP λΉ„λŒ€ν•œ μΈν„°νŽ˜μ΄μŠ€ 결합도 κ°μ†Œ
DIP κ°•ν•œ κ²°ν•© ν…ŒμŠ€νŠΈ μš©μ΄μ„±

μ—°μŠ΅ 문제

μ—°μŠ΅ 문제 1: μœ„λ°˜ 사항 식별

이 μ½”λ“œλ₯Ό κ²€ν† ν•˜κ³  μ–΄λ–€ SOLID 원칙이 μœ„λ°˜λ˜μ—ˆλŠ”μ§€ μ‹λ³„ν•˜μ„Έμš”:

class EmailService:
    def __init__(self):
        self.smtp_server = "smtp.gmail.com"
        self.port = 587

    def send_email(self, recipient, subject, body):
        # Connect to SMTP
        # Send email
        pass

    def save_user(self, user):
        # Save to database
        pass

    def generate_invoice(self, order):
        # Generate PDF invoice
        pass

    def calculate_tax(self, amount, country):
        if country == "US":
            return amount * 0.07
        elif country == "UK":
            return amount * 0.20
        elif country == "DE":
            return amount * 0.19
        # ... more countries

μ—°μŠ΅ 문제 2: μ»΄ν¬μ§€μ…˜μœΌλ‘œ λ¦¬νŒ©ν† λ§

상속 기반 섀계λ₯Ό μ»΄ν¬μ§€μ…˜μ„ μ‚¬μš©ν•˜λ„λ‘ λ¦¬νŒ©ν† λ§ν•˜μ„Έμš”:

class Employee {
    protected String name;
    protected double salary;

    public void work() { }
    public void attendMeeting() { }
}

class Manager extends Employee {
    public void managePeople() { }
}

class Developer extends Employee {
    public void writeCode() { }
}

class ManagerDeveloper extends ??? {  // Diamond problem!
    // Needs both managePeople() and writeCode()
}

μ—°μŠ΅ 문제 3: λ¦¬μŠ€μ½”ν”„ μΉ˜ν™˜ 원칙 적용

이 LSP μœ„λ°˜μ„ μˆ˜μ •ν•˜μ„Έμš”:

class Bird {
public:
    virtual void fly() {
        std::cout << "Flying\n";
    }
};

class Ostrich : public Bird {
public:
    void fly() override {
        throw std::logic_error("Ostriches can't fly!");
    }
};

void makeBirdFly(Bird* bird) {
    bird->fly();  // May throw exception!
}

μ—°μŠ΅ 문제 4: SOLID 섀계 κ΅¬ν˜„

λ‹€μŒ μš”κ΅¬μ‚¬ν•­μ„ λ§Œμ‘±ν•˜λŠ” μ•Œλ¦Ό μ‹œμŠ€ν…œμ„ μ„€κ³„ν•˜μ„Έμš”: - 이메일, SMS, ν‘Έμ‹œ μ•Œλ¦Ό 지원 - μš°μ„ μˆœμœ„λ³„ μ•Œλ¦Ό 필터링 ν—ˆμš© - μƒˆλ‘œμš΄ μ•Œλ¦Ό μœ ν˜•μœΌλ‘œ μ‰½κ²Œ ν™•μž₯ κ°€λŠ₯ - λͺ¨λ“  SOLID 원칙 μ€€μˆ˜

μ—°μŠ΅ 문제 5: μ½”λ“œ 리뷰

μ½”λ“œλ² μ΄μŠ€(μžμ‹ μ˜ 것 λ˜λŠ” μ˜€ν”ˆμ†ŒμŠ€)λ₯Ό κ²€ν† ν•˜κ³  λ‹€μŒμ„ μ‹λ³„ν•˜μ„Έμš”: 1. μΊ‘μŠν™” μœ„λ°˜ 1개 2. κ°œμ„  κ°€λŠ₯ν•œ 좔상화 1개 3. λΆ€μ μ ˆν•œ 상속 μ‚¬μš© 1개 4. λ‹€ν˜•μ„±μ΄ 도움이 될 κ³³ 1개 5. SOLID μœ„λ°˜ μ΅œμ†Œ 3개


이전: 04_Programming_Paradigms.md λ‹€μŒ: 06_Functional_Programming.md

to navigate between lessons