하나의 클래스는 하나의 책임만을 갖고 있어야 한다는 원칙이다.
즉, 클래스를 변경하는 이유는 하나를 가져야 한다. 이를 통해 클래스가 변경이 되어도 다른 기능에 미치는 영향을 최소화할 수 있다.
Report라는 클래스가 있고 해당 클래스는 보고서를 출력하고 저장하는 두가지 책임을 갖고 있다고 하자
class Report {
void printReport() {
// 출력 로직
}
void saveToFile(String fileName) {
// 저장 로직
}
}
단일 원칙에 따라 두가지 책임을 갖고있기 때문에 하나의 클래스는 하나의 책임만 갖도록 수정
class Report {
void printReport() {
// 출력 로직
}
}
class ReportSaver {
void saveToFile(String fileName, Report report) {
// 저장 로직
}
}
소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하며, 변경에 있어서는 반대로 닫혀 있어야 한다. 이를 위해 추상화와 상속 등의 개냠을 활용할 수 있다.
abstract class Shap{
abstract void draw();
}
class Circle extends Shape {
void draw() {
System.out.println("원 그리기");
}
}
class Squara extends Shape {
void draw() {
System.out.println("정사각형 그리기");
}
}
class ShapeDrawer {
void draw(Shape shape) {
shape.draw();
}
}
ShapeDrawer 클래스는 확장에 열려있고, 변경에는 닫혀있다.
새로운 Shape 유형이 추가 되더라도 ShapeDrawer 클래스를 수정할 필요가 없다.
하위 타입의 객체는 상위 타입의 객체를 대체할 수 있어야 한다는 원칙이다.
상위 클래스의 객체를 하위 클래스 객체로 대체하여도 시스템의 동작에 영향을 미치지 않아야 한다.
이 원칙은 상속 구조의 올바른 사용을 보장한다.
class Bird {
void fly() {
System.out.println("새가 날다");
}
}
class Ostrich extends Brid {
void fly() {
throw new UnsupportedOperationException();
}
}
타조 클래스는 새의 하위 클래스다. 그러나 타조 클래스는 fly 메소드를 지원하지 않으므로, 새 타입을 타조로 대체하는 경우는 리스코프 치환의 원칙에 위배가 된다.
사용하지 않는 인터페이스는 구현하지 않아야 한다는 원칙이다.
인터페이스를 작고 구체적으로 유지하여 클라이언트가 자신이 사용하지 않는 메소드에 의존하지 않도록 해야한다.
interface Worker {
void work();
void eat();
}
class HumanWorker implements Worker {
public void work() {
// 작업 수행
}
public void eat() {
// 식사 수행
}
}
class RobotWorker implements Worker {
public void work() {
// 작업 수행
}
public void eat() {
// 로봇은 식사가 필요 없으므로, 이 메소드 구현은 부적합
}
}
Worker 인터페이스는 work와 eat 메소드 두가지를 갖고 있다. 그러나 RobotWorker는 eat을 구현할 필요가 없다.
이는 인터페이스만 구현하도록 할 수 있다.
고수준 모듈은 저수준 모듈에 의존해서는 안되며, 둘 다 추상화에 의존해야 한다는 원칙
추상화는 세부사항에 의존해서는 안되며, 세부 사항이 추상화에 의존을 해야한다. 이 원칙을 통하여 의존성 방향을 역전시켜 변경에 더 유연하게 대응이 가능하다.
interface Switchable {
void turnOn();
void turnOff();
}
class LightBulb implements Switchable {
public void turnOn() {
System.out.println("전구를 켭니다");
}
public void turnOff() {
System.out.println("전구를 끕니다");
}
}
class Switch {
private Switchable device;
public Switch(Switchable device) {
this.device = device;
}
void toggle() {
// 토글 로직
// device 상태를 변경
}
}
Switch 클래스는 Switchable 인터페이스를 통해 LightBuld 클래스에 의존한다.
이는 고수준 모듈 (Switch)과 저수준 모듈 (LightBulb)사이의 직접적인 의존 관계를 제거하며, Switch클래스를 다양한 Switchable 구현체에 대해 사용할 수 있게 하여 의존선을 역전 원칙에 따른다.