SOLID 원칙이란 로버트 마틴이 2000년대 초반에 명명한 객체지향 프로그래밍 및 설계의 다섯가지 원칙이다.
이전 게시글에서 클린코드를 소개하면서 SOLID 원칙을 잠깐 소개했었는데 SOLID 개념을 적용하면 프로그래머가 시간이 지나도 유지 보수와 확장이 쉬운 시스템을 만들 수 있다.
SOLID 원칙들은 소프트웨어 작업에서 프로그래머가 소스 코드가 읽기 쉽고 확장하기 쉽게 될 때까지 소프트웨어 소스 코드를 리팩터링 하여 코드 냄새를 제거하기 위해 적용할 수 있는 지침이다.
약어 | 개념 |
SRP | 단일 책임 원칙 (Single responsibility principle)한 클래스는 하나의 책임만 가져야 한다. |
OCP | 개방-폐쇄 원칙 (Open/closed principle)“소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.” |
LSP | 리스코프 치환 원칙 (Liskov substitution principle)“프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.” 계약에 의한 설계를 참고하라. |
ISP | 인터페이스 분리 원칙 (Interface segregation principle)“특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.” |
DIP | 의존관계 역전 원칙 (Dependency inversion principle)프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안된다.” 의존성 주입은 이 원칙을 따르는 방법 중 하나다. |
1. SRP (Single Responsibility) 단일 책임 원칙
- 클래스는 단 하나의 책임만 가져야 한다.
- 클래스를 변경하는 이유는 단 하나여야 한다.
- 이를 지키지 않으면 다른 책임과 관련된 코드에 영향을 미칠 수 있어서 유지보수에 매우 비효율적이다.
예를 들어 A라는 메서드가 있고 A의 메서드 결과를 기반으로 B를 호출하고, B의 결과를 기반으로 C를 호출하도록 구현했다고 생각해보자. 흐름으로 따지면 A의 결과에 따라 C가 결정되는 방식인데 여기서 A의 코드를 바꿔버리면 B와 C가 전부 영향을 받게 된다. 따라서 유지보수가 매우 비효율적인 상황이 발생한다.
즉 변경을 했을 때 파급이 적으면 SRP를 잘 따라서 설계했다고 말한다.
2. OCP (Open - Closed) 개방-폐쇄 원칙
- 확장에는 열려있어야 하고, 변경에는 닫혀있어야 한다.
- 즉, 기존의 코드를 변경하지 않고 기능을 수정하거나 추가할 수 있도록 설계해야 한다.
- 이를 지키지 않으면 instanceof 와 같은 연산자를 이용하거나, 다운 캐스팅 발생
새로운 요구사항이나 기능이 추가되더라도 기존의 코드를 수정하지 않고 확장이 가능해야 한다.
이를 위해 OCP는 추상화와 다형성을 이용한다. 추상화를 통해 인터페이스나 추상 클래스를 정의하고, 다형성을 이용해서 구현 클래스를 교체할 수 있도록 한다.
예를 들어 쇼핑몰에서 상품을 판매하는 기능을 개발하는데 상품의 종류가 한정되어 있어서 상품 클래스를 하나만 만들어두었다.
class Product {
String name;
int price;
public Product(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public int getPrice() {
return price;
}
}
이제 이 쇼핑몰에서 Book이라는 상품을 추가하려고 하면 클래스 자체를 수정해야한다. 그러면 OCP를 위반하게 되니 애초에 Product를 인터페이스로 만들어버린다.
public interface Product {
String getName();
int getPrice();
}
그러면 이 인터페이스를 구현하는 Book클래스를 만들 수 있다.
public class Book implements Product {
String name;
int price;
public Book(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public int getPrice() {
return price;
}
// Book 클래스의 추가적인 기능 구현
...
}
또 Clothing을 추가하려면
public class Clothing implements Product {
String name;
int price;
public Clothing(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public int getPrice() {
return price;
}
// Clothing 클래스의 추가적인 기능 구현
...
}
이렇게 만들면 된다.
즉 OCP는 기존의 코드를 변경하지 않고도 새로운 기능을 추가하고 확장할 수 있도록 하는 객체지향 설계 원칙이다.
3. LSP (Liskov - Substitution) 리스코프 치환 원칙
- 하위 타입 객체는 상위 타입 객체에서 가능한 행위를 수행할 수 있어야 한다.
- 상속관계에서는 꼭 일반화 관계가 성립해야 한다는 의미 (일관성 있는 관계인지)
- 상속관계가 아닌 클래스들을 상속관계로 설정하면, 이 원칙이 위배된다.
즉, 상속 관계에서는 하위 클래스는 상위 클래스의 대체물로서 동작할 수 있어야 한다는 것이다.
이를 위해서는 상위 클래스에서 정의한 인터페이스나메서드를 하위 클래스에서 구현하고, 하위 클래스에서는 상위 클래스에서 정의한 기능을 변경하거나 무시하지 않도록 해야 한다. 이렇게 해야 상위 클래스에서 사용하는 코드가 하위 클래스에서도 동일하게 작동하며, 상위 클래스를 확장하더라도 하위 클래스의 기능에 영향을 미치지 않는다.
하위 클래스에서는 상위 클래스에서 정의한 메서드의 기본 동작을 유지해야 하며, 상위 클래스에서 정의한 인터페이스와 메서드를 구현하고, 예외를 발생시키지 않고 반환값이 호환 가능해야 한다. 이러한 제약을 만족시키면서, 하위 클래스에서는 상위 클래스의 기능을 그대로 유지하면서 필요에 따라 변경하고, 추가적인 기능을 구현할 수 있다.
4. ISP (Interface Segregation) 인터페이스 분리 원칙
- 클라이언트는 자신이 사용하는 메서드에만 의존해야 한다는 원칙
- 즉 자신이 사용하지 않는 인터페이스에 의존하지 않아야 한다는 것을 의미
- 인터페이스도 하나의 책임만을 가져야 한다.(여기서 책임은 기능을 의미한다)
예를 들어 로봇 클래스가 걷기와 날아다니는 기능을 모두 가지고 있다고 가정해보자. 이때, 로봇 클래스에서 걷는 기능과 날아다니는 기능을 분리하여 각각의 인터페이스를 정의하고, 이를 구현한 클래스를 만들어 사용하면 ISP를 만족시킬 수 있다.
따라서 ISP는 인터페이스를 응집력 있게 설계하고, 클라이언트가 원하는 기능만을 포함하도록 하는 객체지향 설계 원칙이다. 이를 통해 인터페이스의 결합도를 낮추고 유지보수성과 확장성을 높일 수 있다.
5. DIP (Dependency Inversion) 의존 역전 원칙
- 구체적인 구현에 의존하지 않고, 추상 클래스나 추상화된 인터페이스에 의존해야 한다.
- 상위 모듈은 하위 모듈에 의존해서는 안 된다.
DIP원칙은 SOLID에서 가장 기본이 되는 원칙 중 하나이며, 다른 SOLID원칙들의 구현에도 중요한 역할을 한다.
예를 들어, 로봇 클래스에서 걷는 기능을 구현하는 WalkingModule 클래스와 날아다니는 기능을 구현하는 FlyingModule 클래스가 있다고 가정해보자. 이때, 로봇 클래스는 WalkingModule과 FlyingModule에 직접 의존하게 되면 DIP에 위배된다. 대신, 로봇 클래스에서는 추상화된 인터페이스나 추상 클래스를 통해 WalkingModule과 FlyingModule에 의존하도록 설계해야 한다. 이를 통해 추상화된 인터페이스에 의존하게 되므로, 구체적인 구현에 의존하지 않으면서도 WalkingModule과 FlyingModule의 기능을 사용할 수 있다.
따라서 DIP는 의존성을 추상화된 인터페이스나 추상 클래스에 의존하도록 하는 객체지향 설계 원칙이다. 이를 통해 결합도를 낮추고 유지보수성과 확장성을 높일 수 있다.
'CS지식' 카테고리의 다른 글
HTTPS 개념을 잡고 가자..(feat. SSL/TLS을 곁들인) (0) | 2024.03.16 |
---|---|
Ajax, axios, fetch 특징 (0) | 2023.05.11 |
클린코드의 개념 (0) | 2023.05.02 |
URL에 SSAFY를 검색하면 일어나는 과정 (0) | 2023.04.27 |