1. 단일 책임 원칙 (SRP, Single Responsibility Principle)

객체는 단 하나의 책임만 가져야 합니다. 객체에 책임을 할당할 때는 어떤 객체보다도 작업을 잘 할 수 있는 객체에 책임을 할당해야합니다. 그렇다면 설계원칙을 학습해야하는 이유는 무엇일까요?  바로 예측하지 못한 변경사항이 발생하더라도 유연하고 확장성이 있도록 시스템 구조를 설계하기 위해서입니다. 좋은 설계란 새로운 요구사항이 생겼을 때 가능한 영향을 받는 부분을 줄이도록 하는 것입니다. 즉, 객체가 변해야 하는 이유는 1가지 이어야합니다. 책임을 많이 질수록 클래스 내부에서 서로 다른 역할을 수행하는 코드끼리 강하게 결합될 가능성이 높아집니다.

* 산탄총 수술

하나의 책임이 여러 개의 클래스들로 분산되어 있는 경우에도 단일 책임 원칙에 입각해 설계를 변경해야 하는 경우도 있습니다. 예를 들어 로깅, 보안, 트랜잭션과 같은 횡단 관심으로 분류할 수 있는 기능 횡단 관심에 속하는 기능은 대부분 시스템 핵심 기능(하나의 책임) 안에 포함되는 부가 기능(여러 개의 클래스로 분리)입니다.

 

부가 가능을 별개의 클래스로 분리해 책임을 담당하게 합니다. 즉, 여러 곳에 흩어진 공통 책임을 한 곳에 모으면서 응집도를 높입니다. 그러나 이런 독립 클래스를 구현하더라도 구현된 기능들을 호출하고 사용하는 코드는 해당 기능을 사용하는 코드 어딘가에 포함될 수밖에 없습니다.

* 관심지향 프로그래밍(AOP)과 횡단 관심 문제

횡단 관심 문제(Cross Cutting Concern)를 해결하는 방법으로는 관심지향 프로그래밍 기법(AOP, Aspect Oriented Programming)이 있습니다. 횡단 관심을 수행하는 코드는 aspect라는 특별한 객체로 모듈화하고 weaving이라는 작업을 통해 모듈화한 코드를 핵심 기능에 끼워넣을 수 있습니다. 이를 통해 기존의 코드를 전혀 변경하지 않고도 시스템 핵심 기능에서 필요한 부가 기능을 효과적으로 이용할 수 있습니다. 만약 횡단 관심에 변경이 생긴다면 해당 aspect만 수정하면 됩니다.

 

[AOP 관련 용어]

  • JoinPoint: 애플리케이션 실행 중의 특정한 지점
  • Advice: 특정 JoinPoint에 실행하는 코드
  • PointCut: 여러 JoinPoint의 집합체로, 언제 Advice를 실행할지 정의할 때 사용
  • Aspect: 애플리케이션이 가져야 할 로직과 그것을 실행해야 하는 지점을 정의한 것
  • Weaving: 애플리케이션 코드의 해당 지점에 Aspect를 실제로 주입하는 과정

2. 개방-폐쇄의 원칙(OCP, Open Closed Principle)

인터페이스(변하지 않는 것)에서 구체적인 출력 매체(변하는 것)를 캡슐화해 처리하도록 해야합니다. 설계를 할 때 가장 중요한 것은 무엇이 변하는 것인지, 무엇이 변하지 않는 것인지를 구분해야 합니다. 설계의 기본(의존 역전 원칙(DIP)과 밀접)이라고 할 수 있습니다. 변해야하는 것은 쉽게 변할 수 있고, 변하지 않아야 하는 것은 변하는 것에 대해서 영향을 받지 않아야합니다. 클래스를 변경하지 않고도 대상 클래스의 환경을 변경할 수 있게 설계해야합니다. 특히 단위 테스트를 수행할 때 중요합니다.

3. 리스코프 치환 원칙(LSP, Liskov Substitution Principle)

일반화 관계에 대한 이야기며, 자식 클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행할 수 있어야 합니다. LSP는 부모 클래스와 자식 클래스 사이의 행위가 일관성이 있어야 한다는 의미입니다. 즉, LSP를 만족하면 프로그램에서 부모 클래스의 인스턴스 대신에 자식 클래스의 인스턴스로 대체해도 프로그램의 의미는 변화되지 않습니다.

 

일반화 관계는 다음과 같습니다. 포유류는 부모 클래스이고, 원숭이는 자식 클래스입니다.

 - "is a kind of"의 관계 (ex) 원숭이 is a kind of 포유류

 

