개발바닥곰발바닥
Published 2022. 4. 2. 04:04
[객체지향] SOLID 원칙이란 기타
728x90

SOLID 원칙

SOLID 원칙이란 객체 지향 프로그래밍 및 설계의 다섯 가지 기본 원칙이다. 시간이 지나도 유지 보수와 확장이 쉬운 유연성 있는 시스템을 만들고자 할 때 적용할 수 있는 지침이다.

이 원칙들은 애자일 소프트웨어 개발의 전반전 전략의 일부라고 볼 수 있다.

SOLID 원칙을 이루는 요소를 보면 다음과 같다.

  1. SRP(Single responsibility principle) - 단일 책임 원칙
  2. OCP(Open/closed principle) - 개방 폐쇄 원칙
  3. LSP(Liskov substitution principle) - 리스코프 치환 원칙
  4. ISP(Interface segregation principle) - 인터페이스 분리 원칙
  5. DIP(Dependency inversion principle) - 의존관계 역전 원칙

1. SRP - 단일 책임 원칙

한 클래스는 하나의 책임만 가져야 한다.

  • 모든 클래스는 하나의 책임만 가지며, 클래스는 그 책임을 완전히 캡슐화해야 한다.
  • 클래스가 제공하는 모든 기능은 이 책임과 부합해야 한다.
  • 어떤 변화에 의해 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.
  • SRP 원리를 적용하면 책임 영역이 확실해지기 때문에 한 책임의 변경이 다른 책임의 변경으로 연쇄 작용이 일어나는 것을 막을 수 있다(관심사 분리)
  • 책임을 적절히 분리하면 유지보수와 가독성이 향상된다.
  • 무조건 책임을 분리한다고 SRP가 적용되는 것은 아니다. 각 개체 간의 응집력이 있다면 병합이 순 작용이 되고, 결합력이 있다면 분리가 순 작용이 된다.

SRP를 적용하지 않은 코드

public class 악기 {

