[소프트웨어공학] SOLID

수진·2023년 6월 9일
0

소프트웨어공학

목록 보기
13/20

1. SOILD

로버트 마틴이 주장한 다섯 가지 설계 원칙

✓ SRP(단일 책임 원칙, Single Responsibility Principle)
✓ OCP(개방 폐쇄 원칙, Open Closed Principle)
✓ LSP(리스코프의 대입 원칙, Liskov Substitution Principle)
✓ ISP(인터페이스 분리 원칙, Interface Segregation Principle)
✓ DIP(의존성 역전 원칙, Dependency Inversion Principle)
=> Design Pattern의 기반을 이룸

1. SRP

  • 단일 책임 원칙
  • 즉, 클래스는 단 하나의 책임만을 가지도록 설계해야 한다는 의미
  • 책임
    • Actor: 동일한 needs를 가지는 사용자 그룹
      ex) 회계팀, 감사팀, 인사부서, DB 설계팀, UI팀 등
      (개개인의 사람들을 의미하는 것은 아님)
    • 한 actor의 요구사항을 만족시키기 위한 기능들의 모임
  • 한 클래스는 한(one and only one) actor만을 책임져야함

<SRP 적용 전>

위의 그림을 보면, start(), stop(), getOil() 3개의 메소드에 의해 제공되는 기능들은 Car를 책임지고 있음(요구사항을 만족시키고 있음)
Car 클래스는 네개의 actor를 책임지고 있음(책임지는 기능들을 제공하는 메소드를 가지고 있음) 단 하나의 액터만을 책임져야한다는 "단일 책임 원칙"에 위배됨

<SRP 적용 후>

그림 출처 https://yoongrammer.tistory.com/96

  • 클래스에 하나의 메소드를 가지는 게 단일 책임을 의미하는 건 아님

2. OCP

  • 개방 폐쇄의 원칙
  • 기존의 코드를 변경하지 않으면서 새로운 기능을 추가할 수 있도록 설계하는 원칙
  • 새로운 기능에 대해 열려 있어야 하고, 기존의 코드는 새로운 기능의 추가로 인해 영향을 받지 않도록 설계해야함
  • 다섯 가지 원칙 중 가장 핵심이 되는 원칙
  • 무엇이 변하는지 변화의 요인을 살펴 볼 필요가 있음

OCP를 만족하는 설계가 되기 위해서는,

  1. 변하는 것을 식별하고
  2. 변하는 것을 클래스로 분리한다.
  3. 변하는 것들을 포용하는 개념을 추상 클래스나 인터페이스로 추상화한다.
  4. 2번 단계에서 만든 클래스를 3번 단계에서 추출한 추상화된 개념의 자식 클래스로 모델링한다.

3. LSP

  • 일반화 관계를 적절하게 사용했는지를 점검하는 원칙

    일반화 관계
    A가 사용되는 것을 B의 인스턴스로 치환될 수 있다.
    B is a A
    B is a kind of A

  • LSP에서 일반화 관계는 슈퍼 클래스가 제공하는 오퍼레이션과 파생 클래스에서 제공하는 오퍼레이션 간에는 행위적으로 일관성이 있도록 설계가 되어야 한다는 원칙

  • 프로그램에서 슈퍼 클래스의 인스턴스 대신에 파생 클래스의 인스턴스로 대체하여도 프로그램의 의미는 변화되지 않도록 설계

  • 자식 클래스가 부모 클래스보다 더 많은 일을 할 수 있도록 재정의 가능(부모 클래스의 기능을 자식 클래스에서 오버라이딩 가능) => 부모 클래스를 확장시켰다고 볼 수 있음

<행위 일관성>

  • pre => pre' (만약 선조건 pre가 만족된다면 pre'가 만족되어야 한다.)
  • post' => post (만약 후조건 post'가 만족된다면 post가 만족되어야 한다.)

