CS/기타

(CS) SOLID 원칙

흰색텀블러 2025. 1. 7. 10:49

급하신 분들은 마지막에 요약해놨으니, 그부분만 보셔도 무관합니다!

 

들어가기에 앞서...

♤ 객체 지향 프로그래밍?

  • 정의
    • 절차적 프로그래밍 : 반복될 가능성이 있는 것들을 재사용이 가능한 함수로 만들어 사용하는 방식
    • OOP : 특정한 개념의 함수와 자료형을 함께 묶어서 관리하기 위해 탄생한 방법
    • 객체 내부에 자료형(field)와 함수(method)가 같이 존재함.
    • 중요한 4가지 특징
      1. 추상화
        • 필요로 하는 속성이나 행동을 추출하는 작업
        • 세부적인 사물들의 공통적인 특징을 파악 후, 하나의 집합으로 만들어 내는것. (아우디, 벤츠는 모두 “자동차” → 자동차 라는 추상화 집합을 만듬!. 여기서 공통 특징을 만드는 것!!)
      2. 캡슐화
        • 낮은 결합도를 유지할 수 있도록 설계하는 것
        • 한 곳에서 변화가 일어나도, 다른 곳에 미치는 영향을 최소화시키는 것!
        • 결합도란, 어떤 기능을 실행할때, 다른 class 나 module이 얼마나 의존적인지를 나타내는 것.
        • 낮은 결합도, 높은 응징도? → 정보 은닉을 활용함! private를 선언하는 이유임..ㅎ
      3. 상속
        • 일반화 관계 라고 하며, 여러 개체들이 지닌 공통 특성을 부각 → 하나의 개념이나 법칙으로 성립하는 과정
        • 자식 클래스를 외부로부터 은닉하는 캡슐화의 일종
        • 기능의 확장 관점일때 사용하기!!!
        • 상위 클래스 변경 X, 불필요한 클래스 증가, 상속 잘못 → Composition으로 해결
        • Composition : 필드에서 다른 객체를 참조하는 방식
      4. 다형성 (핵심)
        • 부모 클래스의 메소드를 자식 클래스가 오버라이딩해서 자신의 역할에 맞게 활용하는 것!!!!!!!!!!!!!!!!!!!!!
        • 오버로딩 vs 오버라이딩
          • 오버로딩 : 메서드 이름은 동일, 매개변수의 타입, 개수가 다름.
            • 얘는 부생성자 등등
          • 오버라이딩 : 상위 클래스의 메서드를 하위 클래스에서 재정의!!
            • 얘는 그냥 super() 등

 

SOLID 원칙 

  • 객체 지향 프로그래밍에서 유지보수성과 확정성을 높이기 위한 설계 원칙
  • 즉, 코드를 더 유지보수 가능하고, 확장 가능하며, 유연한 설계로 만드는데 도움을 주는 것!

 

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

  • 원칙 : 클래스는 하나의 책임만 가져야 하며, 변경의 이유도 하나여야 한다.
  • 목표 : 코드의 응집도를 높이고, 수정이 필요할 때 영향을 최소화한다.
// 잘못된 설계: 한 클래스가 두 가지 책임(보고서 생성, 출력)을 가짐
public class Report {
    public void generateReport(String data) {
        System.out.println("Report generated with data: " + data);
    }

    public void printReport(String data) {
        System.out.println("Printing report: " + data);
    }
}

// 개선된 설계: 책임을 분리
public class ReportGenerator {
    public String generateReport(String data) {
        return "Report generated with data: " + data;
    }
}

public class ReportPrinter {
    public void printReport(String report) {
        System.out.println("Printing report: " + report);
    }
}

// 사용
public class Main {
    public static void main(String[] args) {
        ReportGenerator generator = new ReportGenerator();
        ReportPrinter printer = new ReportPrinter();

        String report = generator.generateReport("Sales Data");
        printer.printReport(report);
        // 출력: Printing report: Report generated with data: Sales Data
    }
}

 

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

  • 원칙 : SW 엔티티(클래스, 모듈, 함수 등)는 확장에는 열려 있어야하지만, 수정에는 닫혀 있어야한다.
  • 목표 : 기존 코드를 변경하지 않고도 기능을 확장할 수 있도록 설계해야한다.
// 잘못된 설계: 새로운 도형 추가 시 기존 코드 수정 필요
class Shape {
    public String type;
}

class DrawingTool {
    public void draw(Shape shape) {
        if (shape.type.equals("circle")) {
            System.out.println("Drawing a Circle");
        } else if (shape.type.equals("rectangle")) {
            System.out.println("Drawing a Rectangle");
        }
    }
}

// 개선된 설계: 인터페이스를 활용하여 새로운 도형 추가 가능
interface Shape {
    void draw();
}

class Circle implements Shape {
    public void draw() {
        System.out.println("Drawing a Circle");
    }
}

class Rectangle implements Shape {
    public void draw() {
        System.out.println("Drawing a Rectangle");
    }
}

class DrawingTool {
    public void drawShape(Shape shape) {
        shape.draw();
    }
}

