객체지향 5원칙 이란?
로버트 C. 마틴이 1995년 자신의 저서 "Agile Software Development: Principles, Patterns, and Practices"에서 처음 소개한 원칙으로서 객체지향 프로그래밍을 할 때 지켜야할 5개의 규칙을 의미합니다.
소프트웨어의 품질을 높이고 유지보수성을 향상시킨다고 합니다만, 실무에서는 적당히 쓰지 않으면 오히려 생산성이 떨어지는 경우도 있습니다..
저때는 대학 1학년때의 교양 시험문제로 출제되었던거로 기억하는데요
솔리드(SOLID)라고 외우면 편합니다.
1. 단일 책임 원칙(SRP): 하나의 객체는 하나의 책임만 가져야 한다.
( Single Responsibility Principle )
단일 책임 원칙은 하나의 객체는 하나의 책임만 가져야 한다는 원칙입니다.
즉, 하나의 객체는 하나의 기능만 수행해야 합니다.
이를 통해 클래스의 응집도를 높이고 클래스 간의 결합도를 줄여 유지보수와 확장이 용이해집니다.
// 예제: 잘못된 구현
class User {
public void saveUser(User user) {
// 사용자를 저장하는 로직
}
public void sendEmail(User user) {
// 이메일을 보내는 로직
}
}
// 예제: 개선된 구현
class UserRepository {
public void saveUser(User user) {
// 사용자를 저장하는 로직
}
}
class EmailService {
public void sendEmail(User user) {
// 이메일을 보내는 로직
}
}
2. 개방-폐쇄 원칙(OCP): 객체는 확장에 대해서는 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다.
( Open-Closed Principle )
개방-폐쇄 원칙은 객체는 확장에 대해서는 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다는 원칙입니다.
즉, 새로운 기능을 추가하기 위해서는 기존 코드를 수정하지 않고, 새로운 코드를 추가할 수 있어야 합니다.
// 예제: 잘못된 구현
class Shape {
public void draw() {
// 도형을 그리는 로직
}
}
// 새로운 도형 추가 시 클래스를 수정해야 함
// 예제: 개선된 구현
interface Shape {
void draw();
}
class Circle implements Shape {
public void draw() {
// 원을 그리는 로직
}
}
class Rectangle implements Shape {
public void draw() {
// 사각형을 그리는 로직
}
}
3. 리스코프 치환 원칙(LSP): 하위 클래스의 인스턴스는 상위 클래스의 인스턴스로 대체될 수 있어야 한다.
( Liskov Substitution Principl )
리스코프 치환 원칙은 하위 클래스의 인스턴스는 상위 클래스의 인스턴스로 대체될 수 있어야 한다는 원칙입니다.
즉, 하위 클래스는 상위 클래스의 모든 기능을 동일하게 수행할 수 있어야 합니다.
( 서브 타입은 언제나 자신의 기반 타입으로 대체할 수 있어야 합니다. )
// 예제: 잘못된 구현
class Bird {
public void fly() {
// 새가 날아가는 로직
}
}
class Ostrich extends Bird {
public void fly() {
// 타조가 날지 못하는데 날아가는 로직
}
}
// 예제: 개선된 구현
interface Bird {
void fly();
}
class Sparrow implements Bird {
public void fly() {
// 참새가 날아가는 로직
}
}
class Ostrich implements Bird {
public void fly() {
throw new UnsupportedOperationException("타조는 날지 못합니다.");
}
}
4. 인터페이스 분리 원칙(ISP): 객체는 자신과 관련이 없는 인터페이스에 의존해서는 안 된다.
( Interface Segregation Principle )
인터페이스 분리 원칙은 객체는 자신과 관련이 없는 인터페이스에 의존해서는 안 된다는 원칙입니다.
즉, 객체는 자신이 사용하는 인터페이스만 의존해야 합니다.
// 예제: 잘못된 구현
interface Worker {
void work();
void eat();
}
class Programmer implements Worker {
public void work() {
// 개발자가 일하는 로직
}
public void eat() {
// 개발자가 식사하는 로직
}
}
class Robot implements Worker {
public void work() {
// 로봇이 일하는 로직
}
public void eat() {
// 로봇이 식사하는 로직 (로봇은 먹지 않음)
}
}
// 예제: 개선된 구현
interface Workable {
void work();
}
interface Eatable {
void eat();
}
class Programmer implements Workable, Eatable {
public void work() {
// 개발자가 일하는 로직
}
public void eat() {
// 개발자가 식사하는 로직
}
}
class Robot implements Workable {
public void work() {
// 로봇이 일하는 로직
}
}
5. 의존 역전 원칙(DIP): 고수준 모듈은 저수준 모듈에 의존해서는 안 되고, 추상화에 의존해야 한다.
( Dependency Inversion Principle )
의존 역전 원칙은 고수준 모듈은 저수준 모듈에 의존해서는 안 되고, 추상화에 의존해야 한다는 원칙입니다.
즉, 고수준 모듈은 구체적인 클래스에 의존해서는 안 되고, 추상적인 인터이스에 의존해야 합니다.
// Bad example
class Car {
private Engine engine;
public Car() {
this.engine = new Engine();
}
public void start() {
engine.start();
}
}
// Good example
interface Engine {
void start();
}
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.start();
}
}
class GasolineEngine implements Engine {
public void start() {
// 가솔린 엔진을 시작하는 로직
}
}
class ElectricEngine implements Engine {
public void start() {
// 전기 엔진을 시작하는 로직
}
}
실무에서는 아무래도 framework을 용하다보니
인터페이스 분리 원칙( ISP), 의존 역전 원칙 (DIP) 을 많이 쓰고 있습니다.
5원칙을 한번 더 정리하자면
1. 단일 책임 원칙(SRP)
2. 개방-폐쇄 원칙(OCP)
3. 리스코프 치환 원칙(LSP)
4. 인터페이스 분리 원칙(ISP)
5. 의존 역전 원칙(DIP)
사실 이런 원칙을 지키는 것도 좋지만
나만 이해하는 코드가 아닌
가독성이 좋은 코드를 만드려고 노력하는 게 더 좋은 습관이 아닐까 싶습니다.