본문 바로가기
객체 지향 프로그래밍/스프링 입문을 위한 자바 객체 지향의 원리와 이해

[스프링 입문을 위한 자바 객체 지향의 원리와 이해] 객체 지향 설계 5원칙 - SOLID

by 책 읽는 개발자_테드 2021. 10. 15.
반응형

학습 목표

· SOLID란?

· SRP(Single Responsibility Principle) - 단일 책임 원칙

· OCP(Open Closed Principle) - 개방 폐쇄 원칙

· LSP(Liskov Substitution Principle) - 리스코프 치환 원칙

· ISP(Interface Segregation Principle) - 인터페이스 분리 원칙

· DIP - 의존 역전 원칙


SOLID란?

· 객체 지향 언어의 시초인 Simula67이 1960년 발표되고,

  긴 세월 동안 수많은 시행착오 베스트 프랙티스 속에 집대성된 객체 지향 설계의 정수

· 로버트 C. 마틴이 2000년대 초 객체 지향 프로그래밍 및 설계의 다섯 가지 기본 원칙으로 제시, 마이클 페더스가 두문자어로 소개

 

· 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) - 의존 역전 원칙

 

· SOLID의 특징:

   - 객체 지향 4대 특성을 발판으로 하고, 스프링 프레임워크의 근간

   - 객체 지향 프로그램을 구성하는 속성, 메서드, 클래스, 객체, 패키지, 모듈, 라이브러리, 프레임워크, 아키텍처 등 다양한 곳에 적용 가능

   - 제품이 아닌 개념이기에, 적용됐는지 아닌지 애매모호하거나 보는 사람의 관점에 따라 다르게 해석될 수 있음

   - 응집도는 높이고(High Cohesion), 결합도는 낮추라(Loose Coupling)는 고전 원칙 객체 지향 관점으로 재정립한 것

      - 결합도: 모듈(클래스) 간의 상호 의존 정도를 뜻함,

         결합도가 낮으면, 모듈 간 상호 의존성이 줄어듦 -> 객체 재사용과 수정, 유지보수가 용이

      - 응집도: 하나의 모듈(클래스) 내부에 존재하는 구성 요소들의 기능적 관련성을 뜻함,

         응집도가 높은 모듈은 하나의 책임에 집중하고 독립성이 높아짐 -> 재사용, 기능의 수정, 유지보수에 용이

 

· SOLID의 장점: SOLID를 잘 녹여낸 소프트웨어는 그렇지 않은 소프트웨어에 비해 상대적으로

   1. 이해하기 쉽고,

   2. 리팩터링과 유지보수가 수월하고,

   3. 논리적으로 정연함

· SOLID의 단점: 해당 원칙을 적용하면 소스 파일의 개수가 많아짐

 

· 관심사의 분리(Separation Of Concerns)를 적용하면 자연스럽게

  단일 책임 원칙, 인터페이스 불리 원칙, 개방폐쇄 원칙에 도달하게됨

   - 관심사의 분리: 하나의 속성, 하나의 메서드, 하나의 클래래스, 하나의 모듈, 하나의 패키지에는 하나의 관심사만 들어 있어야한다는 것

 

SRP(Single Responsibility Principle) - 단일 책임 원칙

· 객체 지향 프로그램을 구성하는 요소(클래스, 메서드, 패키지 등)는 하나의 책임과 역할만 갖는다 

· "어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다" - 로버트 C. 마틴

· SRP와 가장 관계 깊은 객체 지향 4대 특정: 추상화

   - 애플리케이션의 경계를 정하고, 추상화를 통해 클래스를 선별하고 속성과 메서드를 설계할 때

      SRP 원칙을 고려하는 습관을 들이자

 

▶ 예시 - 단일 책임을 지키지 않은 클래스

https://sehun-kim.github.io/sehun/solid/

 

· 문제점: 위의 남자 클래스는 역할과 책임이 많아서, 하나의 역할과 책임에 문제가 생겨도 다른 부분에 영향을 줄 수 있음

  예) 여자친구와 문제가 생겼는데 어머니, 직장상사, 소대장 모두가 피곤해질 수 있음

 

· 이런 경우 아래 처럼 클래스가 하나의 역할과 책임을 갖도록 분리해야함

https://sehun-kim.github.io/sehun/solid/

 

▶ 예시 - 단일 책임을 지키지 않은 메서드

public class 강아지 {
    final static Boolean 수컷 = true;
    final static Boolean 암컷 = false;
    Boolean 성별;
    
