객체 지향 설계의 5대 원칙 SOLID

현정재·2024년 6월 27일
0

객체 지향 설계의 5대 원칙 SOLID

SOLID 원칙이란 객체지향 설계에서 지켜줘야 할 5개의 소프트웨어 개발 원칙을 말합니다. 좋은 설계란 시스템에 새로운 요구사항이나 변경사항이 있을 때, 영향을 받는 범위가 적은 구조를 말합니다. 그래서 시스템에 예상하지 못한 변경사항이 발생하더라도, 유연하게 대처하고 이후에 확장성이 있는 시스템 구조를 만들 수 있습니다.

SRP(Single Responsibility Principle): 단일 책임 원칙

단일 책임 원칙은 클래스(객체)는 단 하나의 책임만 가져야 한다는 원칙입니다. 여기서 책임이라는 의미는 하나의 '기능 담당'으로 보면 됩니다. 즉, 하나의 클래스는 하나의 기능을 담당하여 하나의 책임을 수행하는 데 집중되도록 클래스를 따로따로 여러 개 설계하라는 원칙입니다. 만일 하나의 클래스에 기능(책임)이 여러 개 있다면 기능 변경(수정)이 일어났을 때 수정해야 할 코드가 많아집니다.

Bad Case:

class A선수 {
    public void 육상100m() {
        System.out.println("100m 달리기");
    }
    public void 수영50m() {
        System.out.println("50m 수영하기");
    }
}

운동을 잘하는 A 선수가 있습니다. 만약 시합이 같은 날 같은 시간에 있다면 A 선수는 두 개의 경기를 
책임져야 하는데 혼자서는 힘이 듭니다. 단 하나의 책임만 가져야 하는데 육상 경기와 수영 경기는 
서로 다른 책임, 두 개의 책임을 가지고 있습니다.

Good Case:

class 육상선수 {
    public void 육상100m() {
        System.out.println("100m 달리기");
    }
}

class 수영선수 {
    public void 수영50m() {
        System.out.println("50m 수영하기");
    }
}

각각의 육상선수와 수영선수가 있다면 같은 날 같은 시간에 시합이 있더라도 
한 경기만 책임을 지면 됩니다.

OCP(Open Closed Principle): 개방 폐쇄 원칙

OCP 원칙은 클래스는 '확장에 열려있어야 하며, 수정에는 닫혀있어야 한다'를 뜻합니다. 기능 추가 요청이 오면 클래스를 확장을 통해 손쉽게 구현하면서, 확장에 따른 클래스 수정은 최소화하도록 프로그램을 작성해야 하는 설계 기법입니다.

Bad Case:

class 좋아하는운동 {
    public void 달리기() {
        System.out.println("달리다");
    }
    public void 축구() {
        System.out.println("축구하다");
    }
}

좋아하는 운동이 달리기와 축구가 있습니다. TV를 보니 테니스도 재미있을 것 같아서 
테니스를 배우기 시작했는데 테니스 매력에 빠졌습니다. 테니스를 추가하려면 기존 
'좋아하는운동' 클래스를 수정해야 합니다. 
추가해야 할 운동이 하루에 하나씩 생긴다면 좋아하는운동 클래스를 계속 수정해야 합니다.

Good Case:

interface 운동 {
    void 연습();
}

class 달리기 implements 운동 {
    public void 연습() {
        System.out.println("달리기 연습");
    }
}

class 수영 implements 운동 {
    public void 연습() {
        System.out.println("수영 연습");
    }
}

class 축구 implements 운동 {
    public void 연습() {
        System.out.println("축구 연습");
    }
}

운동을 인터페이스로 정의하여 다양한 운동 종류를 확장할 수 있게 합니다. 
새로운 운동 종류는 인터페이스를 구현하기만 하면 됩니다.

LSP(Liskov Substitution Principle): 리스코프 치환 원칙

LSP 원칙은 서브 타입은 언제나 기반(부모) 타입으로 교체할 수 있어야 한다는 원칙입니다. 쉽게 말하면 LSP는 다형성 원리를 이용하기 위한 원칙 개념으로 보면 됩니다. 상위 클래스 타입으로 객체를 선언하여 하위 클래스의 인스턴스를 받으면, 업캐스팅된 상태에서 부모의 메서드를 사용해도 동작이 의도대로 흘러가야 하는 것을 의미합니다.

Good Case:

class 커피 {
    public void 금방만들게요() {
        System.out.println("커피 만드는 중.");
    }
    public void 커피나왔습니다() {
        System.out.println("커피 드릴게요.");
    }
}

class 아이스아메리카노 extends 커피 {
    @Override
    public void 금방만들게요() {
        System.out.println("아이스 아메리카노 만드는 중.");
    }
    @Override
    public void 커피나왔습니다() {
        System.out.println("아이스 아메리카노 드릴게요.");
    }
}

class 핫아메리카노 extends 커피 {
    @Override
    public void 금방만들게요() {
        System.out.println("핫 아메리카노 만드는 중.");
    }
    @Override
    public void 커피나왔습니다() {
        System.out.println("핫 아메리카노 드릴게요.");
    }
}

public class Main {
    public static void main(String[] args) {
    	// 부모 클래스 타입으로 자식 객체 생성
        커피 coffee = new 아이스아메리카노();
        processCoffee(coffee); // 출력: 아이스 아메리카노 만드는 중.
                               //      아이스 아메리카노 드릴게요.
        
        // 부모 클래스 타입으로 자식 객체 생성
        coffee = new 핫아메리카노(); // 중간에 다른 자식 클래스로 치환
        processCoffee(coffee); // 출력: 핫 아메리카노 만드는 중.
                               //      핫 아메리카노 드릴게요.
    }
    
    public static void processCoffee(커피 coffee) {
        coffee.금방만들게요();
        coffee.커피나왔습니다();
    }
}

부모 클래스 타입으로 자식 클래스를 사용하고 있고, processCoffee(커피 coffee) 메서드에 
자식 객체들을 매개변수로 전달되었을 때도 잘 동작합니다. 
또한 메서드 오버라이드를 통해 부모 클래스 메서드를 자식 클래스에 맞게 재정의 했습니다.
profile
wonttock

0개의 댓글