티스토리 뷰

반응형

SOLID

객체 지향 설계 프로그래밍 및 설계의 다섯 가지 기본 원칙의 두문자어

 

  • SRP (Single Responsibility Principle): 단일 책임 원칙
  • OCP(Open Closed Principle): 개방 폐쇄 원칙
  • LSP (Liskow Substitution Principle): 리스코프 치환 원칙
  • ISP (Interface Segregation Principle): 인터페이스 분리 원칙
  • DIP (Dependency Inversion Principle): 의존 역전 원칙

⇒ 응집도는 높이고 (High Cohesion), 결합도는 낮추는 (Loose Coupling) 고전 원칙을 을 객체 지향의 관점에서 재정립한 것

 

결합도

어떤 모듈이 다른 모듈에 의존하는 정도

응집도

모듈 내부의 기능이 연관된 정도

 

💡 좋은 소프트웨어 설계를 위해서 결합도는 낮추고 응집도는 높이는 것이 바람직하다. 이와 같이 설계를 하면 재사용성이 좋고, 유지 보수가 용이하다.

 

 

SRP - 단일 책임 원칙

작성된 클래스는 하나의 기능만 가지며,
클래스가 제공하는 모든 서비스는 그 하나의 책임을 수행하는 데 집중되어 있어야 한다

 

class 사람 {
    String 군번;
}

사람 철수 = new 사람();
사람 영희 = new 사람();

  영희는 군번이란 속성을 사용하지 않지만, 사람이라는 클래스를 사용하면 군번이라는 속성을 가지고 있게 된다. 사람이라는 클래스를 사용하면 영희에게 군번 값을 할당하거나 읽어올 수 있는 문제가 생긴다.

 

class 사람 {
    ...
}

class 남자 extends 사람 {
    String 군번;
}

class 여자 extends 사람 {
}

사람 철수 = new 남자();
사람 영희 = new 여자();

  위와 같이 코드를 리팩토링하면, 남자 클래스에만 군번이라는 필드 값을 가지게 된다. 따라서 영희에게 군번을 할당하거나 읽어오는 등의 잘못된 동작을 수행하지 않을 수 있다.

 

class 강아지 {
    final static Boolean 수컷 = true;
    final static Boolean 암컷 = false;
    Boolean 성별;
    
    void 행위() {
        if(this.성별 == 수컷) {
            ...
        } else {
            ...
        }
    }
}

  위의 코드의 메서드 행위에서는 분기가 발생한다. 메서드에서 분기 처리를 위한 if문의 경우 단일 책임을 지키지 않는 하나의 예시이다.

 

abstract class 강아지 {
    abstract void 행위();
}

class 수컷강아지 extends 강아지 {
    void 행위() {
    	...
    }
}

class 암컷강아지 extends 강아지 {
    void 행위() {
    	...
    }
}

  코드를 위와 같이 리팩토링 하여 SRP를 지키도록 만들수 있다.

 

OCP - 개방 폐쇄 원칙

소프트웨어 구성요소는 확장에는 열려있고 변경에는 닫혀 있어야 한다

 

  자동차라는 상위 클래스 혹은 인터페이스가 있기 때문에 운전자는 영향을 받지 않을 수 있다. 자동차 입장에서는 확장에 개방되어 있고, 운전자 입장에서는 주변의 변화에 폐쇄되어 있다. 마티즈라는 구현체가 쏘렌토로 변경이 되어도 운전자는 자동차만 바라보고 있기 때문에 영향을 받지 않는다.

 

  자바가 플랫폼 독립적이라는 것도 OCP 원칙이 적용되어 있는 것이다. .class 파일은 JVM 덕분에 운영체제와 상관없이 실행된다. .class 파일은 운영체제에 변화에도 변경할 필요가 없기 때문에 주변의 변화에 폐쇄되어 있고, JVM만 운영체제에 맞춰서 변경하면 된다.

 

 

LSP - 리스코프 치환 원칙

서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다

⇒ 서브 타입은 기반 타입이 약속한 규약을 지켜야 한다

⇒ 하위 클래스의 인스턴스는 상위형 객체 참조 변수에 대입해 상위 클래스 인스턴스 역할을 하는 데 문제가 없어야 한다 

 

적용 사례

Set<Integer> hashSet = new HashSet<>();
Set<Integer> treeSet = new TreeSet<>();

 

 

객체 지향 상속 조건

  • 하위 클래스 is a kind of 상위 클래스 - 하위 분류는 상위 분류의 한 종류
    • 두 객체가 동일한 일을 한다면 상위 클래스로 표현
  • 구현 클래스 is able to 인터페이스 - 구현 클래스는 인터페이스할 수 있다
    • 똑같은 연산을 제공하지만, 약간씩 다르게 한다면 공통의 인터페이스를 만들고 이를 구현

 

ISP - 인터페이스 분리 원칙

클래스는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안 된다

 

  위의 그림을 보면 여자 클래스는 대학생, 아르바이트, 딸의 역할을 하고 있다. 만약 아르바이트를 그만두었다면, 여자 클래스는 출근할 필요가 없다. 따라서 출근하기 메서드를 제공하면 안 된다.

 

인터페이스 최소주의 원칙

상위 클래스는 풍성할수록 좋고, 인터페이스는 작을 수록 좋다

 

 

DIP - 의존 역전 원칙

추상화된 것은 구체적인 것에 의존하면 안 된다.
구체적인 것이 추상화된 것에 의존해야 한다.

 

class Service {
    MemoryRepository memoryRepository;
}

class MemoryRepository {
}

  Service 클래스는 MemoryRepository를 사용하고 있다. 개발 단계에서 MemoryRepository를 사용하다가 실제로 나중에 DB를 사용할 때 Service 클래스도 같이 변경을 해줘야 한다. MemoryRepository 클래스의 변경이 Service 클래스에도 영향을 미치게 되는 것이다.

 

class Service {
    Repository repository;
}

interface Repository {
}

class MemoryRepository implements Repository {
}

class DatabaseRepository implements Repository {
}

  Service는 추상화 된 인터페이스에 의존을 하게 변경되었다. 고차원 모듈이 저차원 모듈에 의존하기 때문에, 변화가 일어나더라도 Service는 영향을 받지 않는다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함