SOLID - 객체지향

Bam·2023년 2월 21일
0

자바

목록 보기
11/19

객체지향 설계원칙 5가지

1) SRP - 단일 책임 원칙

하나의 클래스는 하나의 책임을 가져야 한다.

// SRP를 위반한 예시
public class User {
    public void createUser() { // 사용자 생성 코드 }
    public void sendEmail() { // 이메일 전송 코드 }
}
// SRP를 준수한 예시
public class User {
    public void createUser() { // 사용자 생성 코드 }
}
public class EmailSender {
    public void sendEmail() { // 이메일 전송 코드 }
}

2) OCP - 개방 폐쇄 원칙

확장에는 열려있고 수정에는 닫혀있어야 한다.
기존의 코드를 변경하지 않으면서(Closed), 기능을 추가할 수 있도록(Open) 설계가 되어야 한다는 원칙을 말한다.

// OCP를 위반한 예시

public class DiscountCalculator {
    public double calculateDiscount(int price, String customerType) {
        double discount = 0.0;
        
        if (customerType.equals("Regular")) {
            discount = price * 0.1;
        } else if (customerType.equals("Premium")) {
            discount = price * 0.2;
        }
        return discount;
    }
}

위 코드는 DiscountCalculator 클래스가 새로운 할인율을 추가하거나 변경할 때마다 코드를 수정해야 합니다. 이를 OCP를 준수하도록 리팩토링하면, 아래와 같이 인터페이스를 사용하여 할인율 계산을 캡슐화할 수 있습니다.

// OCP를 준수한 예시
public interface DiscountCalculator {
    double calculateDiscount(int price);
}
public class RegularDiscountCalculator implements DiscountCalculator {
    public double calculateDiscount(int price) {
        return price * 0.1;
    }
}
public class PremiumDiscountCalculator implements DiscountCalculator {
    public double calculateDiscount(int price) {
        return price * 0.2;
    }
}

3) LSP - 리스코프 치환 원칙

자식클래스는 부모클래스로 대체 할 수 있어야한다.
즉 부모클래스 자리에 자식클래스를 넣어도 계획대로 잘 작동해야 한다.

// LSP 위반 예시
public class Rectangle {
    private int width;
    private int height;
 
    public void setWidth(int width) {
        this.width = width;
    }
    public void setHeight(int height) {
        this.height = height;
    }
    public int getArea() {
        return width * height;
    }
}
public class Square extends Rectangle {
    public void setWidth(int width) {
        this.width = width;
        this.height = width;
    }
    public void setHeight(int height) {
        this.width = height;
        this.height = height;
    }
}

위 코드는 사각형(Rectangle)과 정사각형(Square) 클래스가 상속 관계에 있습니다. 그러나 정사각형은 가로와 세로가 항상 같으므로, setWidth()와 setHeight() 메서드를 오버라이딩하여 항상 두 값이 같도록 만들었습니다. 이로 인해 정사각형 클래스의 getArea() 메서드는 올바른 결과를 반환하지 않아 LSP를 위반합니다.

// LSP를 준수한 예시
public abstract class Shape {
    public abstract int getArea();
}

public class Rectangle extends Shape {
    private int width;
    private int height;
 
    public void setWidth(int width) {
        this.width = width;
    }
    public void setHeight(int height) {
        this.height = height;
    }
    public int getArea() {
        return width * height;
    }
}
public class Square extends Shape {
    private int length;
    public void setLength(int length) {
        this.length = length;
    } 
    public int getArea() {
        return length * length;
    }
}

4) ISP- 인터페이스 분리 원칙

클라이언트는 자신이 사용하는 메소드에만 의존해야 한다
클래스는 자신이 사용하지 않는 인터페이스는 구현하지 않아야함

public interface Animal {
    void eat();
    void fly();
    void swim();
}
public class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating.");
    }    
    @Override
    public void fly() {
        // This method should not exist for a dog.
    }    
    @Override
    public void swim() {
        System.out.println("Dog is swimming.");
    }
}

Animal 인터페이스는 eat(), fly(), swim() 메서드를 모두 포함하고 있습니다. 하지만 Dog 클래스에서는 fly() 메서드를 구현할 필요가 없으며, 오히려 이 메서드를 사용할 경우 잘못된 결과를 내게 됩니다.

public interface Animal {
    void eat();
}
public interface Swimmable {
    void swim();
}
public class Dog implements Animal, Swimmable {
    @Override
    public void eat() {
        System.out.println("Dog is eating.");
    }
    @Override
    public void swim() {
        System.out.println("Dog is swimming.");
    }
}

Animal 인터페이스는 eat() 메서드만을 포함하도록 변경하고, Swimmable 인터페이스를 새로 만들어 swim() 메서드를 추가합니다. 이제 Dog 클래스에서는 필요한 인터페이스만 구현하면 되므로 ISP를 지킬 수 있습니다.

5) DIP- 의존 역전 원칙

변하기 쉬운 것(구체적인 것)보다는 변하기 어려운 것(추상적인 것)에 의존해야함

public class UserService {
    private UserDao userDao;
    
    public UserService() {
        userDao = new UserDao();
    }
    public void addUser(User user) {
        userDao.addUser(user);
    }
}
public class UserDao {
    public void addUser(User user) {
        // code to add user to database
    }
}

UserService 클래스는 UserDao 클래스를 직접 생성하고 사용합니다. 이는 DIP를 위반하는 코드입니다. UserService 클래스는 UserDao 클래스에 직접 의존하고 있기 때문입니다. 이러한 의존성은 코드 유지보수와 확장을 어렵게 만들 수 있습니다.

public interface IUserDao {
    void addUser(User user);
}

public class UserService {
    private IUserDao userDao;
    
    public UserService(IUserDao userDao) {
        this.userDao = userDao;
    }
    public void addUser(User user) {
        userDao.addUser(user);
    }
}
public class UserDao implements IUserDao {
    public void addUser(User user) {
        // code to add user to database
    }
}

UserDao 클래스를 IUserDao 인터페이스로 추상화하고, UserService 클래스는 IUserDao 인터페이스에 의존하도록 변경했습니다. 또한, UserService 클래스는 IUserDao 인터페이스를 생성자 인자로 받아서 사용합니다. 이렇게 함으로써 UserService 클래스와 UserDao 클래스 사이의 결합도를 낮출 수 있으며, 코드 유지보수와 확장성을 높일 수 있습니다.

profile
Challenger

0개의 댓글