[클린코드] 11장 시스템

JUN·2024년 8월 19일
0

클린코드

목록 보기
11/14

서론.

소프트웨어 팀도 도시처럼 각자 분업화(추상화와 모듈화)를 통해 잘 돌아가야한다. 이번 단원에서는 높은 추상화 수준, 즉 시스템 수준에서도 깨끗함을 유지하는 방법을 알아본다.

시스템 제작과 시스템 사용을 분리하라

높은 추상화에서도 깨끗한 코드를 유지하려면 시스템 제작과 사용을 명확히 구분해야함.

  • 시스템 제작 : 소프트웨어 시스템의 구조와 설계를 담당하는 과정
  • 시스템 사용 : 시스템 제작으로 만들어진 구조와 컴포넌트로 실제 기능을 구현하는 과정.

시스템 제작과 사용을 명확히 분리하려면 관심사 분리가 필요함.

하지만 많은 사람들이 시작 단계 라는 관심사를 분리하지 않음 → 예시 : 초기화 지연

초기화 지연 기법의 문제

필요할 때까지 객체를 생성하지 않으며 null포인터를 반환하지 않는 장점이 있는 기법.

  1. 다른 클래스의 인수에 객체 생성을 의존.

  2. 테스트 문제.

    단위 테스트에서 해당 메서드를 테스트하기 위한 테스트 전용 객체가 필요함.

    →작게나마 srp 깬다.

따라서, 시스템 제작 시 초기화 지연 기법을 한번 이상 사용했을 시에는 설계 오류를 검증할 것.

1. Main 분리

시스템 생성과 사용을 분리하는 한가지 방법.

  • 생성과 관련된 코드를 모드 main이나 main의 모듈로 옮기고 나머지 시스템은 모든 객체가 생성되었고 모든 의존성이 연결되었다고 가정.
// 메인 클래스
public class Main {
    public static void main(String[] args) {
        // Animal 객체 생성 및 사용 예시
        
        // 객체 생성 단계
        Animal dog = new Dog();
        Animal cat = new Cat();
        
        // 사용 단계
        System.out.print("Dog: ");
        dog.makeSound(); // 멍멍!
        
        System.out.print("Cat: ");
        cat.makeSound(); // 야옹!
    }
}

팩토리 패턴의 사용

객체 생성 시점을 애플리케이션이 결정할 필요도 있음. 그럴때는 메인에서 추상 팩토리 패턴으로 해결한다.

// 메인 클래스
public class Main {
    public static void main(String[] args) {
        // Animal 생성을 담당하는 팩토리 객체 생성
        AnimalFactory animalFactory = new AnimalFactory();
        
        // Dog 생성 및 사용 예시
        Animal dog = animalFactory.createAnimal("dog");
        dog.makeSound(); // 멍멍!
        
        // Cat 생성 및 사용 예시
        Animal cat = animalFactory.createAnimal("cat");
        cat.makeSound(); // 야옹!
    }
}
  • 책에서의 예시

    주요 구성 요소

    1. main

      • 프로그램의 시작 지점으로, 전체 시스템을 초기화하고 실행합니다.
      • LineItemFactory 구현체를 생성하여 OrderProcessing에 전달합니다.
    2. LineItemFactory 구현

      • LineItemFactory 인터페이스를 구현한 클래스입니다.
      • MakeLineItem 메서드를 통해 LineItem 객체를 생성합니다.
    3. LineItemFactory 인터페이스

      • MakeLineItem 메서드를 정의하는 인터페이스입니다.
      • 다양한 LineItem 객체 생성 로직을 캡슐화할 수 있습니다.
    4. OrderProcessing

      • 시스템 사용 단계의 클래스입니다.
      • 비즈니스 로직을 수행하며 LineItemFactory를 통해 LineItem 객체를 생성합니다.
    5. LineItem
      - 실제로 생성되는 객체입니다.
      - 비즈니스 로직에서 사용됩니다.

      흐름 설명

    6. main

      • main 함수는 프로그램 실행 시 LineItemFactory의 구현체를 생성합니다.
      • 예를 들어, LineItemFactoryImpl이라는 구체적인 팩토리 클래스를 생성할 수 있습니다.
      • OrderProcessing 객체를 생성하고, LineItemFactory 구현체를 OrderProcessing에 전달합니다.
    7. OrderProcessing
      - OrderProcessing 클래스는 비즈니스 로직을 수행합니다.
      - 필요한 시점에 LineItemFactoryMakeLineItem 메서드를 호출하여 LineItem 객체를 생성합니다.
      - 생성된 LineItem 객체를 사용하여 비즈니스 로직을 처리합니다.

      코드 예시

      1. LineItemFactory 인터페이스

      public interface LineItemFactory {
          LineItem makeLineItem();
      }
      

      2. LineItem 클래스

      public class LineItem {
          // LineItem 관련 필드 및 메서드
      }
      

      3. LineItemFactory 구현 클래스

      public class LineItemFactoryImpl implements LineItemFactory {
          @Override
          public LineItem makeLineItem() {
              return new LineItem();
          }
      }
      

      4. OrderProcessing 클래스

      public class OrderProcessing {
          private final LineItemFactory lineItemFactory;
      
          public OrderProcessing(LineItemFactory lineItemFactory) {
              this.lineItemFactory = lineItemFactory;
          }
      
          public void processOrder() {
              // 비즈니스 로직 수행
              LineItem item = lineItemFactory.makeLineItem();
              // LineItem 사용
          }
      }
      

      5. Main 클래스

      public class Main {
          public static void main(String[] args) {
              // LineItemFactory 구현체 생성
              LineItemFactory factory = new LineItemFactoryImpl();
      
              // OrderProcessing 객체 생성
              OrderProcessing orderProcessing = new OrderProcessing(factory);
      
              // 주문 처리
              orderProcessing.processOrder();
          }
      }
      

      설명

    • Main 클래스에서는 LineItemFactory의 구현체를 생성하여 OrderProcessing 객체에 전달합니다.

    • OrderProcessing 클래스에서는 LineItemFactory 인터페이스를 통해 LineItem 객체를 생성하고, 이를 비즈니스 로직에 사용합니다.

    • 이렇게 함으로써, 객체 생성과 비즈니스 로직이 명확히 분리되어 코드의 유연성과 유지보수성이 향상됩니다.

      이 예시를 통해 팩토리 패턴을 사용하여 시스템 생성과 사용을 분리하는 방법을 이해할 수 있습니다.

