객체 지향 프로그래밍의 5가지 설계 원칙, SOLID

Dodam·2023년 8월 21일
0

[기타]

목록 보기
4/8
post-thumbnail

SOLID란 객체 지향 프로그래밍을 하면서 지켜야 하는 5대 원칙으로, 각각 SRP(단일 책임 원칙), OCP(개방-폐쇄 원칙), LSP(리스코프 치환 원칙), DIP(의존관계 역전 원칙), ISP(인터페이스 분리 원칙)를 의미한다.

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

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

  • 클라이언트 객체는 직접 구현 객체를 생성하고, 연결하고, 실행하는 다양한 책임을 가지고 있음
  • SRP 단일 책임 원칙을 따르면서 관심사를 분리함
  • 구현 객체를 생성하고 연결하는 책임은 AppConfig가 담당
  • 클라이언트 객체실행하는 책임만 담당

2. 의존관계 역전 원칙(DIP, Dependency Inversion Principle)

프로그래머는 "구체화가 아닌 추상화에 의존해야 한다"의 원칙을 따르는 방법

  • 새로운 할인 정책을 개발하고 적용하려면 클라이언트 코드도 함께 변경해야 한다.
    - 기존 클라이언트 코드(OrderServiceImpl)는 DIP를 지키며 DiscountPolicy 추상화 인터페이스에 의존하는 것 같지만, FixDiscountPolicy 구체화 구현 클래스에도 함께 의존했기 때문
  • 클라이언트 코드가 DiscountPolicy 추상화 인터페이스에만 의존하도록 코드를 변경해야 한다.
  • 클라이언트 코드는 인터페이스만으로는 아무것도 실행할 수 없음
  • AppConfig가 FixDiscountPolicy 객체 인스턴스를 클라이언트 코드 대신 생성해서 클라이언트 코드에 의존관계를 주입함으로써 DIP 원칙을 따르면서 문제도 해결

3. 개방-폐쇄 원칙(OCP, Open Close Principle)

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

  • 다형성을 사용하고 클라이언트가 DIP를 지킴
  • 애플리케이션을 사용 영역과 구성 영역으로 나눔 
  • AppConfig가 의존관계를 FixDiscountPolicy → RateDiscountPolicy로 변경해서 클라이언트 코드에 주입하므로 클라이언트 코드는 변경하지 않아도 됨
  • 소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경은 닫혀 있다.

OCP 원칙을 따른 JDBC

만약 자바 애플리케이션에서 사용하고 있는 데이터베이스를 MySQL에서 Oracle로 바꾸고 싶다면,
복잡한 하드 코딩 없이 그냥 connection 객체 부분만 교체해주면 된다.
즉, 자바 애플리케이션은 데이터베이스라고 하는 주변의 변화에 닫혀(closed) 있는 것이다.
반대로 데이터베이스를 손쉽게 교체한다는 것은 데이터베이스가 자신의 확장에는 열려(open) 있다는 의미이다.


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

서브 타입은 언제나 기반(부모) 타입으로 교체할 수 있어야 한다.

  • LSP는 다형성 원리를 이용하기 위한 원칙 개념
  • 자식 클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행이 보장되어야 한다.
  • 즉, 부모 클래스의 인스턴스를 사용하는 위치에 자식 클래스의 인스턴스를 대신 사용했을 때 코드가 원래 의도대로 작동해야 한다.
  • 다형성을 이용하고 싶다면 extends 대신 implements 하여 인터페이스 타입으로 사용
  • LSP 원칙을 잘 적용한 예제가 자바의 컬렉션 프레임워크(Collection Framework) 이다.

컬렉션 프레임워크 (Collection Framework)

만약 변수에 LinkedList 자료형을 담아 사용하다가, 중간에 전혀 다른 HashSet 자료형으로 바꿔도 add() 메서드 동작을 보장받기 위해서는 Collection 이라는 인터페이스 타입으로 변수를 선언하여 할당하면 된다.
왜냐하면 인터페이스 Collection의 추상 메서드를 각기 하위 자료형 클래스에서 implements 하여 인터페이스 구현 규약을 잘 지키도록 미리 잘 설계되어 있기 때문이다.


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

