본문 바로가기
Computer Science/Object Oriented

SOLID 원칙

by parkkingcar 2023. 5. 17.

 

 

SOLID 원칙

 

SOLID 원칙이란 유지 관리가 가능하고 유연한 소프트웨어 시스템을 개발하는 데 도움이 되는 일련의 설계 원칙입니다. 모듈화되고 재사용 가능하며 쉽게 유지 관리할 수 있는 코드를 장려합니다.

 

로버트 마틴이 2000년대 초반에 명명한 객체 지향 프로그래밍 및 설계의 다섯 가지 기본 원칙을 마이클 페더스가 두문자어 기억술로 소개하였습니다.

 

 

 

SRP  단일 책임 원칙 (Single responsibility principle) :  한 클래스는 하나의 책임을 가져야 한다. 
OCP    개방-폐쇄 원칙 (Open/closed principle) : 소프트웨어 엔터티가 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
LSP 리스코프 치환 원칙 (Liskov substitution principle) : 프로그램의 정확성에 영향을 주지 않으면서 상위클래스의 객체를 서브클래스의 객체로  대체할 수 있어야 한다
ISP  인터페이스 분리 원칙 (Interface segregation principle) : 클라이언트가 사용하지 않는 인터페이스에 의존하도록 강요해서는 안 된다
DIP  의존관계 역전 원칙 (Dependency inversion principle) : 상위 수준 모듈이 하위 수준 모듈에 의존해서는 안 된다

 

 

 

 

 

단일 책임 원칙 (SRP)

SRP는 높은 응집력을 촉진하고 각 클래스가 특정 기능에 집중하도록 하여 코드를 더 쉽게 이해하고 유지 관리하고 테스트할 수 있도록 합니다.

 

 

예시코드

public class Order {
    private List<Item> items;
    
    public void addItem(Item item) {
        // 아이템을 주문에 추가하는 로직
    }
    
    public void removeItem(Item item) {
        // 주문에서 아이템을 제거하는 로직
    }
    
    public double calculateTotalPrice() {
        double total = 0;
        // 주문의 총 가격을 계산하는 로직
        return total;
    }
    
    public void printOrder() {
        // 주문의 세부 정보를 출력하는 로직
    }
    
    public void saveOrder() {
        // 주문을 데이터베이스에 저장하는 로직
    }
}

 

 

위 예시에서는 주문을 나타내는 Order 클래스가 있습니다. 하지만 Order 클래스는 위와 같이 여러 책임이 있어, 단일 책임 원칙을 위반하고 있습니다. 

 

 

단일 책임 원칙을 준수하기 위해 Order 클래스를 별개의 클래스로 분리하여 각각 하나의 책임만을 가지도록 리팩터링할 수 있습니다.

 

 

public class Order {
    private List<Item> items;
    
    public void addItem(Item item) {
        // 아이템을 주문에 추가하는 로직
    }
    
    public void removeItem(Item item) {
        // 주문에서 아이템을 제거하는 로직
    }
    
    public double calculateTotalPrice() {
        double total = 0;
        // 주문의 총 가격을 계산하는 로직
        return total;
    }
}

public class OrderPrinter {
    public void printOrder(Order order) {
        // 주문의 세부 정보를 출력하는 로직
    }
}

public class OrderRepository {
    public void saveOrder(Order order) {
        // 주문을 데이터베이스에 저장하는 로직
    }
}

 

위의 리팩토링 코드는 각 책임을 별개의 클래스로 분리하였습니다. Order 클래스는 이제 주문의 관리와 계산에 집중합니다.

 

OrderPrinter 클래스는 주문의 세부 정보 출력, OrderRepository 클래스는 주문을 데이터베이스에 저장하는 작업에 각각 책임을 가집니다.

 

 

 

단일 책임 원칙을 통해, 코드의 이해와 수정이 용이해지고 테스트하기도 쉬워집니다.

 

 

 

 

 

개방-폐쇄 원칙 (OCP)

개방 폐쇄 원칙은 기존의 코드를 변경하지 않고도 새로운 기능을 추가할 수 있도록 설계되어야 한다는 원칙입니다. 이를 위해 추상화와 상속을 활용하여 기능의 확장을 용이하게 해야 합니다.

 

 

