[Design Pattern] SOLID 원칙
Posted: Updated:
자바 디자인 패턴 스터디를 하며 ‘Java 객체 지향 디자인 패턴’ 교재를 정리한 글입니다.
SOLID 원칙
- 로버트 마틴이 주장한 다섯가지 설계 원칙
- SRP: 단일 책임 원칙, Single Resposibility Principle
- OCP: 개방 폐쇄 원칙, Open Closed Principle
- LSP: 리스코프의 대입 원칙, Liskov Substitution Principle
- ISP: 인터페이스 분리 원칙, Interface Segregation Principle
- DIP: 의존성 역전 원칙, Dependency Inversion Principle
SRP
- 단일 책임 원칙
- 클래스(객체)는 단 하나의 책임만 가져야 함
책임
- 해야 하는 것, 할 수 있는 것
- 책임 할당 시 어떤 객체보다도 작업을 잘 할 수 있는 객체에 할당해야 함
- 책임에 수반되는 모든 일을 자신만이 수행해야 함
변경
- 책임 = 변경 이유
- 시스템에 새로운 요구사항이나 변경이 있을 때 가능하면 영향 받는 부분을 줄여야 함
책임 분리
- 한 클래스에 많은 책임을 부여하지 않고 단 하나의 책임만 수행하도록 해서 변경 사유가 될 수 있는 것을 하나로 만들어야 함
- 여러 책임을 수행하는 클래스가 있다면, 도움을 필요로 하는 코드도 많을 수밖에 없음
- 변경 사항이 생기면 직접, 간접적으로 사용되는 코드를 모두 다시 테스트해야 함
- 회귀 테스트: 어떤 변화가 있을 때 해당 변화가 기존 시스템의 기능에 영향을 주는지 평가하는 테스트
- 시스템에 변경 사항이 발생했을 때 영향을 받는 부분을 적게 해야 함
산탄총 수술
- 하나의 책임이 여러개의 클래스들로 분산되어 있는 경우에도 설계를 변경해야 할 경우
- 클래스 하나하나를 모두 변경해야 해서 에러가 발생할 수 있음
- 로깅, 보안, 트랜잭션 등의 횡단 관심 기능
- 시스템 핵심 기능(하나의 책임) 안에 포함되는 부가 기능(여러 개의 클래스로 분리)
- 부가 기능에 변경이 발생하면 부가 기능을 실행하는 모든 핵심 기능에도 변경이 적용되어야 함
- 해결 방법: 부가 기능을 별개의 클래스로 분리해 책임을 담당하게 함으로 여러 곳에 흩어진 공통 책임을 한 곳에 모아 응집도를 높임
AOP(Aspect-Oriented Programming)
- 관심 지향 프로그래밍
- 횡단 관심의 문제를 해결하는 방법 중 하나
- 횡단 관심을 수행하는 코드를 애스펙트(aspect)라는 특별한 객체로 모듈화하고, 위빙(weaving) 작업을 통해 모듈화한 코드를 핵심 기능에 끼워 넣음
- 변경 사항이 생길 경우 애스펙트만 수정하면 됨
- 조인포인트
- 애플리케이션 실행 중의 특정한 지점
- 메서드 호출, 메서드 실행 자체, 클래스 초기화, 객체 생성 시점
- 어떤 지점에서 AOP를 사용해 추가 로직을 삽입할 지 정의하는 핵심 개념
- 어드바이스
- 특정 조인포인트에 실행하는 코드
- Before 어드바이스: 조인포인트 이전에 실행
- After 어드바이스: 조인포인트 이후에 실행
- 포인트컷
- 조인포인트의 집합체
- 언제 어드바이스를 실행할지 정의할 때 사용
- 어드바이스를 어떻게 적용할지 상세한 제어 가능
- 어드바이스 실행 지점을 다양하게 제어할 필요가 있을 때는 복잡하게 구성하여 사용
- 애스펙트
- 어드바이스와 포인트컷의 조합
- 애플리케이션이 가져야 할 로직과 실행해야 하는 지점의 정의
- 위빙
- 애플리케이션 코드의 해당 지점에 애스펙트를 실제로 주입하는 과정
- 컴파일 시점 AOP 솔루션: 컴파일 시점에 위빙이 일어나며, 빌드 중 별도의 과정을 거침
- 실행 시점 AOP 솔루션: 실행 중에 동적으로 위빙
OCP
- 개방 폐쇄의 원칙
- 기존의 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계
- 무엇이 변하는 것인지, 무엇이 변하지 않는 것인지 구분하여 변해야 하는 것은 쉽게 변할 수 있도록 하고, 변하지 않아야 할 것은 변하는 것에 영향을 받지 않게 해야 함
단위 테스트
- 클래스를 변경하지 않고도 대상 클래스의 환경을 변경할 수 있는 설계가 중요
- 실제 데이터가 변경될 위험이 있다면 가짜 객체를 만들어야 함
테스트 더블
- 모의 객체
- 테스트 더블의 한 종류
- 무엇인가를 대신하는 가짜
- 더미 객체
- 테스트할 때 객체만 필요하고 기능까지는 필요 없는 경우 사용
- 메서드 호출 시 정상 동작하지 않고 예외 발생
- 테스트 스텁
- 더미 객체에 단순한 기능을 추가
- 객체의 특정 상태를 가정하여 작성
- 테스트 스파이
- 테스트 대상 클래스가 의존하는 클래스로의 간접 출력을 검증하는 데 사용
- 클래스 실행 동안 의존 클래스로부터의 호출과 호출 결과를 찾아내고 원하는 대로 호출되었는지 검사
- 가짜 객체
- 실제 의존 클래스의 기능 전체나 일부를 단순하게 구현해야 함
- 목 객체
- 미리 정의한 기댓값과 실제 호출을 비교하여 문제가 있다면 테스트를 실패하게 함
LSP
- 리스코프 치환 원칙
- 일반화 관계를 적절하게 사용했는지 점검
- 일반화 관계에서 최소한 슈퍼 클래스가 제공하는 오퍼레이션을 파생 클래스에서도 제공하는 행위적 일관성이 있어야 함
- 슈퍼 클래스의 인스턴스 대신 파생 클래스의 인스턴스를 사용해도 프로그램 의미가 변화되지 않음
- is a kind of 관계를 만족해야 함
- 피터 코드의 상속 규칙(재정의하지 않음)을 지키면 LSP를 만족시킬 수 있음
DIP
- 의존 역전 원칙
- 객체 사이에 서로 도움을 주고 받을 때 생기는 의존 관계의 가이드라인
- 변화하기 쉬운 것, 자주 변화하는 것보다 변화하기 어려운 것, 거의 변화가 없는 것에 의존
- 변하기 어려운 것
- 정책, 전략처럼 어떤 큰 흐름이나 개념같은 추상적인 것
- 인터페이스
- 변하기 쉬운 것
- 구체적인 방식, 사물 등
- 구체 클래스
- 변하기 어려운 것
의존성 주입
-
클래스 외부에서 의존되는 것을 대상 객체의 인스턴스 변수에 주입
-
예제 코드) 아이가 장난감을 로봇에서 레고로 바꾸어 가지고 놀 경우
public abstract class Toy {
public String toString() {
return "Toy";
}
}
public class Robot extends Toy{
public String toString() {
return "Robot";
}
}
// 추가 작성
public class Lego extends Toy{
public String toString() {
return "Lego";
}
}
public class Kid {
private Toy toy;
public void setToy(Toy toy) {
this.toy = toy;
}
public void play() {
System.out.println(toy.toString());
}
}
public class Main {
public static void main(String[] args) {
Toy t = new Robot();
Kid k = new Kid();
k.setToy(t);
k.play();
// toy 변경
t = new Lego();
k.setToy(t);
k.play();
}
}
- Kid, Toy, Robot 등 기존 코드의 변화 없이 장난감을 바꿀 수 있음
역전
- 의존 관계를 역전시켜야 더 나은 설계가 된다
- 전체적인 대규모 비즈니스 작업을 총괄하는 추상적인 모듈이 구체적인 작업을 실행하는 모듈 호출
ISP
- 인터페이스 분리 원칙
- 인터페이스를 클라이언트에 특화되도록 분리시키라는 설계 원칙
- 클라이언트 자신이 이용하지 않는 기능에는 영향을 받지 않아야 함
SRP와 ISP의 관계
- 단일 책임을 수행하지 않고 여러 책임을 수행하는 비대한 클래스를 SRP에 따라 단일 책임을 갖는 여러 클래스들로 분할하고 각자 인터페이스를 제공하면 ISP도 만족시킬 수 있음
- SRP를 만족하더라도 ISP를 반드시 만족할 수 있는 것은 아님
- 예시) 게시판의 여러 기능을 구현한 클래스가 있을 경우
- 글쓰기, 읽기, 수정, 삭제 기능이 있음
- 클라이언트에 따라 기능을 일부분만 사용할 수 있음
- 게시판이 게시판에 관련된 책임만 수행 👉 SRP 만족
- 모든 메서드가 들어있는 인터페이스를 클라이언트에 상관 없이 사용 👉 ISP 위배
- 예시) 게시판의 여러 기능을 구현한 클래스가 있을 경우
댓글남기기