    void 소변보다(){
        if (this.성별 == 수컷) {
            //한쪽 다리를 들고 소변을 본다.
        }else{
            //뒷다리 두 개를 굽혀 앉은 자세로 소변을 본다.
        }
    }
}

 

· 문제점: 강아지가 수컷이냐, 암컷이냐에 따라 소변보다() 메서드에서 분기 처리가 진행됨

  메서드가 단일 책임 원칙을 지키지 않을 경우 나타나는 대표적인 냄새 -> 분기 처리를 위한 if 

 

· 아래와 같이 리팩터링 가능

abstract class 강아지 {
	abstract void 소변보다()
}
 
class 수컷강아지 extends 강아지 {
    void 소변보다() {
		// 한쪽 다리를 들고 소변을 본다.
    }
}
 
class 암컷강아지 extends 강아지 {
    void 소변보다() {
		// 뒷다리 두 개를 굽혀 앉은 자세로 소변을 본다.
    }
}

 

OCP(Open Closed Principle) - 개방 폐쇄 원칙

 

· 소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있고, 변경에 대해서 닫혀 있어야 한다. - 로버트 C. 마틴

   - 의역: 자신의 확장에는 열려 있고, 주변의 변화에 대해서는 닫혀 있어야 한다.

 

· 개방 폐쇄 원칙을 무시하고 프로그램을 작성하면,

  객체 지향 프로그래밍의 가장 큰 장점인 유연성, 재사용성, 유지보수성 등을 얻을 수 없음

 

▶ 예시 - 개방 폐쇄 원칙을 지키지 않은 클래스 설계

https://sehun-kim.github.io/sehun/solid/

 

· 운전자와 차량 관계를 위와 같이 설계하고, 운잔자가 사용하는 차량이 마티즈에서 쏘나타로 변경되었다고 가장하자.

· 문제점: 차량이 바뀌면서 운전자의 행동에도 변화가 발생함. 즉, 주변 변화에 자신이 의존적으로 변화하게됨.

   - 창문과 기어가 수동인 마티즈에서 창문과 기어가 자동인 쏘나타로 변경했으므로

 

 

· 아래와 같이 상위 클래스 또는 인터페이스 중간에 둠으로써 다양한 자동차가 생겨도 운전자의 행동이 변화하지 않을 수 있음

   - 자동차 입장에서 자신의 확장에 개방되고, 운전자 입장에서는 주변의 변화에 폐쇄됨

https://sehun-kim.github.io/sehun/solid/

 

▶ 예시 - OCP가 잘 적용된 사례 1: JDBC

· JDBC를 사용하는 클라이언트는 데이터베이스가 바뀌어도(예:MySQL -> 오라클) Connection 설정 부분 이외에 수정할 필요가 없음

   - Connection 설정 부분을 별도의 설정 파일로 분리해두면, 클라이언트는 한 줄도 변경 x

· JDBC, iBatis, MyBatis, 하이버네이트 등 데이터베이스 프로그래밍을 지원하는 라이브러리, 프레임워크에서도 OCP 적용 예를 볼 수 있음

 

https://sehun-kim.github.io/sehun/solid/

 

▶ 예시 - OCP가 잘 적용된 사례 2: 자바

· 개발자의 소스코드와 운영체제별 JVM 사이에는 목적파일이라는 완충 장치가 존재하여, 

  자바 개발자는 작성하는 윈도우, 리눅스 등 어느 운영체제에서 운영될 지 걱정하지 않음

· 개발자가 작성한 소스코드는 운영체제 변화에 닫혀 있고, 각 운영체제별 JVM은 확장에 열려 있음

https://medium.com/@ahn428/java-jvm-java-virtual-machine-jre-java-runtime-environment-jdk-java-developement-kit-fed91def1d6f

 

LSP(Liskov Substitution Principle) - 리스코프 치환 원칙

 

· 상위 클래스의 인스턴스는 하위 클래스의 인스턴스로 대체해 사용할 수 있어야 한다. (이펙티브 자바)

예) 동물 뽀로로 = new 펭귄()

 

· 객체 지향의 상속은 다음의 조건을 만족함

   - 하위 클래스 is a kind of 상위 클래스 - 하위 분류는 상위 분류의 한 종류

   - 구현 클래스 is able to 인터페이스 - 구현 분류는 인터페이스할 수 있어야 함

   예) AutoCloseable - 자동으로 닫힐 수 있어야 함 / Runnable - 실행할 수 있어야 함

 

