SOLID

SeungJoo
|2023. 11. 11. 14:46
반응형

SOLID

객체지향 프로그래밍에서 좋은 소프트웨어 설계를 위해 사용되는 다섯 가지 기본원칙입니다.

이 원칙들도 땅에서 솟아서 갑자기 나온 게 아니라 응집도는 높이고 결합도는 낮추는 고전 원칙을 객체 지향의 관점에서 재정립한 것이라고 할 수 있습니다.

결합도와 응집도

결합도

소프트웨어의 모듈 간에 서로 얼마나 강하게 연결되어 있는지를 나타냅니다.

높은 결함도는 모듈 간의 의존성이 높은 것을 의미하며 한 개의 모듈을 변경할 때 다른 모듈들에게 영향을 미칠 수 있음을 나타냅니다.

응집도

모듈 내부의 요소들이 얼마나 밀접하게 관련되어 있는지를 나타냅니다. 높은 응집도를 갖는 모듈은 코드의 가독성이 향상되고 모듈을 이해하기 쉬워집니다.

 

SRP - 단일 책임 원칙

소프트웨어 컴포넌트, 클래스, 함수, 모듈 등이 하나의 책임(기능담당)만을 가져야 한다는 개념을 나타냅니다. 쉽게 이해하기에 간단한 예시를 들어보겠습니다.

간단 예시

가정용 스마트폰 앱을 개발하는 상황이라고 했을 때 먼저 가정용 스마트폰 앱 클래스가 여러 가지 잭임을 가지는 경우는 (SRP 원칙을 위반합니다.) 한 클래스에서 사용자의 프로필관리, 메시지 전송 및 수신, 사진 촬영등 여러 가지 작업을 처리하면, 클래스가 커지고 복잡해지며 유지 보수가 힘들어집니다. 이런 걸 방지하기 위해서 각 책임을 가지 클래스를 분리해야 합니다. 사용자 프로필 관리 클래스, 메시지 전송 및 수신 클래스 사진 촬영 클래스등으로 분리하며 클래스마다 하나의 명확한 책임을 가지며 서로 독립적으로 변경될 수 있습니다.

코드로 보는 예시

// SRP 위반 (하나의 클래스가 두 가지 책임을 가짐)
class FileHandler {
    public void readFile(String filename) {
        // 파일 읽기 로직
    }

    public void writeFile(String filename, String data) {
        // 파일 쓰기 로직
    }
}

// SRP 준수 (두 가지 책임을 분리한 클래스)
class FileReader {
    public String readFile(String filename) {
        // 파일 읽기 로직
        // 읽은 데이터를 반환
    }
}

class FileWriter {
    public void writeFile(String filename, String data) {
        // 파일 쓰기 로직
    }
}

OCP - 개방 폐쇄 원칙

소프트웨어 엔티티인 클래스, 모듈, 함수등은 확장에 대해 열려 있어야 하고 수정에 대해 닫혀 있어야 합니다. 쉽게 말해 새로운 기능을 추가하기 위해서는 기존 코드를 수정하지 말아야 하며, 기존 코드를 변경하지 않고 확장할 수 있어야 합니다.

코드로 보는 예시

// 계좌 관리 인터페이스
interface AccountManagement {
    void performTransaction(int accountNumber, double amount);
}
// OCP를 준수하기 위해 먼저 AccountManagement 인터페이스를 도입했습니다. 
// 인터페이스는 계좌 관리와 관련된 모든 작업을 처리하는 클래스가 구현해야 하는 메서드를 정의합니다.

// 계좌 생성 및 종료 클래스
class AccountCreateClose implements AccountManagement {
    public void performTransaction(int accountNumber, double amount) {
        // 계좌 생성 또는 종료 로직
    }
}
// AccountCreateClose 및 AccountUpdate 클래스는 AccountManagement 인터페이스를 구현합니다. 
// 각 클래스는 특정 계좌 관련 작업을 처리하고, performTransaction 메서드를 구현