예시코드

public abstract class Shape {
    public abstract double calculateArea();
}

public class Rectangle extends Shape {
    private double width;
    private double height;
    
    // 생성자, 게터, 세터 등 생략
    
    @Override
    public double calculateArea() {
        return width * height;
    }
}

public class Circle extends Shape {
    private double radius;
    
    // 생성자, 게터, 세터 등 생략
    
    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

public class AreaCalculator {
    public double calculateTotalArea(Shape[] shapes) {
        double totalArea = 0;
        for (Shape shape : shapes) {
            totalArea += shape.calculateArea();
        }
        return totalArea;
    }
}

 

위의 예시에서는 도형을 나타내는 추상 클래스 Shape는 calculateArea라는 추상 메서드를 가지고 있습니다. 이 추상 메서드는 도형의 면적을 계산하는 기능을 가지도록 서브 클래스에서 구현합니다.

 

Rectangle 클래스와 Circle 클래스는 Shape 클래스를 상속하며, 각각 사각형과 원을 나타냅니다. 

 

 클래스는 calculateArea 메서드를 오버라이딩하여 면적을 계산하는 방식을 구현합니다. 

 

AreaCalculator 클래스는 도형 배열을 받아 각 도형의 면적을 계산하여 총 면적을 반환하는 기능을 가지고 있습니다. 

이 클래스는 개방-폐쇄 원칙을 따르며, 새로운 도형 클래스가 추가되더라도 수정할 필요 없이 기존의 계산 로직을 활용하여 면적을 계산할 수 있습니다. 

 

 

개방-폐쇄 원칙을 준수함으로써, 기존의 코드를 수정하지 않고도 새로운 도형 클래스를 추가하고 기능을 확장할 수 있습니다. 이를 통해 유연하고 확장 가능한 소프트웨어를 구축할 수 있습니다.

 

 

 

추가적으로 프론트엔드에서도 개방폐쇄원칙을 통해 개발하여 확장성을 용이하게 하는 예시가 아래 블로그에 있습니다. 프로젝트때 다뤘었는데 예시로 보고 인상깊게 남아 첨부하였습니다.

 

 

프론트엔드와 SOLID 원칙 | 카카오엔터테인먼트 FE 기술블로그

임성묵(steve) 판타지, 무협을 좋아하는 개발자입니다. 덕업일치를 위해 카카오페이지로의 이직을 결심했는데 인사팀의 실수로 백엔드에서 FE개발자로 전향하게 되었습니다. 인생소설로는 데로

fe-developers.kakaoent.com

 

 

 

 

 

 

리스코프 치환 원칙 (LSP)

리스코프 치환 원칙은 상속 관계에 있는 클래스들이 서로를 대체할 수 있어야 한다는 개념을 나타냅니다. 

 

리스코프 치환 원칙의 핵심은 부모 클래스의 행동 규약을 자식 클래스가 위반하면 안 된다는 것 입니다. 행동 규약을 위반한다는 것은 자식 클래스가 오버라이딩을 할 때, 잘못되게 재정의하면 리스코프 치환 원칙을 위배할 수 있다는 의미입니다. 즉, 어느 메소드를 오버로딩을 부모가 아닌 자식 클래스에서 해버리면 LSP 원칙 위반이 발생합니다.

 

예) 부모 클래스를 상속하는 자식 클래스가 부모 클래스의 특정 메소드를 멋대로 코드를 재사용하기위해 메소드 타입을 바꾸고 매개변수 갯수를 바꿔 버림

 

 

쉽게 말하면, 부모 클래스의 인스턴스를 사용하는 위치에 자식 클래스의 인스턴스를 대신 사용했을 때 코드가 원래 의도대로 작동해야 합니다. 이를 위해 자식 클래스는 부모 클래스의 기능을 규약을 지키며 오버라이딩하여 기능을 변경하거나 확장해야 합니다.

 

 

 

 

예시코드

public class Rectangle {
    protected int width;
    protected int height;
    
    // 생성자, 게터, 세터 등 생략
    
    public int getWidth() {
        return width;
    }
    
    public void setWidth(int width) {
        this.width = width;
    }
    