2. 의존성 주입

의존성 주입 (Dependency Injection)

제어 역전(Inversion of Control, IoC) 기법을 의존성 관리에 적용한 것

제어 역전 (IoC) 개념
제어 역전이란?: 객체가 맡은 보조 책임을 새로운 객체에게 전적으로 떠넘겨, 새로운 객체는 단일 책임 원칙(Single Responsibility Principle, SRP)을 지키게 하는 기법

의존성 관리 맥락: 객체가 의존성을 직접 인스턴스로 만드는 대신, 이를 전담 메커니즘(main 루틴이나 특수 컨테이너 등)에 맡긴다.

DI의 본질:

  • 클래스가 의존성을 해결하려 시도하지 않고, 수동적으로 의존성을 주입받음.
  • 설정자(setter) 메서드나 생성자 인수를 통해 의존성을 주입받음

DI 컨테이너 역할

  • 필요한 객체의 인스턴스를 생성하고, 설정 파일이나 특수 생성 모듈에 명시된 방식으로 의존성을 설정한다.

확장

시스템은 처음부터 완벽하게 설계되지 않기에 점진적으로 발전하고 확장 가능한 구조를 가지는 것이 중요

횡단 관심사

횡단 관심사는 여러 모듈에 걸쳐 공통적으로 나타나는 문제로, 이를 효과적으로 관리하는 것이 시스템의 복잡성을 줄이는 데 중요

횡단 관심사의 개념

횡단 관심사는 로깅, 보안, 트랜잭션 관리 등 여러 모듈에서 공통적으로 나타나는 문제이다. 이를 중앙에서 관리함으로써 코드 중복을 줄이고 일관성을 유지할 수 있다.

영속성 프레임워크

영속성 프레임워크는 데이터베이스와의 상호작용을 중앙에서 관리하여, 데이터 접근 로직을 단순화하고 일관성을 유지할 수 있다.

관점 지향 프로그래밍 (AOP)

AOP는 횡단 관심사를 모듈 간에 분리하여, 코드의 중복을 줄이고 유지보수를 쉽게 하는 프로그래밍 기법이다. 이를 통해 시스템의 일관성과 유연성을 높일 수 있다.

자바 프록시

자바 프록시는 동적 프록시를 사용하여 런타임에 객체의 동작을 변경하거나 확장할 수 있는 기능을 제공한다.

자바 프록시의 사용 예제

자바 프록시는 런타임에 객체의 동작을 가로채어 추가 기능을 수행하거나, 객체의 동작을 동적으로 변경할 수 있다.

JDK 동적 프록시와 바이트 코드 처리 라이브러리

JDK 동적 프록시와 바이트 코드 처리 라이브러리는 런타임에 객체의 동작을 동적으로 변경할 수 있는 강력한 도구를 제공한다. 이를 통해 객체의 동작을 유연하게 제어할 수 있다.

profile
순간은 기록하고 반복은 단순화하자 🚀

0개의 댓글