4. ISP

  • 인터페이스 분리 원칙
  • Clients should not be forced to depend upon interfaces that they do not use -> 클라이언트는 자신들이 사용하는 인터페이스에 의존하도록 강제되어서는 안된다.
  • 인터페이스를 클라이언트에 특화되도록 분리시키라는 설계 원칙
  • 클라이언트의 관점에서 클라이언트 자신이 이용하지 않는 기능에는 영향을 받지 않아야 한다는 내용이 담겨 있다.
  • 온갖 연산을 가지는 큰 인터페이스 대신에 필요로 하는 연산만을 가지는 인터페이스를 사용하라 (fat interface -> small interface)

<ISP 적용 전>

public interface PrintingService {
	public void print();
    public void fax();
    public void copy();
}

public class MultiPrinter implements PrintingService {
	@Override
    public void print() {
    	System.out.println("복합기 출력");
    }
    
    @Override
    public void fax() {
    	System.out.println("복합기 팩스");
    }
    
    @Override
    public void copy() {
    	System.out.println("복합기 복사");
    }
    
public class NewPrinter implements PrintingService {
	@Override
    public void print() {
    	System.out.println("새 프린터 출력");
    }
    
    @Override
    public void fax() {
    	throw new UnsupportedOperationException("Not Supported");
    }
    
    @Override
    public void copy() {
    	throw new UnsupportedOperationException("Not Supported");
    }

=> 새로운 인터페이스를 추가하려고 하는 경우, print() 기능만을 사용하고 싶은데 전혀 사용하지 않는 fax()나 copy()를 사용하도록 강제한다. 하지만 fax()나 copy() 연산에 복사할 페이지 수를 인자로 요구하도록 변경된다면 NewPrinter도 영향을 받는다. 하나의 큰 인터페이스(fat interface) 대신에 클라이언트가 필요로 하는 연산만을 가지고 있는 인터페이스를 제공하면 된다.

인터페이스 분리
-> ProfessionalPrintingService 인터페이스를 만들어 NewPrinter가 이 인터페이스를 통해 print() 기능을 제공할 수 있게 함

➕ MultiPrinter도 ProfessionalPrintingService 인터페이스를 구현하여 print() 서비스 제공할 수 있음

5. DIP

  • 의존성 역전 원칙(Dependency Inversion Principle)
    1. 상위 모듈은 하위 모듈에 의존하면 안된다. 이 두 모듈 모두 다른 추상화된 것에 의존해야 한다.
    2. 추상화된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화된 것에 의존해야 한다.
  • High-level module : 도메인 핵심 로직
  • Low-level module : 도메인 핵심 로직을 둘러싼 환경
  • 도메인 핵심 로직이 주변 환경의 변화에 영향 받지 않도록 설계
    -> 의존 관계를 맺을 때 도메인 핵심 로직(domain core logic)으로 의존 관계 방향이 설정되어 있어야 함
  • 의존 관계를 설정할 때에는 구체적인 클래스보다는 이를 추상화한 개념과 인터페이스 같은 수단을 이용하여 관계를 맺도록 설계해야 한다.

<DIP 적용>
도메인 핵심 로직(High-level module)에서 직접적으로 Low-level model로 의존성 관계를 맺는 대신에 중간에 징검다리 역할이 필요 -> 변화가 일어나는 경우, 변화들을 포용할 수 있는 개념을 추상 클래스나 인터페이스로 모델링
그 후, 의존성 역전 -> 주변 환경으로부터 도메인 핵심 로직으로 의존성을 갖도록 의존 관계를 역전

<패키지 관점에서 본 DIP>

  • 도메인 핵심 로직을 제공하는 클래스와 인터페이스는 동일한 패키지 내에 있음
  • Low-level model은 패키지 외부에 위치
  • core 패키지는 외부와 의존 관계가 전혀 존재하지 않음
    -> 외부의 상황 변화에도 core 패키지 내부에 있는 것들은 전혀 영향을 받지 않게 되어 있음, core 패키지는 독립적으로 배포가 가능함
  • 패키지 관점에서 보면 Low-level model의 클래스들은 core 패키지와 의존 관계에 있음, 이들은 core 패키지를 import 필요

0개의 댓글