객체는 자신이 사용하는 메소드에만 의존해야 한다.

  • 인터페이스는 '그 인터페이스를 사용하는 클라이언트'를 기준으로 분리해야 한다.
    즉, 클라이언트 입장에서 사용하는 기능만 제공하도록 인터페이스를 분리해야 한다.
  • SRP 원칙이 클래스의 단일 책임을 강조한다면, ISP는 인터페이스의 단일 책임을 강조한다.
  • 인터페이스는 다중 상속(구현)이 가능하기 때문에, 분리할 수 있으면 최대한 분리하여 각 클래스의 용도에 맞게 implements 하는 설계 원칙이다.

ISP 원칙 위반 예제 및 수정하기

스마트폰 인터페이스는 통화, 메시지 기능 이외에도 무선 충전, AR 뷰어, 생체인식 등의 다채로운 기능을 포함하고 있다.

interface SmartPhone {
	void call(String number); // 통화 기능
  	void message(String number, String text); // 문자 메시지 전송 기능
  	void wirelessCharge(); // 무선 충전 기능
  	void AR(); // 증강 현실(AR) 기능
  	void biometrics(); // 생체 인식 기능
}

만약 갤럭시 S20이나 S21 클래스를 구현한다면, 최신 스마트폰 기종인 만큼 객체의 동작 모두가 필요하므로 ISP 원칙을 만족하게 된다.

class S20 (or S21) implements SmartPhone {
	public void call(String number) {
    }
  
  	public void message(String number, String text) {
    }
  
  	public void wirelessCharge() {
    }
  
  	public void AR() {
    }
  
  	public void biometrics() {
    }
}

그러나 최신 기종 스마트폰뿐만 아니라 구형 기종 스마트폰 클래스도 다뤄야 할 경우 문제가 생긴다.
갤럭시 S3 클래스를 구현해야 한다면 무선 충전, 생체인식과 같은 기능이 포함되어 있지 않기 때문이다.

이렇게 될 경우, 추상 메소드 구현 규칙상 오버라이딩은 하되, 메서드 내부는 빈 공간으로 두거나 혹은 예외(Exception)을 발생토록 구성해야 한다.

결국 필요하지도 않은 기능을 어쩔 수 없이 구현해야 하는 낭비가 발생된다.

class S3 implements SmartPhone {
	public void call(String number) {
    }
  
  	public void message(String number, String text) {
    }
  
  	public void wirelessCharge() {
    	System.out.println("지원하지 않는 기능 입니다.");
    }
  
  	  	public void AR() {
    	System.out.println("지원하지 않는 기능 입니다.");
    }
  
  	  	public void biometrics() {
    	System.out.println("지원하지 않는 기능 입니다.");
    }
}

따라서 각각의 기능에 맞게 인터페이스를 잘게 분리하도록 구성한다. 그리고 잘게 분리된 인터페이스를 클래스가 지원되는 기능만을 선별하여 implements 하면 ISP 원칙이 지켜지게 된다.
interface IPhone {
	void call(String number); // 통화 기능
  	void message(String number, String text); // 문자 메시지 전송 기능
}

interface WirelessChargable {
	void wirelessCharge(); // 무선 충전 기능
}

interface ARable {
	void AR(); // 증강 현실(AR) 기능
}

interface Biometricsable {
	void biometrics(); // 생체 인식 기능
}
class S21 implements IPhone, WirelessChargable, ARable, Biometricsable {
	public void call(String number) {
    }
  
  	public void message(String number, String text) {
    }
  
  	public void wirelessCharge() {
    }
  
  	public void AR() {
    }
  
  	public void biometrics() {
    }
}

class S3 implements IPhone {
	public void call(String number) {
    }
  
  	public void message(String number, String text) {
    }
}
profile
⏰ Good things take time

0개의 댓글