	public void 연주하다(String type) {
		if(type.equals("피아노") {
				// 건반을 눌러 연주
		}
		else if(type.equals("기타") {
				// 줄을 튕겨 연주
		}
		...
	}
}

악기라는 클래스에서 악기 종류에 따라 연주 방법이 다르므로 단일 책임 원칙에서 벗어난다.

SRP를 적용한 코드

public interface 악기 {
	public void 연주하다();
}

public class 피아노 implements 악기 {
	public void 연주하다() {
			// 건반을 눌러 연주
	}
}

public class 기타 implements 악기 {
	public void 연주하다() {
			// 줄을 튕겨 연주
	}
}

악기를 interface로 추상화하면서 피아노와 기타 클래스가 단일 책임 원칙을 준수하게 됐다.

2. OCP - 개방/폐쇄 원칙

소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.

소프트웨어 요소(컴포넌트, 클래스, 모듈, 함수)의 변경을 위한 비용은 최대한 줄이고, 확장을 위한 비용을 극대화해야 한다는 원칙이다.

기능을 추가하거나 변경해야 할 때 기존 코드를 변경하지 않아도 기존 코드에 새로운 코드를 확장함으로써 기능의 추가나 변경이 가능해야 한다.

개방-폐쇄 원칙은 객체 지향 프로그래밍의 핵심 원칙으로 이 원칙을 따르지 않는다고 해서 객체지향 언어의 구현이 불가능한 것은 아니지만 객체지향 프로그래밍의 가장 큰 장점인 유연성, 재사용성, 유지보수성을 결코 얻을 수 없으므로 개방-폐쇄 원칙은 반드시 지켜야할 기본적인 원칙이다.

3. LSP - 리스코프 치환 원칙

프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.

리스코프 치환은 자료형 S가 자료형 T의 하위형이라면 프로그램의 속성의 변경 없이 자료형 T의 객체를 자료형 S의 객체로 치환할 수 있어야 한다는 원칙이다.

한 마디로 서브타입은 언제나 기반 타입으로 교체할 수 있어야 한다는 뜻이다. 상속은 extends 관계이든 인터페이스 상속(implements)이든 다형성을 통한 확장성 획득을 목표로 한다. LSP 원칙도 서브 클래스가 확장에 대한 인터페이스를 준수해야 함을 의미한다. 다형성과 확장성을 극대화하려면 하위클래스보다는 상위 클래스(인터페이스)를 사용하는 것이 더 좋다.

일반적으로 선언은 기반 클래스로하고 생성은 구체 클래스로 대입하는 방법을 사용한다. SRP 원칙에서 예를 들었던 코드로 간단하게 다시 예를 들면 아래와 같다.

public static void main(String[] args) {
/*선언할 때 자료형은 상위 인터페이스를 사용했으므로 구체 클래스는
기타가 될 수도 있고, 피아노가 될 수도 있다.*/
	악기 instrument = new 기타();
}

LSP는 규약을 준수하는 상속구조를 제공하고 LSP를 바탕으로 OCP는 확장하는 부분에 다형성을 제공해 유연성 있는 프로그램을 만들 수 있도록 한다.

LSP를 위반하는 전형적인 사례

너비와 높이의 조회(getter) 및 할당(setter) 메서드를 가진 직사각형 클래스로부터 정사각형 클래스를 파생하는 경우를 들 수 있다. 정사각형 클래스는 항상 너비와 높이가 같기 때문에 정사각형 클래스가 직사각형을 다루는 문맥에서 사용되는 경우, 정사각형의 너비와 높이는 독립적으로 변경할 수 없기 때문에 예기치 못한 행동을 하게 된다. 정사각형의 불변 조건(높이와 너비가 같음)을 유지하면, 이 메서드는 크기를 독립적으로 변경할 수 있는 직사각형의 할당자의 사후 조건을 위반한다.

그러나, 정사각형과 직사각형이 조회(getter) 메서드만 가진다면 (불변 객체) LSP 위반이 발생하지 않는다.

ISP - 인터페이스 분리 원칙

특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.

인터페이스 분리 원칙은 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다는 원칙으로 어떤 클래스가 다른 클래스에 종속될 때에는 가능한 최소한의 인터페이스만을 사용해야 한다는 뜻이다.

큰 덩어리의 인터페이스들을 구체적이고 작은 단위들로 분리시킴으로써 클라이언트들이 꼭 필요한 메서드만 이용할 수 있게 한다. 이런 작은 단위들을 역할 인터페이스라고도 부른다. ISP 원칙을 통해 시스템의 내부 의존성을 약화시켜 리팩토링, 수정, 재배포를 쉽게 할 수 있다.

SRP가 클래스의 단일책임을 강조한다면 ISP는 인터페이스의 단일책임을 강조한다. 그러나 ISP는 어떤 클래스 혹은 인터페이스가 여러 책임 또는 역할을 갖는 것을 인정한다.

DIP - 의존관계 역전 원칙

프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다.

의존관계 역전 원칙은 상위 계층이 하위 계층에 의존하는 의존관계를 반전(역전)시킴으로써 상위 계층이 하위 계층의 구현으로부터 독립되게 할 수 있다. 의존성 주입(DI)는 이 원칙을 따르는 방법 중 하나다.

이 원칙은 다음과 같은 사항을 준수해야 한다.

  1. 상위 모듈은 하위 모듈에 의존하면 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.
  2. 추상화는 세부 사항에 의존하면 안된다. 세부사항이 추상화에 의존해야 한다.

이 원칙은 ‘상위와 하위 객체 모두가 동일한 추상화에 의존해야 한다’는 객체 지향 설계의 대원칙을 제공한다.

만약 연주자라는 클래스가 있을 때 연주할 악기로 기타 객체를 생성해서 의존하게 된다면, 연주하는 악기가 바뀔 때마다 연주자 클래스를 수정해야 할 것이다. 즉 연주자 자신보다 자주 변하기 쉬운 것에 의존하지말고, 추상화된 악기 인터페이스에 의존한다면 악기 종류가 바뀌어도 연주자는 영향을 받지 않는다.

728x90
profile

개발바닥곰발바닥

@bestinu

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!