객체지향 프로그래밍의 5가지 주요 원칙은 소프트웨어 설계의 품질을 높이고 유지보수성을 강화하기 위한 지침이다.
SRP, OCP, LSP, ISP, DIP 5가지가 있다.
단일 책임 원칙 (SRP, Single Responsibility Principle)
하나의 클래스는 단 하나의 책임만 가져야 한다. (= 클래스)
- 목적 : 클래스를 변경해야 하는 이유가 단 하나뿐이어야 한다.
- 장점 : 클래스의 역할이 명확해지고 코드의 가독성과 유지보수성이 향상된다.
class Person {
void cook(); //요리하기 - 요리사
void plate(); //플레이팅 - 요리사
void order(); //주문하기 - 손님
void pickup(); //픽업하기 - 손님
void eat(); //먹기 - 손님
}
class Chef {
void cook();
}
class Customer {
void order();
void pickup();
void eat();
}
개방-폐쇄 원칙 (OCP, Open/Closed Principle)
클래스는 확장에는 열려있어야 하고, 수정에는 닫혀있어야 한다.
- 확장에는 열려있다 : 요구사항이 변경될 때, 새로운 동작을 추가하여 애플리케이션의 기능을 확장할 수 있다.
- 수정에는 닫혀있다 : 기존의 코드를 수정하지 않고 애플리케이션의 동작을 추가하거나 변경할 수 있다.
- 목적 : 기존 코드를 수정하지 않고 새로운 기능을 추가할 수 있도록 설계
- 장점 : 코드 변경으로 인한 버그를 최소화할 수 있다.
운전자는 자동차가 변경되어도 엑셀을 밟거나 브레이크를 밟는 것에 대한 영향을 받지 않는다
= 운전자가 수정에는 닫혀있다.
자동차는 Bus나 Truck 외에도 다른 자동차 종류로 클래스를 인터페이스를 통해서 확장할 수 있고, 운전자의 역할에는 영향을 끼치지 않는다.
= 확장에는 열려있다.
interface Car {
void accel();
void brake();
}
class Bus implements Car {
void accel() { //속도 7 증가 };
void brake() { //속도 5 감소 };
}
class Truck implements Car {
void accel() { //속도 5 증가 };
void brake() { //속도 3 감소 };
}
리스코프 치환 원칙 (LSP, Liskov Substitution Principle)
상위 클래스 객체는 하위 클래스 객체로 대체할 수 있어야 한다.
- 목적 : 다형성을 올바르게 구현하고 프로그램의 일관성을 유지
- 장점 : 상속 관계에서 프로그램 동작이 깨지지 않도록 보장한다.
class Car {
int speed;
void drive() {
this.speed += 10;
}
}
class Bus extends Car {
int speed;
int km;
@Override
void drive() {
// 부모 기능 그대로 수행
this.speed += 10;
// 하위 타입 기능 추가
this.km += 1;
}
}
인터페이스 분리 원칙 (ISP, Interface Segregation Principle)
클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안 된다.
- 목적 : 인터페이스를 세분화하여 필요한 메소드만 제공
- 장점 : 인터페이스를 작게 만들어서 구현 클래스가 불필요한 메소드를 갖지 않게 함
날 수 없는 동물도 있기 때문에, 동물 인터페이스를 사용하면 강아지는 날 수 없기 때문에 동물 인터페이스를 사용하지 못한다.
public interface Animal {
void walk();
void fly();
}
public class Dog implements Animal {
public void walk() {
// 강아지 걷기
}
public void fly() {
// 강아지는 날 수 없음
}
}
수정된 코드
// 걷기
public interface Walkable {
void walk();
}
// 날기
public interface Flyable {
void fly();
}
public class Dog implements Walkable {
public void walk() {
// 강아지 걷기
}
}
public class Bird implements Walkable, Flyable {
public void walk() {
// 새 걷기
}
public void fly() {
// 새 날기
}
}
의존 역전 원칙 (DIP, Dependency Inversion Principle)
상위 클래스는 하위 클래스에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다.
=> 구현 클래스에 의존하지 말고, 인터페이스에 의존해라
- 목적 : 구체 클래스가 아닌 추상화(인터페이스, 추상 클래스)에 의존하도록 설계
- 장점 : 모듈 간 결합도를 낮추고 유연성을 증가시킨다.
왼쪽은 DIP가 적용되어 있지 않아서 자동차가 일반 타이어와 스노우 타이어에 직접적으로 의존 관계를 맺고 있다.
타이어를 인터페이스로 만들어 이 인터페이스를 통해 구현체인 일반 타이어, 스노우 타이어 등을 적절하게 가질 수 있어야 한다.