SOLID란, 로버트 마틴이 정의한 객체 지향 설계의 중요한 원칙을 두문자어로 표현한 것이다.
SOLID 원칙을 단순히 외우는 것을 넘어, 이번 기회에 이 원칙을 이해하고, 다른 개발자 친구들과 함께 서로가 생각하는 SOLID원칙에 대한 생각을 공유하는 시간을 가져보았다.
이러한 시간을 가지며, 좁게만 생각했던 SOLID원칙에 대해 조금은 더 넓게 확장해서 생각할 수 있게 되었고, 각각 다른 개념이라고 생각했던 것이 사실은 모두 이어지는, 연관된 개념이라는 것을 깨닫게 되었다.
SOLID의 핵심, 즉 객체 지향 설계의 핵심은 분리에 있다고 생각한다. 그리고, SOLID라는 개념은 하나의 원칙을 지킴으로써, 모두 다 지켜질 수 있는 연관된 개념이다.
SRP를 잘 지키기 위해서 분리를 하고, 인터페이스를 만들게 되면, DIP원칙을 지키게 된다. 또한, 이를 통해 하위타입으로 치환할 수 있다는 LSP원칙을 지킬 수 있다. 이렇게 잘 분리되어 있다면, 확장에 용이하기 때문에 OCP까지도 모두 만족하게 된다.
이 모든 것은 분리를 통해 이루어질 수 있기때문에, 객체 지향 설계의 핵심은 분리다.
1. SRP(Single Responsibility Principle) - 단일 책임 원칙
클래스는 하나의 책임만을 가지고 있어야 한다.
그렇기 때문에, 클래스는 해당 클래스와 관련된 단 하나의 어떤 것(액터)에 대한 책임만 있으므로, 이 액터가 아닌 다른 액터에 대한 이유로 수정이 되어서는 안된다. 라는 것이 단일 책임 원칙이다.
2. OCP(Open Closed Principle) - 개방 폐쇄 원칙
소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀있어야 한다.
확장할 때 기존 코드를 건드려야 한다는 것은, 모듈화가 완전하게 되어있지 못하다는 의미다. 그렇기 때문에 완전한 모듈화를 통해, 기존에 작성한 코드를 건드리지 않고, 코드를 더할 수 있어야한다. 라는 것이 개방 폐쇄 원칙이다.
3. LSP(Liskov Substitution Principle) - 리스코프 치환 법칙
프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으며, 하위타입의 인스턴스로 바꿀 수 있어야 한다.
단순히 인터페이스뿐만 아니라, 타입이 될 수 있는 클래스까지도 포함할 수 있지만, 대표적으로 인터페이스를 예시로 많이 언급한다.
즉, 자식 클래스는 부모 클래스의 모든 기능을 대체할 수 있어야 하며, 부모 클래스의 메서드와 동작을 일관되게 유지할 수 있어야 한다. 라는 것이 리스코프 치환 법칙이다.
4. ISP(Interface Segregation Principle) - 인터페이스 분리 원칙
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
인터페이스란 추상메소드로만 이루어진 클래스를 말한다. 이러한 인터페이스를 상속받은 클래스가 인스턴스를 생성할 수 있는 클래스가 되기 위해서는 인터페이스에 정의된 추상메소드를 모두 구현해야만 한다.
즉, 클래스에서 필요없는 기능을 가지더라도 구현해야만 하는데, 이러한 불필요한 구현을 최대한 방지하기 위해, 클라이언트가 필요에 따라 사용할 수 있는 더 작은 방법으로 인터페이스를 나누는 것이다.
다시 말해 객체가 자신에게 필요한 기능만을 가지도록 제한하는 것이 인터페이스 분리 법칙이다.
분리 원칙을 잘 지키지 못한 예시
- 현악기와 관악기의 기능을 모두 다 담은 Instrument Interface
interface Instrument {
void play();
void tuneStrings();
void adjustBreath();
}
class Guitar implements Instrument {
@Override
public void play() {
System.out.println("나 기타쳐~~~");
}
@Override
public void tuneStrings() {
System.out.println("기타 튜닝중이야~~~~");
}
@Override
public void adjustBreath() {
System.out.println("기타연주는 호흡조절이 필요 없는데, 어쩔 수 없이 구현한다.......");
}
}
class Flute implements Instrument {
@Override
public void play() {
System.out.println("나 플룻부는 중~~~~");
}
@Override
public void tuneStrings() {
System.out.println("플룻은 줄이 없는데, 어쩔 수 없이 구현한다...");
}
@Override
public void adjustBreath() {
System.out.println("관악기는 호흡조절이 생명이다~~~~");
}
}
분리 원칙을 잘 지킨 예시
- 악기의 공통기능을 담은 Playable
- 현악기의 기능을 담은 StringInstrument
- 관악기의 기능을 담은 WindInstrument
interface Playable {
void play();
}
interface StringInstrument extends Playable {
void tuneStrings();
}
interface WindInstrument extends Playable {
void adjustBreath();
}
class Guitar implements StringInstrument {
@Override
public void play() {
System.out.println("나 기타쳐~~~");
}
@Override
public void tuneStrings() {
System.out.println("기타 튜닝중이야~~~~");
}
}
// 플루트 클래스
class Flute implements WindInstrument {
@Override
public void play() {
System.out.println("나 플룻부는 중~~~~");
}
@Override
public void adjustBreath() {
System.out.println("관악기는 호흡조절이 생명이다~~~~");
}
}
5. DIP(Dependency Inversion Principle) - 의존관계 역전 원칙
프로그래머는 추상화에 의존해야지 구체화에 의존하면 안된다.
의존성 주입은 이 원칙을 따르는 방법 중 하나다.
'언어(Language) > JAVA' 카테고리의 다른 글
[JAVA] Optional, 사용하는 이유와 메서드 5가지 (5) | 2024.07.25 |
---|---|
[JAVA] 예외(Exception)는 어디서 처리하는게 좋을까? (0) | 2024.07.24 |
[JAVA] 생성자도 상속이 된다는 입장에서 쓰는 글 (0) | 2024.07.04 |
ERROR: Exception in thread "main" java.lang.ClassCastException (0) | 2024.05.23 |
[Java] Java는 컴파일과 인터프리터 중 어떤 언어에 속할까? (0) | 2024.02.01 |