ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • (다시 보는) SOLID 원칙이란?
    JAVA/자바공부 2023. 5. 29. 02:40

    1년 동안 많은 것이 변했다. 

    쌓은 지식도 지식을 쌓은 방법도 변했다. 아마도 그 원인은

     

    1. ChatGPT 등장

    2. 절대적인 Code input/output 증가

     

    다시한번 SOLID 원칙에 대해 정리해 본다.

    SOLID 원칙 전에 객체지향에 대한 복습

     

    객체지향이란?

    • 코드를 데이터(상태-속성)와 기능(함수-행동)으로 객체 정의
    • 객체끼리 메시지로 상호작용하게 하는 코딩 방법
    • 캡슐화, 추상화, 상속, 다형성이 객체지향의 핵심
    • 재사용할 수 있고 확장에 유연한 코드를 작성할 수 있다. 

    객체지향을 왜 해야할까?

    • 소프트웨어 비용을 줄여서 시장에서 경쟁력을 가질 수 있다.
    • 소프트웨어는 계속 변해서 다음의 비용이 발생한다.  
      • 코드 분석 시간
      • 코드 변경 시간 
    • 비용을 줄이기 위한 방법으로 객체지향 뿐만 아니라 다양한 선택지가 있다. 
      1. 객체 지향, 함수형, 리액티브 프로그래밍
      2. 코드, 설계, 아키텍처 (클린 아키텍처, MSA, DRY, SOLID, DDD)
      3. 업무 프로세스 / 문화 (애자일, DevOps)

    SOLID 원칙이란?

    • 로버트 마틴 C (AKA 엉클 밥)가 만들었다. 객체지향을 위한 코드 디자인 원칙이다.
    • 클린코드, 유연한 소프트웨어를 위한 OOP 개발 가이드라인으로 세계적으로 채택되었다.
    • SOLID 원칙을 따르면 이해하기 쉽고, 테스트 쉽고, 확장, 수정이 쉬운 code를 작성할 수 있다.
    • SOLID 원칙은 서로 보충하는 관계다. (Trade off 관계가 아니다.)
    • 절때 맹목적으로 따르지 않는다. 오히려 Component 수가 늘어서 복잡도 상승할 수 있다. 
    • 객관적이 아니라 주관적으로 적용한다. 

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

    • 모든 Software Component는 1개의 책임만 가진다.
    • 엉클 밥 표현을 빌려 (Clean Code) 모든 Software Component는 1개의 reason to change만 가진다와 동의어다.
    • Component는 class, method, function, module 뭐든지 될 수 있다.
    • SRP를 따르면 유지보수가 쉬워진다. 모든 소프트웨어는 변경을 피할 수 없다. 변경으로 수반되는 버그는 비용이다.
    • 너무 많은 Component를 생성하면 복잡도가 올라간다. 상위 개념적으로 묶자(공통 속성을 추상화해서 응집).
    • 추상화란 특정 속성을 축출하여 추상적인 개념으로(전화번호, 이메일을 축출해 User entity로 추상화), 또는 공통적인 속성을 가진 것끼리 묶어서 추상화하는 것 (Email보내는 클래스를 EmailSender, 문자 보내는 클래스를 PhoneMessageSender, 둘다 알림을 보내는 용도로 NotificationSende로 추상화할 수 있다.) 

    응집도 높이고, 결합도는 낮추는 것을 목표로 하는 디자인 원칙이다.

     

    "응집도 결합도란? (Cohesion, Coupling)"

    • 응집도
      • software component끼리 연결된 정도
      • 상위 개념으로 관계 있는 것 끼리 응집한다.
      • 쓰레기장 vs 분리수거(ex, 플라스틱만 분리수거 된 통 - 응집되어 있다.)

    예를 들면, 쓰레기장에 널부러진 쓰레기는 응집도가 없는 상태.

    아파트 앞 분리수거 된 쓰레기는 플라스틱 쓰레기통, 음식물 쓰레기통 나뉘어 있다. 플라스틱 쓰레기통은 응집도가 있는 상태이다.

     

    • 결합도
      • software component끼리 서로 의존하는 정도 

    예를 들면 아래의 기차와 철도의 관계다. 기차의 폭, 사이즈에 따라 철도도 맞춤형으로 제작됬다.

    기차-철도가 결합도가 높아서 다른 철도나 기차로 갈아낄 수 없다. 이를 결합도가 높다고 표현한다. 

     

    기차 뿌뿌

     

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

    • Software Component는 수정엔 닫혀있고, 화장에는 열려있어야한다.
    • 수정엔 닫혀있다.
      • 새로운 기능을 추가할때 기존 코드 변경이 있으면 안된다.
    • 확장에는 열려있다.
      • 새로운 기능, 행동 추가에 있어 확장할 수 있다.

     

    ex) 보험 회사에서 오래된 회원에게 보험료 할인 혜택이 있다고 가정하자. 

    • 건강 보험료에만 할인 혜택이 적용 -> 자동차 보험료에도 할인 혜택적용 
      • feature가 추가되었을때 기존 코드 변경 없이 기능 추가는 어떻게 해야할까?
      • 인터페이스 활용

    3) 리스코브 치환 원칙 LSP (Liskov Substitution Principle)

    • Object should be replaceable with their subtypes without affecting the correctness of the program
    • 프로그램 정확성에 영향주지 않고 하위 유형으로 교체할 수 있어야한다.
    • 상속 (IS-A 관계)
      • 아우디 → Car
      • 타조 → 새 (fly)
      • 가솔린 → 연료
    • 타조는 fly할 수 없는데 새를 상속할 수 있을까?
      • LSP에서는 새의 자리에 타조가 올 수 있어서 좋지 않다고 한다.
    • if it looks like a duck and quacks like a duck but it needs batteries, you probably have the wrong adstraction
    • 상속을 부수고 재구성하기, (상속을 통한 재사용이 어렵다. 상위 클래스들의 증가, 상속 오용의 문제가 올 수 있다.)
      • 레이싱카 → Car => Vihecle 만들어서 다시 구조 잡기
      • 또는 상속말고 조립하는 방식으로 구현한다. (클래스의 증식을 방지할 수 있고, 재사용할 수 있고, 상속 요용 가능성이 없어진다.) 
    • 오버라이드 메서드를 내부 메서드랑 조합해서 잘 활용하자.

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

    • Fat Interface를 피해라
    • 낮은 응집도의 Interface를 피해라
    • Empty Method Implementations를 피해라

    예를 들어 제록스 프린터기, 삼성 프린터기, 캐넌 프린터기가 있고 기능을 구현할 인터페이스를 선언한다 가정해보자. 

    // Fat Interface
    public interface IPrinter {
    
    	public void print();
        
        public void fax();
        
        public void scan();
        
     	public void mail();
        
        public void copy();
    }
    
    
    // 분리한 결과
    public interface IPrint {
    
    	public void print();
        
        public void copy();
    }
    
    public interface IFax {
    
    	public void fax();
        
        public void scan();
        
        public void mail();
    }

     

    5) DIP (Dependency Inversion Principle)

    • 고수준 모듈은 저수준 모듈에 의존하지 않는다. 둘 다 추상화에 의존한다.
    • 추상화에 의존하면 저수준 모듈이 바뀌었을때 고수준 모듈의 기능이 바뀌는 것을 방지할 수 있다.
    • 고수준 모듈을 변경하지 많으면서 저수준 모듈을 변경할 수 있는 유연함을 가질 수 있다. 
    • 요구사항/업무 이해가 높아지면서 상위 수준 관점에서 꾸준히 추상화할 시도가 필요하다. (한번에 설계 불가)

    의존?

    • (기능 구현을 위해 다른 구성 요소를 사용하는 것을 의존한다고 표현한다.)
    • 의존은 변경이 전파될 수 있다. (의존하는 대상이 바뀌면 바뀔 가능성이 높아짐)
      • 의존이 많으면 기능별 분리를 고려한다. 
      • 여러 의존을 단일 기능으로 묶을 수 있는지 고려한다. 
    • 추상화를 통해 결합도를 낮춘다.

    ex) E-commerce (고수준 모듈, 저수준 모듈 예시)

    • 고수준 모듈 : 의미 있는 단일 기능을 제공 (카탈로그를 저장한다.)
    • 저수준 모듈: 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능 제공 (CATALOG를 MYSQL_TABLE에 저장한다.)

    고수준과 저수준
    코드 예시

     

    DI,  Service Locator, 팩토리, 빌더 

    • 의존 대상 객체를 생성하지 않고 의존성 주입 방법

    IoC

    • 스프링이 프로그램 실행중에 외부 주입을 대신해준다.
    • Assembler

     

     

    이전 포스팅

    https://yeoon.tistory.com/55

    반응형
Designed by Tistory.