LSP를 만족시키는 간단한 방법은 재정의하지 않는 것입니다. 즉, 부모 클래스에서 상속받은 메서드들이 자식 클래스에 오버라이드, 즉 재정의되지 않도록 하면 됩니다. 

4. 인터페이스 분리 원칙 (ISP, Interface Segregation Principle)

인터페이스를 클라이언트에 특화되도록 분리시키라는 설계 원칙입니다. 클라이언트 자신이 이용하지 않는 기능에는 영향을 받지 않아야 합니다. 단일 책임 원칙(SRP)과 밀접합니다. 

복합기의 기능을 제공하는 클래스는 매우 커질 가능성이 큽니다. 하지만 복합기의 기능을 클라이언트에서 동시에 사용하는 경우는 거의 없습니다. 팩스 기능의 변경으로 인해 프린터 기능만 이용하는 클라이언트가 영향을 받을 수 있습니다. 이렇게 클라이언트와 무관하게 발생한 변화로 클라이언트 자신이 영향을 받지 않으려면 범용의 인터페이스보다는 클라이언트에 특화된 인터페이스를 사용해야합니다.

복합기를 사용하는 객체들마다 자신이 관심을 갖는 메서드들만 있는 인터페이스를 제공받도록 설계하였습니다. 
인터페이스가 일종의 방화벽 역할을 수행하는 것 입니다. 클라이언트는 자신이 사용하지 않는 메서드에 생긴 변화로 인한 영향을 받지 않게 됩니다.

SRP와 ISP의 관계

어떤 클래스가 여러 책임을 수행하게 되면 방대한 메서드를 가진 비대한 클래스가 될 가능성이 커집니다.
이렇게 비대한 클래스를 단일 책임을 갖는 여러 클래스로 분할하면 SRP를 만족합니다. 또한 각자의 인터페이스를 제공한다면 ISP를 만족할 수 있습니다.

 

그렇다면 ISP는 SRP를 만족하면 성립될까요? 반드시 그렇다고 할 수 없습니다.

5. 의존 역전 원칙(DIP, Dependency Inversion Principle)

의존 관계를 맺을 때 변화하기 쉬운 것 또는 자주 변화하는 것보다는 변화하기 어려운 것, 거의 변화가 없는 것에 의존하라는 원칙입니다. 객체 사이에 서로 도움을 주고 받으면 의존 관계가 발생하는데, DIP는 이때의 가이드라인에 해당합니다. OCP가 되려면 기본적으로 DIP가 만족되어야합니다. 

 

변하기 쉬운 것과 변하기 어려운 것의 구분은 다음과 같이 구분하면 됩니다.

  • 변하기 어려운 것 : "정책", "전략"
  • 변하기 쉬운 것 : "구체적인 방식", "사물"

DIP를 만족하는 설계 예시

객체지향 관점에서 변하기 어려운 추상적인 것들을 표현하는 수단

  • 추상 클래스
  • 인터 페이스

DIP를 만족시키는 방법

  • 어떤 클래스가 도움을 받을 때 구체적인 클래스보다는 인터페이스나 추상 클래스와 의존 관계를 맺도록 설계해야 합니다.
  • DIP를 만족하는 설계는 변화에 유연한 시스템이 됩니다. 의존성 주입(DI)이라는 기술로 변화를 쉽게 수용할 수 있는 코드를 작성할 수 있습니다. 의존성 주입이란 클래스 외부에서 의존되는 것을 대상 객체의 인스턴스 변수에 주입하는 기술입니다.

할리우드 원칙(Hollywood principle)이란?

  • “Don’t call us, we’ll call you.”
  • we: 프레임워크
  • you: 구현 모듈, 필요 객체

예제 코드

public class Kid {
	private Toy toy;

	public void setToy(Toy toy){ this.toy = toy; }
		public void play(){ System.out.println(toy.toString()); }
	}
    
	public class Robot extends Toy {
		public String toString() { return "Robot"; }
	}
    
	public class Lego extends Toy {
		public String toString() { return "Lego"; 
    }
}
public class Main {

	public static void main(String[] args) {
  		Kid k = Kid();
  		// 1. 아이가 로봇을 가지고 놀 때
  		Toy t = new Robot();
  
  		// 2. 아이가 레고를 가지고 놀 때
  		Toy t = new Lego();
  		k.setToy(t);
  		k.play();
	}
}

즉, 아이가 마음이 바뀌어 다른 어떤 장난감을 가지고 놀더라도 기존의 코드에 전혀 영향을 받지 않고도 장난감을 바꿀 수 있습니다.

REFERENCE

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기