// 사용
public class Main {
    public static void main(String[] args) {
        DrawingTool tool = new DrawingTool();
        
        Shape circle = new Circle();
        Shape rectangle = new Rectangle();

        tool.drawShape(circle);     // 출력: Drawing a Circle
        tool.drawShape(rectangle);  // 출력: Drawing a Rectangle
    }
}

 

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

  • 원칙 : 자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다.
  • 목표 : 상속 관계에서 부모 클래스를 사용하는 코드가 자식 클래스에서도 동작하도록 보장한다.
// 잘못된 설계: 자식 클래스가 부모 클래스의 행동을 위반
class Bird {
    public void fly() {
        System.out.println("Flying");
    }
}

class Penguin extends Bird {
    public void fly() {
        throw new UnsupportedOperationException("Penguins can't fly!");
    }
}

// 개선된 설계: 공통 행동을 추상화하고, 자식 클래스에서 올바르게 구현
abstract class Bird {
    public abstract void move();
}

class FlyingBird extends Bird {
    public void move() {
        System.out.println("Flying");
    }
}

class Penguin extends Bird {
    public void move() {
        System.out.println("Swimming");
    }
}

// 사용
public class Main {
    public static void main(String[] args) {
        Bird flyingBird = new FlyingBird();
        Bird penguin = new Penguin();

        flyingBird.move(); // 출력: Flying
        penguin.move();    // 출력: Swimming
    }
}

 

Interface Segregation Principle (인터페이스 분리 원칙) - ISP

  • 원칙 : 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.
  • 목표 : 큰 인터페이스를 작은 인터페이스로 분리하여 필요한 기능만 제공한다.
// 잘못된 설계: 모든 메서드를 가진 거대한 인터페이스
interface Machine {
    void print();
    void scan();
    void fax();
}

class Printer implements Machine {
    public void print() {
        System.out.println("Printing...");
    }

    public void scan() {
        throw new UnsupportedOperationException("Scan not supported");
    }

    public void fax() {
        throw new UnsupportedOperationException("Fax not supported");
    }
}

// 개선된 설계: 기능별로 인터페이스 분리
interface Printer {
    void print();
}

interface Scanner {
    void scan();
}

class SimplePrinter implements Printer {
    public void print() {
        System.out.println("Printing...");
    }
}

class AdvancedPrinter implements Printer, Scanner {
    public void print() {
        System.out.println("Printing...");
    }

    public void scan() {
        System.out.println("Scanning...");
    }
}

// 사용
public class Main {
    public static void main(String[] args) {
        Printer printer = new SimplePrinter();
        printer.print(); // 출력: Printing...

        AdvancedPrinter advancedPrinter = new AdvancedPrinter();
        advancedPrinter.print(); // 출력: Printing...
        advancedPrinter.scan();  // 출력: Scanning...
    }
}

 

Dependency Inversion Principle (의존성 역전 원칙) - DIP

  • 원칙 : 고수준 모듈은 저수준 모듈에 의존해서는 안되며, 둘 다 추상화에 의존해야 한다.
  • 목표 : 구체적인 구현이 아닌, 추상화에 의존함으로써 유연한 설계를 해야한다.
// 잘못된 설계: 고수준 모듈이 저수준 모듈에 직접 의존
class Light {
    public void turnOn() {
        System.out.println("Light On");
    }
}

class Switch {
    private Light light;

    public Switch(Light light) {
        this.light = light;
    }

    public void toggle() {
        light.turnOn();
    }
}

// 개선된 설계: 인터페이스를 통해 의존성 역전
interface Switchable {
    void turnOn();
}

class Light implements Switchable {
    public void turnOn() {
        System.out.println("Light On");
    }
}

class Fan implements Switchable {
    public void turnOn() {
        System.out.println("Fan On");
    }
}

class Switch {
    private Switchable device;

    public Switch(Switchable device) {
        this.device = device;
    }

    public void toggle() {
        device.turnOn();
    }
}

// 사용
public class Main {
    public static void main(String[] args) {
        Switchable light = new Light();
        Switchable fan = new Fan();

        Switch lightSwitch = new Switch(light);
        Switch fanSwitch = new Switch(fan);

        lightSwitch.toggle(); // 출력: Light On
        fanSwitch.toggle();   // 출력: Fan On
    }
}

 

전체 요약

  • S : 하나의 클래스는 하나의 책임만 가져야 한다
  • O : 기존 코드를 변경하지 않고 새로운 기능을 추가할 수 있어야 한다.
  • L : 부모 클래스 대신 자식 클래스를 사용할 수 있어야 한다.
  • I : 클라리언트는 자신이 사용하지 않는 인터페이스에 의존하지 않아야 한다.
  • D : 고수준 모듈과 저수준 모듈이 모두 추상화에 의존해야 한다.

 

'CS > 기타' 카테고리의 다른 글

(CS) 네트워크 - 네트워크 계층 IP  (0) 2025.03.22
(CS) 네트워크 - 물리 계층과 데이터 링크 계층  (0) 2025.03.19
(CS) 네트워크 - 기본구조  (1) 2025.03.18
(CS) 정렬 - 퀵 정렬  (0) 2025.01.21
(CS) 의존성 주입  (0) 2025.01.09