-
객체지향 5대 원칙 - SOLID (단일책임원칙, 개방폐쇄원칙, 리스코프치환 원칙, 인터페이스분리원칙, 의존역전원칙)전산학/소프트웨어공학 2024. 7. 30. 21:50728x90반응형
객체지향 개발에서 SOLID 원칙은 코드의 유연성과 유지보수성을 높이기 위한 다섯 가지 설계 원칙을 말합니다. 각 원칙과 이에 대한 Java 코드 예시는 다음과 같습니다:
1. 단일 책임 원칙 (Single Responsibility Principle, SRP)
- 설명: 하나의 클래스는 하나의 책임만 가져야 한다. 즉, 클래스는 하나의 기능만 수행해야 하며, 그 기능에 대해서만 책임져야 한다.
Java 코드 예시:
// 위반된 예시: User 클래스가 사용자 데이터 처리와 이메일 전송 두 가지 책임을 가짐 class User { private String name; private String email; public User(String name, String email) { this.name = name; this.email = email; } public void sendEmail(String message) { // 이메일 전송 로직 System.out.println("Sending email to " + email + ": " + message); } // 사용자 데이터 관련 메소드들 public String getName() { return name; } public String getEmail() { return email; } } // 수정된 예시: 책임을 분리한 두 개의 클래스 class User { private String name; private String email; public User(String name, String email) { this.name = name; this.email = email; } // 사용자 데이터 관련 메소드들 public String getName() { return name; } public String getEmail() { return email; } } class EmailService { public void sendEmail(User user, String message) { // 이메일 전송 로직 System.out.println("Sending email to " + user.getEmail() + ": " + message); } }
2. 개방-폐쇄 원칙 (Open/Closed Principle, OCP)
- 설명: 소프트웨어 요소(클래스, 모듈 등)는 확장에 열려 있어야 하고, 수정에는 닫혀 있어야 한다. 즉, 기능을 확장할 수 있지만, 기존 코드를 변경하지 않고 기능을 추가할 수 있어야 한다.
Java 코드 예시:
// 위반된 예시: 새로운 할인 정책을 추가할 때마다 기존 코드를 수정해야 함 class DiscountService { public double calculateDiscount(String type, double price) { if (type.equals("seasonal")) { return price * 0.1; } else if (type.equals("clearance")) { return price * 0.5; } return 0; } } // 수정된 예시: 할인 정책을 확장할 수 있도록 구조 변경 interface DiscountStrategy { double applyDiscount(double price); } class SeasonalDiscount implements DiscountStrategy { @Override public double applyDiscount(double price) { return price * 0.1; } } class ClearanceDiscount implements DiscountStrategy { @Override public double applyDiscount(double price) { return price * 0.5; } } class DiscountService { public double calculateDiscount(DiscountStrategy strategy, double price) { return strategy.applyDiscount(price); } }
3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)
- 설명: 서브 클래스는 언제나 자신의 기반 클래스(Base class)로 교체할 수 있어야 한다. 즉, 자식 클래스는 부모 클래스의 기능을 온전히 수행해야 하며, 부모 클래스의 계약을 위반해서는 안 된다.
Java 코드 예시:
// 위반된 예시: Square는 Rectangle을 상속하지만, 기능이 왜곡됨 class Rectangle { protected int width; protected int height; public void setWidth(int width) { this.width = width; } public void setHeight(int height) { this.height = height; } public int getArea() { return width * height; } } class Square extends Rectangle { @Override public void setWidth(int width) { this.width = width; this.height = width; // 정사각형이므로 width와 height가 같아야 함 } @Override public void setHeight(int height) { this.height = height; this.width = height; // 정사각형이므로 width와 height가 같아야 함 } } // 수정된 예시: 상속 관계를 피하고, 개념을 분리함 interface Shape { int getArea(); } class Rectangle implements Shape { private int width; private int height; public Rectangle(int width, int height) { this.width = width; this.height = height; } @Override public int getArea() { return width * height; } } class Square implements Shape { private int side; public Square(int side) { this.side = side; } @Override public int getArea() { return side * side; } }
4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)
- 설명: 클라이언트는 자신이 사용하지 않는 인터페이스에 의존하지 않아야 한다. 즉, 인터페이스는 특정 클라이언트를 위한 최소한의 기능만을 포함해야 한다.
Java 코드 예시:
// 위반된 예시: 모든 기능을 하나의 인터페이스에 몰아넣음 interface Worker { void work(); void eat(); } class HumanWorker implements Worker { @Override public void work() { System.out.println("Human working"); } @Override public void eat() { System.out.println("Human eating"); } } class RobotWorker implements Worker { @Override public void work() { System.out.println("Robot working"); } // Robot은 eat() 메소드를 필요로 하지 않지만, 구현해야 함 @Override public void eat() { throw new UnsupportedOperationException("Robots do not eat"); } } // 수정된 예시: 인터페이스를 분리하여 필요 없는 메소드 구현을 방지함 interface Workable { void work(); } interface Eatable { void eat(); } class HumanWorker implements Workable, Eatable { @Override public void work() { System.out.println("Human working"); } @Override public void eat() { System.out.println("Human eating"); } } class RobotWorker implements Workable { @Override public void work() { System.out.println("Robot working"); } }
5. 의존 역전 원칙 (Dependency Inversion Principle, DIP)
- 설명: 고수준 모듈(정책 결정)이 저수준 모듈(세부 구현)에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다. 또한, 추상화는 구체적인 것에 의존해서는 안 된다. 즉, 구현보다 인터페이스에 의존해야 한다.
Java 코드 예시:
// 위반된 예시: 고수준 모듈이 저수준 모듈에 의존함 class Keyboard { public void type() { System.out.println("Typing on keyboard"); } } class Computer { private Keyboard keyboard; public Computer() { this.keyboard = new Keyboard(); // 저수준 모듈에 직접 의존 } public void typeOnKeyboard() { keyboard.type(); } } // 수정된 예시: 인터페이스를 통해 의존성을 역전함 interface InputDevice { void input(); } class Keyboard implements InputDevice { @Override public void input() { System.out.println("Typing on keyboard"); } } class Computer { private InputDevice inputDevice; public Computer(InputDevice inputDevice) { this.inputDevice = inputDevice; // 추상화에 의존 } public void typeOnInputDevice() { inputDevice.input(); } }
이렇게 SOLID 원칙을 따르면, 소프트웨어 시스템의 유연성과 유지보수성이 높아지며, 코드 재사용성도 향상됩니다. 각 원칙은 독립적으로 적용될 수 있지만, 함께 사용되었을 때 가장 큰 효과를 발휘합니다.
SOLID는 객체 지향 소프트웨어 개발에서 좋은 설계를 위한 다섯 가지 원칙의 약어입니다. 이 원칙들은 로버트 C. 마틴(애칭: "아저씨" 마틴)에 의해 소개되었으며, 유지보수가 쉽고 확장 가능하며 재사용 가능한 소프트웨어 시스템을 만드는 데 도움을 줍니다. 각 원칙의 의미와 목적은 다음과 같습니다:
- **단일 책임 원칙 (Single Responsibility Principle, SRP)**:
- **설명**: 클래스는 단 하나의 책임만 가져야 합니다. 즉, 클래스는 한 가지 기능만 담당해야 합니다.
- **목적**: 변경의 이유를 최소화하여 클래스를 보다 단순하고 이해하기 쉽게 만듭니다. 이는 유지보수와 확장이 용이하도록 합니다.
- **개방-폐쇄 원칙 (Open/Closed Principle, OCP)**:
- **설명**: 소프트웨어 요소(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 합니다.
- **목적**: 새로운 기능을 추가할 때 기존 코드를 수정하지 않고도 시스템을 확장할 수 있도록 합니다. 이는 버그가 발생할 가능성을 줄이고 시스템 안정성을 높입니다.
- **리스코프 치환 원칙 (Liskov Substitution Principle, LSP)**:
- **설명**: 서브타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 합니다. 즉, 서브 클래스는 부모 클래스의 기능을 온전히 수행해야 합니다.
- **목적**: 객체 지향 시스템에서 다형성을 사용하여 코드를 더 유연하게 만들고, 예상치 못한 동작을 방지합니다.
- **인터페이스 분리 원칙 (Interface Segregation Principle, ISP)**:
- **설명**: 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫습니다. 즉, 인터페이스는 클라이언트가 사용하는 메서드들만 포함해야 합니다.
- **목적**: 인터페이스의 크기를 작게 만들어 클라이언트가 필요하지 않는 메서드를 구현하지 않도록 합니다. 이는 클라이언트와의 결합도를 낮추고 시스템의 유연성을 높입니다.
- **의존 역전 원칙 (Dependency Inversion Principle, DIP)**:
- **설명**: 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 합니다. 또한, 추상화는 구체적인 사항에 의존하지 않아야 합니다.
- **목적**: 모듈 간의 의존성을 줄이고, 변화에 더 잘 대처할 수 있도록 합니다. 이를 통해 시스템을 더 유연하고 재사용 가능하게 만듭니다.
이 다섯 가지 원칙을 따르면 객체 지향 시스템의 설계가 더 견고해지고 유지보수가 용이하며, 확장이 쉬워집니다. 각 원칙은 코드의 품질을 높이기 위해 특정한 측면에서의 모범 사례를 따르도록 합니다.
물론입니다. 각 원칙을 객체지향 언어(여기서는 Python)로 설명해 보겠습니다.
1. 단일 책임 원칙 (SRP)
**잘못된 예:**
```python
class User:
def __init__(self, username, email):
self.username = username
self.email = emaildef save\_to\_database(self): # 데이터베이스에 사용자 정보 저장 pass def send\_email(self, message): # 이메일 보내기 pass
```
**SRP 적용 예:**
```python
class User:
def __init__(self, username, email):
self.username = username
self.email = emailclass UserRepository:
def save(self, user):
# 데이터베이스에 사용자 정보 저장
passclass EmailService:
def send_email(self, user, message):
# 이메일 보내기
pass
```2. 개방-폐쇄 원칙 (OCP)
**잘못된 예:**
```python
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = heightclass AreaCalculator:
def calculate(self, shape):
if isinstance(shape, Rectangle):
return shape.width * shape.height
# 새로운 도형을 추가하려면 이 메서드를 수정해야 함
```**OCP 적용 예:**
```python
class Shape:
def area(self):
passclass Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = heightdef area(self): return self.width \* self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radiusdef area(self): return 3.14 \* self.radius \* self.radius
class AreaCalculator:
def calculate(self, shape):
return shape.area()
```3. 리스코프 치환 원칙 (LSP)
**잘못된 예:**
```python
class Bird:
def fly(self):
passclass Penguin(Bird):
def fly(self):
raise Exception("펭귄은 날 수 없습니다.")
```**LSP 적용 예:**
```python
class Bird:
passclass FlyingBird(Bird):
def fly(self):
passclass Sparrow(FlyingBird):
def fly(self):
# 참새가 나는 방법 구현
passclass Penguin(Bird):
# 펭귄은 날지 않으므로 fly 메서드가 없음
pass
```4. 인터페이스 분리 원칙 (ISP)
**잘못된 예:**
```python
class Worker:
def work(self):
passdef eat(self): pass
class HumanWorker(Worker):
def work(self):
# 일하는 방법 구현
passdef eat(self): # 먹는 방법 구현 pass
class RobotWorker(Worker):
def work(self):
# 일하는 방법 구현
passdef eat(self): raise Exception("로봇은 먹을 수 없습니다.")
```
**ISP 적용 예:**
```python
class Workable:
def work(self):
passclass Eatable:
def eat(self):
passclass HumanWorker(Workable, Eatable):
def work(self):
# 일하는 방법 구현
passdef eat(self): # 먹는 방법 구현 pass
class RobotWorker(Workable):
def work(self):
# 일하는 방법 구현
pass
```5. 의존 역전 원칙 (DIP)
**잘못된 예:**
```python
class LightBulb:
def turn_on(self):
passdef turn\_off(self): pass
class Switch:
def __init__(self, light_bulb):
self.light_bulb = light_bulbdef operate(self): self.light\_bulb.turn\_on() # 스위치가 직접 LightBulb 클래스에 의존
```
**DIP 적용 예:**
```python
class Switchable:
def turn_on(self):
passdef turn\_off(self): pass
class LightBulb(Switchable):
def turn_on(self):
# 전구 켜기 구현
passdef turn\_off(self): # 전구 끄기 구현 pass
class Switch:
def __init__(self, device: Switchable):
self.device = devicedef operate(self): self.device.turn\_on() # 스위치가 추상화된 Switchable 인터페이스에 의존
```
이 예제들은 SOLID 원칙을 적용하여 객체 지향 설계를 개선하는 방법을 보여줍니다. 각 원칙은 코드의 유지보수성과 확장성을 높이는 데 도움이 됩니다.
728x90반응형'전산학 > 소프트웨어공학' 카테고리의 다른 글
기능적 요구사항 vs 비기능적 요구사항 (0) 2024.08.01 상향식 비용 산정기법 vs 하향식 비용 산정기법 (0) 2024.08.01 비즈니스 관리 용어: RTO, RPO, MTD, WRT, MTRS (0) 2024.08.01 스토리보드, 목업 비교하기 (0) 2024.08.01 결합도(Coupling)와 응집도(Cohesion) 개념과 순서 및 자바 코드 (0) 2024.07.30