SOLID 원칙

    SOLID 원칙이란?

    객체지향 설계에서 유지보수성과 확장성을 높이기 위한 5가지 방법

    이 원칙을 준수하면, 유연하고 변경에 강한 코드 작성 가능!


    단일 책임 원칙 (SRP)

    Single Responsibility Principle

     

    하나의 클래스는 단 하나의 책임(기능) 만 가져야 함

    즉, 하나의 변경 이유(Reason to Change) 만 가져야 함

     

    ⚠️ 여러 기능이 한 클래스에 섞이면 유지보수가 어려움!

     

    SRP 위반

    class Report {
        void generateReport() {
            // 리포트 생성 로직
        }
    
        void saveToFile() {
            // 파일 저장 로직
        }
    }

     

    ➡︎ 리포트 생성파일 저장이라는 두 가지 책임을 가짐

     

    SRP 적용

    class ReportGenerator {
        void generateReport() {
            // 리포트 생성 로직
        }
    }
    
    class FileSaver {
        void saveToFile() {
            // 파일 저장 로직
        }
    }

    개방-폐쇄 원칙 (OCP)

    Open/Closed Principle

     

    코드는 확장에는 열려(Open) 있고, 변경에는 닫혀(Closed) 있어야 함

    즉, 새로운 기능을 추가할 때 기존 코드를 수정하지 않아야 함

     

    OCP 위반

    class PaymentService {
        void pay(String paymentType) {
            if (paymentType.equals("CreditCard")) {
                // 신용카드 결제 로직
            } else if (paymentType.equals("PayPal")) {
                // PayPal 결제 로직
            }
        }
    }

     

    ➡︎ 새로운 결제 방식이 추가될 때마다 기존 코드를 수정해야 함

     

    OCP 적용

    interface Payment {
        void pay();
    }
    
    class CreditCardPayment implements Payment {
        public void pay() {
            // 신용카드 결제 로직
        }
    }
    
    class PayPalPayment implements Payment {
        public void pay() {
            // PayPal 결제 로직
        }
    }
    
    class PaymentService {
        void processPayment(Payment payment) {
            payment.pay();
        }
    }

     

    ➡︎ 다형성을 활용해서 기존 코드를 수정하지 않고, 새로운 결제 방식 추가 가능!


    리스코프 치환 원칙(LSP)

    Liskov Substitution Principle

     

    자식 클래스는 부모 클래스를 대체할 수 있어야 함

    즉, 부모 클래스를 상속받은 모든 클래스는  부모의 역할을 온전히 수행할 수 있어야 함

     

    LSP 위반

    class Rectangle {
        int width, height;
    
        void setWidth(int width) { this.width = width; }
        void setHeight(int height) { this.height = height; }
    }
    
    class Square extends Rectangle {
        void setWidth(int width) { 
            this.width = width;
            this.height = width; // 가로와 세로를 동일하게 설정 (LSP 위반)
        }
    }

     

    ➡︎ Square 클래스는 Rectangle 을 대체할 수 없음

     

    LSP 적용

    interface Shape {
        int getArea();
    }
    
    class Rectangle implements Shape {
        int width, height;
        public int getArea() { return width * height; }
    }
    
    class Square implements Shape {
        int side;
        public int getArea() { return side * side; }
    }

     

    ➡︎ 공통 인터페이스를 활용

     


    인터페이스 분리 원칙(ISP)

    Interface Segregation Principle

     

    하나의 큰 인터페이스보다, 여러 개의 작은 인터페이스로 분리하는 게 좋음

    즉, 사용하지 않는 메서드에 의존하면 안됨

     

    ISP 위반

    interface Worker {
        void work();
        void eat();
    }
    
    class Robot implements Worker {
        public void work() { System.out.println("일을 합니다."); }
        public void eat() { throw new UnsupportedOperationException(); } // 로봇은 먹을 수 없음 (ISP 위반)
    }

     

    ➡︎ Roboteat() 메서드를 가질 필요가 없음

     

    ISP 적용

    interface Workable {
        void work();
    }
    
    interface Eatable {
        void eat();
    }
    
    class Robot implements Workable {
        public void work() { System.out.println("일을 합니다."); }
    }

     

    ➡︎ 인터페이스를 분리하여 불필요한 의존성 제거


    의존성 역전 원칙(DIP)

    Dependency Inversion Principle

     

    상위 모듈이 하위 모듈에 의존하면 안 됨

    즉, 세부 구현이 아니라 추상화(Interface)에 의존해야 함

     

    DIP 위반

    class MySQLDatabase {
        void connect() { System.out.println("MySQL 연결"); }
    }
    
    class DataManager {
        MySQLDatabase database = new MySQLDatabase(); // 특정 DB에 강하게 결합 (DIP 위반)
    }

     

    ➡︎ DB 를 변경하려면 DataManager 코드를 수정해야 함

     

    DIP 적용

    interface Database {  // 추상화 적용
        void connect();
    }
    
    class MySQLDatabase implements Database {
        public void connect() { System.out.println("MySQL 연결"); }
    }
    
    class PostgreSQLDatabase implements Database {
        public void connect() { System.out.println("PostgreSQL 연결"); }
    }
    
    class DataManager {
        Database database;
    
        DataManager(Database database) { // 의존성 주입 (DIP 적용)
            this.database = database;
        }
    }

     

    ➡︎ 추상화 적용 ➔ 인터페이스를 활용하여 유연한 설계 가능


    추상화

    추상화(Abstraction) 란?

    객체의 핵심점인 특징만 노출하고, 불필요한 세부 사항은 숨기는 개념

    인터페이스추상 클래스로 구현 가능

    코드의 복잡도/결합도를 낮추고, 확장성을 높임

     

    비교 항목 인터페이스 추상 클래스
    목적 행동 정의 기본 기능 제공
    메서드 구현 전부 구현 X (default 메서드 제외) 일부 구현 가능
    다중 상속 가능 - implements 불가능 - extends
    사용 예시 다양한 객체가 동일한 동작을 할 때 공통 기능을 제공하면서 일부만 구현이 필요할 때

     

    * 자세한 내용은 아래 글의 인터페이스 vs 추상 클래스 내용 참고

     

    상속(Inheritance)과 다형성(Polymorphism)

    상속상속(Inheritance) 이란?기존 클래스를 확장하여 새로운 클래스를 만드는 개념코드 재사용성 증가 및 객체 간 계층 구조 형성 상속의 기본 구조// 부모 클래스 (Super Class)class Animal { String name; voi

    jelliclesu.tistory.com


    주요 포인트

    • SOLID 원칙은 단순 암기가 아니라 어떻게 적용되는 지 알아야 함
      • SRP: 하나의 클래스는 하나의 책임만 가져야 함
      • OCP: 코드는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 함
      • LSP: 자식 클래스는 부모 클래스를 대체할 수 있어야 함
      • ISP: 하나의 큰 인터페이스보다, 여러 개의 작은 인터페이스로 분리하는 게 좋음
      • DIP: 상위 모듈이 하위 모듈에 의존하면 안 됨
    • 추상화가 왜 필요한지, 인터페이스와 추상 클래스의 차이는 무엇인지 명확하게 알아야 함
      • 추상화: 객체의 핵심적인 특징만 노출하고, 불필요한 세부 사항은 숨김
      • 장점: 코드의 결합도는 낮아지고, 유지보수성 향상
    • SOLID 원칙과 추상화를 연계하여 유연한 설계 방법을 설명할 줄 알아야 함

    + Recent posts