· 객체 지향에서 상속은 조직도나 계층도가 아닌 분류도가 돼야 하며,

  그렇지 않으면 위 문장대로 구현되지 않은 코드가 존재하게 됨

   - 리스코프 치환 원칙은 객체 지향의 상속 특성을 올바르게 활용하면 자연스럽게 얻게 되는 것

 

▶ 예시 - 리스코프 치환 원칙 위반 사례: 계층도/조직도

https://sehun-kim.github.io/sehun/solid/

 

▶ 예시 - 리스코프 치환 원칙 적용 사례: 분류도

https://sehun-kim.github.io/sehun/solid/

 

 

ISP(Interface Segregation Principle) - 인터페이스 분리 원칙

 

· 클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안 된다. - 로버트 C. 마틴

 

▶ 예시 - 인터페이스 분리 원칙을 지키지 않은 클래스 설계

 

https://sehun-kim.github.io/sehun/solid/

· 위 그림은 단일 책임 원칙(SRP)으로 남자 클래스를 하나의 역할(책임)만 하는 다수의 클래스로 분할하는 방법으로 앞서 해결했었지만,

  인터페이스 분리 원칙(ISP)을 통해 문제를 해결할 수도 있음

 

https://sehun-kim.github.io/sehun/solid/

· 남자 클래스를 여자친구를 만날 때 남자친구 인터페이스로 제한하고,

  어머니와 있을 때 아들 인터페이스로 제한하고,

  직장 상사 앞에서는 사원 인터페이스로 제한하고,

  소대장 앞에서는 소대원 인터페이스로 제한하는 것이 인터페이스 원칙의 핵심

 

· 같은 문제에 대한 두 가지 다른 해결책(SRP, ISP)을 사용할 수 있지만, 

  특별한 경우가 아니라면 SRP을 적용하는 것이 더 좋은 해결책

 

· 인터페이스는 작을수록, 상위 클래스는 풍성할수록 좋음

   - 반약한 상위 클래스를 사용하면, 불필요한 하위 클래스로의 형변환이 발생하면서 상속의 혜택을 제대로 누리기 힘듦

   - 인터페이스 최소 원칙: 인터페이스를 통해 메서드를 외부에 제공할 때는 최소한의 메서드만 제공하라

 

DIP - 의존 역전 원칙

· 고차원 모듈은 저차원 모듈에 의존하면 안 된다.

  두 모듈 모두 다른 추상화된 것에 의존해야 한다.

  추상화된 것은 구체적인 것에 의존하면 안 된다.

  구체적인 것이 추상화된 것에 의존해야 한다.

  자주 변경되는 구체(Concrete 클래스에 의존하지 마라 - 로버트 c. 마틴

   - 의역: 자신보다 변하기 쉬운 것에 의존하지 마라

 

· 자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스에 의존하게 하는 것이다. 이렇게하면, 변하기 쉬운 것의 변화에 영향받지 않게 된다.

· 상위 클래스일수록, 인터페이스일수록, 추상 클래스일수록 변하지 않은 가능성이 높음

· 사용예: JDBC

 

▶ 예시 - 의존 역전 원칙을 적용 x

· 자동차는 한 번 사면 몇 년은 타지만 스노우타이어는 계절이 바뀌면 일반 타이어로 교체해야함

· 이렇게 자신보다 더 자주 변하는 것에 의존하므로, '부서지기 쉬움'이라는 나쁜 냄새를 풍김

 

▶ 예시 - 의존 역전 원칙을 적용 o

· 자동차가 구체적인 타이어가 아닌 추상화된 타이어 인터페이스에 의존하도록 변경

   - 스노우타이어가 다른 타이어로 변경되어도 자동차는 이제 그 영향을 받지 않음

· 위 내용은 개방 폐쇄 원칙(OCP)을 설명할때도 나온 설명으로, 하나의 해결책을 찾으면 그 안에 여러 설계 원칙이 녹아있는 경우가 많음

   - 디자인 패턴, 설계 원칙 모두 객체 지향 4대 특성을 활용하고, 그 대표적인 것이 상속과 다형성이므로 결국 비슷비슷한 코드로 구현됨

 

// 기존 코드
class Car {
    SnowTire tire = new SnowTire();
}

// DIP 적용
class Car {
    Tire tire;

    void setTire(Tire tire) {
        this.tire = tire;
    }
}
반응형

댓글