    public int getHeight() {
        return height;
    }
    
    public void setHeight(int height) {
        this.height = height;
    }
    
    public int calculateArea() {
        return width * height;
    }
}

public class Square extends Rectangle {
    
    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        super.setHeight(width);
    }
    
    @Override
    public void setHeight(int height) {
        super.setHeight(height);
        super.setWidth(height);
    }
}

 

위의 예시에서 Rectangle 클래스는 가로와 세로 길이를 가지고 사각형의 면적을 계산하는 기능을 제공합니다. 

 

Square 클래스는 Rectangle 클래스를 상속받습니다. 사각형이기 때문에 가로와 세로 길이가 동일해야 합니다. 따라서 Square 클래스에서는 setWidth와 setHeight 메서드를 오버라이딩하여 가로와 세로 길이를 동일하게 설정하도록 구현합니다. 

 

리스코프 치환 원칙을 준수하면 Square 클래스는 Rectangle 클래스의 대체 가능성을 유지합니다. 

즉, Square 클래스를 사용하는 부분에서 Rectangle 클래스를 사용해도 정상적으로 작동해야 합니다. 

 

 

리스코프 치환 원칙을 준수하는 것은 상속 계층 구조의 일관성을 유지하고, 코드의 가독성과 유지보수성을 향상시키는 데 도움이 됩니다.

 

 

 

 

 

 

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

인터페이스 분리 원칙은 클라이언트가 자신이 사용하지 않는 인터페이스에 의존하지 않아야 한다는 개념을 나타냅니다.

 

즉, 한 인터페이스에 너무 많은 기능을 포함시키지 말고, 클라이언트가 필요로 하는 기능에 따라 여러 개의 작은 인터페이스로 분리해야 합니다. 이를 통해 클라이언트는 자신이 필요로 하는 인터페이스에만 의존하고, 불필요한 의존성을 피할 수 있습니다.

 

 

 

예시코드

public interface Printer {
    void print(Document document);
}

public interface Scanner {
    void scan(Document document);
}

public interface FaxMachine {
    void sendFax(Document document, String number);
    void receiveFax();
}

public class AllInOnePrinter implements Printer, Scanner, FaxMachine {
    private final Printer printer;
    private final Scanner scanner;
    private final FaxMachine faxMachine;
    
    // Printer, Scanner, FaxMachine 인터페이스의 모든 메서드를 구현
    // ...
}

public class Document {
    // 문서 관련 정보와 동작을 나타내는 클래스
}

위의 예시에서는 Printer, Scanner, FaxMachine 인터페이스가 있습니다. 각 인터페이스는 프린터, 스캐너, 팩스 기능에 대한 메서드를 정의합니다. 인터페이스를 작은 단위로 분리하여 클라이언트가 필요로 하는 기능에만 의존할 수 있도록 하였습니다.

 

 AllInOnePrinter 클래스는 Printer, Scanner, FaxMachine 인터페이스를 모두 구현한 클래스입니다. 이 클래스는 프린터, 스캐너, 팩스 기능을 모두 가지고 있으며, 각 인터페이스의 메서드를 구현합니다.

 

복합기의 기능을 제공하는 클래스는 매우 비대해질 가능성이 큽니다. 하지만 이 모든 메소드를 사용하는 클라이언트가 동시에 사용하는 경우는 거의 없습니다. 클라이언트의 필요에 따라 프린터 기능만 이용하던지, 팩스 기능만 이용하던지, 스캐너 기능만 이용할 수 있습니다. 클라이언트의 필요에 따라 프린터 기능만 이용하는 클라이언트가 팩스 기능의 변경으로 인해 발생하는 문제에 영향을 받지 않도록 해야합니다.

 

 

 

public interface Printer {
    void print(Document document);
}

public interface Scanner {
    void scan(Document document);
}

public interface FaxMachine {
    void sendFax(Document document, String number);
    void receiveFax();
}

public class SimplePrinter implements Printer {
    @Override
    public void print(Document document) {
        // 프린터 동작 구현
    }
}

public class SimpleScanner implements Scanner {
    @Override
    public void scan(Document document) {
        // 스캐너 동작 구현
    }
}

