반응형
Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

웹풀스택 공부 중

15 - 7. Java - Design Pattern & SOLID Principle 본문

웹개발/Java

15 - 7. Java - Design Pattern & SOLID Principle

lukeit 2024. 11. 4. 13:18

좋은, 객체 지향적 프로그래밍이란?

  • 중복의 최소화
  • 코드 변경의 용이성
  • 재사용성
  • DRY: Don’t Repeat Yourself
  • KISS: Keep It Simple and Stupid
  • YAGNI: You Ain’t Gonna Need It
  • 구현보다 인터페이스에 맞춰서 코딩하자
    • 구현은 언제든지 바뀔 수 있다. 인터페이스를 사용해서 유연하게 구현하자!
  • 상속 보다는 인터페이스 구성 (Composite)을 사용하자!
    • 상속이 아닌 인터페이스 구성 시 원하는 구현을 붙였다 떼었다가 할 수 있다!

SOLID

객체 지향의 정수

  • “제대로된 분업화”

  • 목표: High Cohesion, Loose Coupling

    • Cohesion: 응집도
      • 관련된 작업을 하나로 묶는 행위 (높여야한다)
    • Coupling: 결합도
      • 하나가 바뀌면 다른 하나가 또 바뀌는 현상 (낮춰야한다)
  • S: Single Responsibility

    하나의 모듈은 한가지 책임만 가진다

    • 그래야지만 모듈이 변경되는 이유가 단 한가지일 수 있다

    • String 대신 StringBuilder를 사용해야하는 이유

      • 불변 (Mutable): String

        • String은 하나하나마다 메모리를 사용한다 (메모리 효율성이 좋진 않음)

        • 이런식으로 하나 만들때마다 계속 메모리 낭비가 있다

      • 가변 (Immutable): StringBuffer/ StringBuilder

        • StringBuilder는 빌드를 호출했을때만 메모리를 사용하기에, 한번에 호출하여 메모리를 아낄 수 있다

  • O: Open-Closed Principle

    확장에 열려있고, 수정에 닫혀있다

    • 확장에 열려있다: 요구사항이 변경될 때, 새로운 동작을 추가하여 애플리케이션의 기능을 확장 시킬 수 있다

    • 수정에 닫혀있다: 기존의 코드를 수정하지 않고, 애플리케이션의 동작을 추가하거나 변경이 가능하다

    • 본질적으로 얘기하는 것은 “추상화”이며, 런타임 의존성과 컴파일 타임 의존성에 대한것이다

      • 컴파일 타임의 의존성은 Interface에 해당하는 것이다
      • 런타임 의존성은 구체 Class에 해당하는 것이다
        • 그래서 Bean 객체들을 final로 받아야한다
    • OCP를 구현할 때, 다형성이 핵심 역할을 한다. 다형성을 사용하면 기존 클래스나 모듈을 변경하지 않고도 새로운 동작을 추가할 수 있다.

  • L: Liscov Subsitution

    “상속 시” 부모 클래스에 대한 가정을 충족 시킨다

    • “리스코프 치환”
    • “계급제” 에 관련있다
      • 부모가 가진 / 제공하는 모든 Method, Field는 자식도 전부 제공해야한다
      • 부모 Class가 가진 모든 것을 자식 Class에서도 모두 따라야한다
  • I: Interface Segregation

    인터페이스 분리 - 인터페이스 내에 메소드의 최소 갯수에 관련되어있다

    • “인터페이스 내에 메소드는 최소한의 갯수로 만들도록 지향하자
      • 하나의 일반적인 Interface보다 여러 개의 구체적인 Interface가 낫다
        • ex.) 도메인으로 Interface를 쪼개어 놓으면
          • 필요한 구현은 1개인데, 구현체에 나머지 쓰지도 않는 것까지 구현해야한다
          • 너무 많은 역할을 구현하게 되면, 비구현하는 Method가 꽤 많이 생긴다
    • 인터페이스에 비구현 Method를 만들지 말자
    • 그래서 Interface를 최대한 더 쪼게보자
  • D: Dependency Invarsion (의존성 역전)

    인터페이스로 구현체를 연결한다

    상위 수준의 모듈과 하위 수준의 모듈간의 의존성을 역전시켜 코드의 유연성과 유지보수성을 높이는 것을 목표로 한다

    • 고수준 모듈: 다양한 일을 할 수 있는 Module

      • 입력과 출력으로부터 (비지니스와 관련된) 먼 추상화된 모듈이다
        • 추상화: 구체적인 구현에 의존하지 않아야한다
    • 저수준 모듈: 한가지 일만 할 수 있는 Module

      • 입력과 출력에 가까운 (HTTP, Database, Cache, 등과 관련된) 구현 모듈
    • 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안되며, 저수준 모듈이 고수준 모듈에 의존해야한다

    • “의존 역전 원칙”이란 결국 비지니스와 관련된 부분이 세부 사항에는 의존하지 않아야하는 설계 원칙이다

    • 즉, 구체적인 Class가 아니라 Interface나 Abstract Class와 같은 추상화된 형태에 의존하도록 설계를 해야한다

    • 예시

      • 의존성 역전 윈칙을 적용하지 않은 코드:

        • Notification Class는 EmailService 라는 구체적인 클래스에 의존하고 있다. 만약 EmailService 가 변경되면 Notification 도 수정해야하므로 의존성이 강하게 결합된 상태이다

          // 구체적인 클래스 의존 (의존성 역전 원칙 위반)
          class EmailService {
          sendEmail() {
            console.log("Sending email...");
          }
          }
          
          class Notification {
          constructor() {
            this.emailService = new EmailService();
          }
          
          sendNotification() {
            this.emailService.sendEmail();
          }
          }
      • 의존성 역전 윈칙을 적용한 코드:

        • Notification class가 EmailService 와 같은 구체적인 Class에 의존하지 않고, MessageService 라는 추상화된 Class에 의존하게 설계되어 있다.

          • 이런식으로 한다면 구체적인 구현이 변경되더라도 상위 Class는 수정할 필요가 없다
          // 추상화된 인터페이스
          class MessageService {
          sendMessage() {
            throw new Error("This method should be overridden!");
          }
          }
          
          // 구체적인 클래스들은 추상화된 인터페이스를 구현
          class EmailService extends MessageService {
          sendMessage() {
            console.log("Sending email...");
          }
          }
          
          class SMSService extends MessageService {
          sendMessage() {
            console.log("Sending SMS...");
          }
          }
          
          // 상위 클래스는 추상화된 인터페이스에 의존
          class Notification {
          constructor(messageService) {
            this.messageService = messageService;  // 추상화된 MessageService에 의존
          }
          
          sendNotification() {
            this.messageService.sendMessage();  // 구체적인 구현은 알 필요 없음
          }
          }
          
          // 사용하는 쪽에서 구체적인 구현을 주입
          const emailService = new EmailService();
          const notification = new Notification(emailService);
          notification.sendNotification(); // "Sending email..." 출력
          
반응형

'웹개발 > Java' 카테고리의 다른 글

15 - 8. Java - 익명 클래스 & 익명 구현 객체  (1) 2024.11.14
15 - 6. Java - ENUM  (0) 2024.11.04
15 - 5. Java - Abstract Class vs Interface  (0) 2024.11.04
15 - 4. Java - Generic & 자료 구조  (0) 2024.11.04
15 - 3. Java - Static  (0) 2024.11.04