// 계좌 업데이트 클래스
class AccountUpdate implements AccountManagement {
    public void performTransaction(int accountNumber, double amount) {
        // 계좌 정보 업데이트 로직
    }
}
// 거래 로깅은 계좌 관리 작업과 별개의 작업입니다. 
// OCP를 준수하기 위해 거래 로깅 관련 코드를 별도의 클래스로 분리합니다.

// 거래 기록 로깅 클래스
class TransactionLogger {
    public void logTransaction(int accountNumber, double amount) {
        // 거래 로그 작성 로직
    }
}
// 기존 코드를 수정하지 않고도 시스템을 확장할 수 있습니다. 
// 또한 거래 로깅 기능도 확장 가능하며, 새로운 로깅 방식을 추가하려면
// TransactionLogger 클래스를 수정하거나 새로운 로깅 클래스를 작성할 수 있습니다

OCP를 준수하고 시스템 유지 보수성 및 확장성을 향상시키며 새로운 요구 사항이 생겼을 때는 기존 코드를 수정하지 않고도 시스템을 확장할 수 있습니다.

LSP - 리스코프 치환 원칙

여러 하위 클래스가 상위 클래스로 대체 가능해야 한다는 개념을 나타냅니다. 이것은 하위 클래스가 상위 클래스와 동일한 방식으로 동작하며 상위클래스로 사용되는 것을 의미합니다.

코드로 보는 예시

LSP를 준수하며 다향성을 통해 서브 클래스를 슈퍼클래스로 대체할 수 있으며 LSP를 준수하는 코드는 유지 보수성과 확장이 용이합니다.

interface Animal {
    void makeSound();
}

class Bird implements Animal {
    @Override
    public void makeSound() {
    }
}

class Dog implements Animal {
    @Override
    public void makeSound() {
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Bird();
        animal.makeSound();

        Animal animal2 = new Dog();
        animal2.makeSound();
    }
}
// 서브 클래스는 슈퍼클래스인 Animal로 대체 가능합니다.

ISP - 인터페이스 분리 원칙

클라이언트가 하나의 무건운 인터페이스를 구현하는 대신, 클라이언트별로 필요한 작은 인터페이스를 분리하여 제공합니다. 이렇게 했을 때의 이점은 자신이 필요로 하는 메서드만 사용할 수 있습니다.

코드로 보는 예시

// ISP를 준수한 코드
interface Movable {
    void move();
}

interface Soundable {
    void makeSound();
}

class Dog implements Movable, Soundable {
    public void move() {
    }
    public void makeSound() {
    }
}

class Bird implements Movable, Soundable {
    public void move() {
    }
    public void makeSound() {
    }
}

ISP를 준수하는 코드이며 코드가 모듈화되어 유지보수가 쉽고 새로운 동물 종류를 추가하거나 move를 확장하기에도 용이합니다.

DIP - 의존 역전 원칙

객체지향 설계와 의존성 관리의 핵심 원

칙이며 코드의 유지보수성과 확장성을 향상하는 역할을 합니다.

코드로 보는 예시

// 고수준 모듈: 스위치
interface Switch {
    void operate();
}
// 저수준 모듈: 전구
class LightBulb implements Switch {
    public void operate() {
        System.out.println("전구를 켭니다.");
        // 전구 작동 로직
    }
}
// DIP 준수
public class Main {
    public static void main(String[] args) {
        Switch lightSwitch = new LightBulb(); // 고수준 모듈
        lightSwitch.operate();
    }
}

고수준 모듈 스위치는 저수준 모듈 전구에 직접 의존하지 않고 스위치 인터페이스를 사용하여 동작을 제어합니다. 이렇게 했을 때 고수준과 저수준 간의 의존성이 역전되며 코드의 유지보수 확장성이 향상됩니다.

728x90

'JAVA' 카테고리의 다른 글

JPA를 이용한 복합키(Composite Key) 매핑  (0) 2024.01.02
Pubilc과 Private  (1) 2023.11.01
Stream(기초)  (0) 2023.10.30