public class SimpleFaxMachine implements FaxMachine {
    @Override
    public void sendFax(Document document, String number) {
        // 팩스 전송 동작 구현
    }
    
    @Override
    public void receiveFax() {
        // 팩스 수신 동작 구현
    }
}

인터페이스 분리 원칙을 준수함으로써 클라이언트는 필요한 기능에 맞는 인터페이스에만 의존하게 됩니다. 이렇게 인터페이스를 분리하면 클라이언트의 코드는 간결하고 의존성이 명확해지며, 필요 없는 기능에 대한 의존성을 피할 수 있습니다.

 

 

 

 

 

의존관계 역전 원칙 (DIP)

의존관계 역전 원칙은 상위 수준의 모듈은 하위 수준의 구체적인 구현에 의존하지 않고, 인터페이스나 추상화에 의존해야 한다는 원칙입니다. 이렇게 하면 모듈 간의 결합도가 낮아지고, 상위 수준의 모듈이 하위 수준 모듈의 변경에 영향을 받지 않으면서 유연하고 재사용 가능한 코드를 작성할 수 있습니다.

 

 

 

예시코드

// 추상화를 나타내는 인터페이스
interface Engine {
    void start();
}

// 추상화에 따른 구체적인 구현
class CarEngine implements Engine {
    public void start() {
        System.out.println("Engine started.");
    }
}

// 고수준 모듈(Car)은 저수준 모듈(Engine)에 의존하지 않고, 추상화(Engine)에 의존함
class Car {
    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.start();
    }
}

public class Main {
    public static void main(String[] args) {
        // 의존관계 주입(Dependency Injection)을 통해 Car 객체 생성
        Engine engine = new CarEngine();
        Car car = new Car(engine);

        // Car 객체의 start 메서드 호출
        car.start();
    }
}

 

위의 코드에서 Car 클래스는 Engine 인터페이스에 의존하고 있습니다. 

 

이를 통해 Car 클래스는 Engine의 구체적인 구현에 대해 알 필요가 없으며 다양한 구현체를 쉽게 교체하거나 확장할 수 있습니다. Engine 클래스가 변경되더라도 Car 클래스에는 영향을 주지 않습니다.

 

 

의존관계 역전 원칙을 따르면 변경이 일어나는 경우 저수준 모듈의 구현을 수정하거나 대체할 필요 없이 고수준 모듈만 수정하여 시스템을 유지보수하거나 확장할 수 있습니다. 

 

 

 

 

 

 

 

 

참고자료

 

프론트엔드와 SOLID 원칙 | 카카오엔터테인먼트 FE 기술블로그

임성묵(steve) 판타지, 무협을 좋아하는 개발자입니다. 덕업일치를 위해 카카오페이지로의 이직을 결심했는데 인사팀의 실수로 백엔드에서 FE개발자로 전향하게 되었습니다. 인생소설로는 데로

fe-developers.kakaoent.com

 

💠 완벽하게 이해하는 LSP (리스코프 치환 원칙)

리스코프 치환 원칙 - LSP (Liskov Substitution Principle) 리스코프 치환 원칙은 1988년 바바라 리스코프(Barbara Liskov)가 올바른 상속 관계의 특징을 정의하기 위해 발표한 것으로, 서브 타입은 언제나 기반

inpa.tistory.com

 

SOLID (객체 지향 설계) - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. -->

ko.wikipedia.org

 

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

객체 지향 프로그래밍 및 설계에서 5가지 기본원칙(SRP, OCP, LSP, ISP, DIP)의 네번째 원칙인 ISP(Interface Segregation Principle)에 대해 알아보겠습니다. ISP(인터페이스 분리 원칙)은 클라이언트가 자신이 이

dreamcoding.tistory.com

코드생성

 

Introducing ChatGPT

We’ve trained a model called ChatGPT which interacts in a conversational way. The dialogue format makes it possible for ChatGPT to answer followup questions, admit its mistakes, challenge incorrect premises, and reject inappropriate requests.

openai.com

 

'Computer Science > Object Oriented' 카테고리의 다른 글

[OOP] 객체 지향 프로그래밍이란?  (0) 2022.12.05

댓글