GRASP

👀·2024년 2월 15일
0

  • General Responsibility Assignment Software Patterns
  • 객체지향 설계에서 객체들 간의 역할과 책임을 명확하게 정의
  • 각 객체에 책임을 부여
  • 객체들 간의 책임을 명확히 할당함으로써 유지보수성이 높은 소프트웨어 시스템을 만들 수 있다

9가지 patten

1. 정보 전문가 (Information Expert)

  • 책임의 명확한 할당:
    작업을 수행하기 위해 필요한 정보나 기능을 최적의 객체에세 할당함으로써 책임의 명확한 분배를 가능하게 한다.

  • 객체의 응집도 증대
    단일 책임 원칙을 따르고, 관련 기능들을 한 곳에 모으는 등의 설계 원칙을 적용하여 객체의 응집도를 높일 수 있다.

  • 유연성과 확장성 향상
    각 객체가 자신의 정보나 기능을 가장 잘 알고 있으므로, 변경이 발생할 경우 해당 객체만 수정하면 되어 시스템의 유연성과 확장성을 향상시킨다.

객체의 응집도 - 해당 객체가 한 가지 목적을 위해 얼마나 잘 구성되어 있는지를 나타낸다.

  • 높은 응집도: 해당 객체는 하나의 목적을 수행하기 위해 구성되어 있다.
  • 낮은 응집도: 객체 내부의 기능들이 여러 목적을 수행하거나 서로 연관성이 적다.
  • 작업의 성격 분석
    어떤 작업을 수행하기 위해 필요한 정보나 기능이 무엇인지 분석 필요.

  • 정보와 기능의 소유자 확인
    작업을 수행하는 데 필요한 정보나 기능을 최적으로 갖고 있는 객체를 찾아낸다.

  • 책임 할당
    해당 작업을 수행할 객체에게 적절한 책임을 할당한다.


2. 생성자 (Creator)

객체를 생성하는 책임을 어느 객체에게 할당해야 하는지를 결정하는 데 사용된다.
이 패턴은 객체 간의 관계를 설정하고 새로운 객체 인스턴스를 생성하는 데
필요한 책임을 어떤 객체에게 부여해야 하는지를 판단하는데 도움을 준다

  • 객체 간의 관계 설정
    한 객체가 다른 객체의 인스턴스를 생성하고 그 관계를 설정해야 할 때 해당 객체에게 생성자 책임을 할당한다.
    ex) 객체 A가 객체 B를 생성해야 할 때, A가 B의 인스턴스를 생성하는 것이 책임을 A에게 할당할 수 있기 때문에 좋다.

  • 복잡한 초기 로직

    객체는 특정한 상태를 보유해야 한다.
    사용자 객체 생성 => 사용자의 이름, 이메일 등의 정보 설정 필요

    다른 객체나 외부 서비스와 상호작용이 필요한 경우

         데이터 관련 객체가 생성 => 데이터베이스와의 연결 설정 필요.
         객체 생성 => 설정 파일을 읽어와 초기 설정을 적용하는 경우 초기 설정 필요

    올바른 동작을 보장해야 한다.

        초기화 과정에서 필요한 설정이나 검증 작업을 수행하여 객체의 일관성과 안정성을 확보 필요

    유연성과 확장성 제공

    	객체 생성 => 권한 부여 => 권한으로 인한 객체의 설정 변경 가능 

    에러 처리

        데이터의 유효성 체크 및 예외 상황에 대한 처리 수행 필요

의존성 주입

  • 객체 지향 프로그래밍에서 하나의 객체가 다른 객체에 대한 의존성을 외부로부터 주입받는 디자인 패턴.
  • 객체 간의 결합도를 낮추고 코드의 유연성과 재사용성을 향상시키는 데 사용.

1. 생성자 주입(Constructor Insjection)

// 의존성을 객체의 생성자를 통해 주입하는 방법 
// 객체가 생성될때 외부에서 필요한 의존성을 전달받아 초기화 된다.
class Client {
  private service: any;

  constructor(service: any) {
      this.service = service;
  }
}

2. 메서드 주입 (Method Injection)

class Client {
  private service: any;

  setService(service: any): void {
      this.service = service;
  }
}

3. 속성 주입 (Property Injection)

class Client {
  private service: any;

  constructor() {
      this.service = null;
  }

  setService(service: any): void {
      this.service = service;
  }
}
  • 단일 책임 원칙 (SRP)

    	Single Responsibility Principle
    	컴포넌트 (클래스, 모듈, 함수 등)가 하나의 책임만을 가져야 한다.

    1. 유지보수성 향상
    하나의 모듈이 한 가지 책임만을 담당하면, 해당 모듈의 코드 파악 및 수정이 쉽다.
    2. 재사용성 향상
    책임이 분리되어 있으면 모듈을 쉽게 다른 프로젝트나 컨텍스트에 재사용하라 수 있다.
    3. 테스트 용이성
    책임이 분리되어 있기 때문에 테스트 작성이 쉽다.
    4. 기능 분리
    각각의 기능을 독립적으로 처리 할 수 있는 모듈로 분리한다.
    5. 추상화
    모듈의 책임을 명확히 정의하고 추상화하여 일관성있는 인터페이스를 제공한다.
    6. 의존성 관리
    다른 모듈에 대한 의존성을 최소화하고 모듈 간의 결합도를 낮춘다.


3. Controller - 컨트롤러

소프트웨어 아키텍처에서 사용되는 디자인 패턴 중 하나
모델-뷰-컨트롤러(Model-View-Controller, MVC) 아키텍처의 구성 요소 중 하나
이 패턴은 사용자 인터페이스와 비즈니스 로직 사이의 상호 작용을 관리, 조정하는데 사용.

  • 사용자 입력 처리
    사용자가 입력한 정보 (키보드, 터치, 마우스 등)를 받아 처리
    사용자와의 상호작용 제어

  • 비즈니스 로직 호출
    사용자의 입력에 따라 비즈니스 로직을 호출 및 실행
    데이터 처리, 계산, 데이터베이스 엑세스 등

  • 모델 업데이트
    비즈니스 로직의 실행 결과를 모델에 반영하여 데이터를 업데이트한다.
    모델의 상태 변경, 화면에 표시되는 데이터 변경 등

  • 뷰 선택 및 렌더링
    최종적으로 화면에 표시할 뷰를 선택하고 렌더링한다.
    뷰를 결정하고 필요한 데이터를 전달하여 화면에 적절히 표시될 수 있도록 한다.

주의_
컨트롤러가 많은 책임을 가지게 되면 1. 코드의 복잡성을 증가 2. 유지보수 어려움
=> 단일 책임 원칙을 준수하여 적절한 수준의 컨트롤러를 구현하는 것이 중요


4. Indirection

  • 상호작용 간소화
    두 객체의 상호작용 과정이 복잡하고 유지보수가 어려운 경우, 중간 단계를 도입하여 상호작용을 간소화해준다.
  • 유연성 확보
    직접적인 의존성을 제거하고 중간 단계를 통해 간접적으로 상호작용함으로써 시스템의 유연성을 향상시킨다.
    변경사항에 대한 영향을 최소화하고 코드 재사용성을 향상시킨다.
  • 커플링 감소
    객체 간의 중간 단계를 도입하여 객체 간의 직접적인 의존성을 제거한다.
// EventService - 이벤트 처리를 담당하는 클래스
class EventService {
    constructor(private orderService: OrderService) {
        // 이벤트 처리를 위한 초기화 작업
    }

    // 주문 생성 이벤트를 처리하는 메서드
    handleOrderCreation(orderId: number): void {
        console.log(`Order created: ${orderId}`);
        // 주문 확인을 위해 OrderService에 주문 ID를 전달
        this.orderService.confirmOrder(orderId);
    }

    // 주문 확인 이벤트를 처리하는 메서드
    handleOrderConfirmation(orderId: number): void {
        console.log(`Order confirmed: ${orderId}`);
    }
}

// OrderService - 주문을 확인하고 처리하는 서비스
class OrderService {
    constructor(private eventService: EventService) {
        // 주문 처리를 위한 초기화 작업
    }

    // 주문을 확인하고 처리하는 메서드
    confirmOrder(orderId: number): void {
        console.log(`Order confirmed: ${orderId}`);
        // 주문 처리 완료를 알리는 이벤트를 발생시킴
        this.eventService.handleOrderConfirmation(orderId);
    }
}

// 클라이언트 코드에서 주문을 생성하는 예시
let orderId: number = 123;
let eventService: EventService = new EventService(new OrderService(eventService));

eventService.handleOrderCreation(orderId);

위의 코드에서는 EventService와 NotificationService라는 두 개의 서비스가 상호 작용하여 이벤트를 처리하고 사용자에게 알림을 보내는 과정을 담당한다.
EventService를 통해 이벤트를 처리하고, 필요한 경우에 NotificationService를 호출하여 사용자에게 알림을 보낸다.
이렇게 중간 단계를 도입하여 객체 간의 직접적인 상호 작용을 줄임으로써 유연성을 높일 수 있다.


5. Polymorphism

  • 동적 다형성
// 동물 클래스
class Animal {
    sound(): void {
        console.log('Animal makes a sound');
    }
}

// 개 클래스
class Dog extends Animal {
    sound(): void {
        console.log('Dog barks');
    }
}

// 고양이 클래스
class Cat extends Animal {
    sound(): void {
        console.log('Cat meows');
    }
}

// 동물 객체 생성
const dog: Animal = new Dog();
const cat: Animal = new Cat();

// 다형성을 활용한 동물 객체의 sound 메서드 호출
dog.sound(); // => Dog barks
cat.sound(); // => Cat meows

Animal 클래스를 상속 받은 Dog, Cat 클래스를 정의하고 각 클래스의
sound함수를 오버라이딩 하여 각 객체의 동작을 재정의한다.

상속과 method 오버라이딩을 통해 다형성을 구현할 수 있다.


6. Protected Variations

Protected Variations 패턴이 요구되는 경우

  • 외부 자원 또는 서비스에 대한 의존성이 높을 경우
  • 외부 인터페이스나 라리브러리에 대한 의존성이 높을 경우

Protected Variations 패턴
외부 자원이나 서비스의 변경이 시스템 전체에 미치는 영향 최소화

  • 추상화
    외부 자원이나 서비스를 추상화하여 내부 시스템에서만 접근 가능하도록 인터페이스를 제공한다.
    => 시스템의 다른 부분이 외부 자원의 세부사항에 대해 접근하지 못하도록 한다.
  • 인터페이스
    외부자원이나 서비스와의 상호작용을 담당하는 인터페이스를 도입하여 내부 시스템에서만 접근 가능하도록 한다.
    => 외부 자원의 변경이 시스템 전체에 영향을 미치지 않도록 한다.
  • 어댑터
    외부 자원이나 서비스를 내부 시스템의 인터페이스에 맞게 변환하는 어댑터를 도입하여
    외부자원과의 상호작용을 추상화한다.
    => 외부자원의 변동성을 시스템 내부에 캡슐화할 수 있다.

7. Pure Fabrication

시스템에서 특정 기능을 처리하는 가상의 객체를 도입하여 시스템을 단순화한다.
*Indirection패턴 :
객체 간의 결합도를 낮추기 위해 중간 단계를 도입하는 것
*Pure Fabrication패턴 :
실제로는 존재하지 않지만 시스템을 단순화하고 복잡성을 줄이기 위해 도입되는 가상의 객체

Pure Fabrication패턴

  • 시스템의 복잡한 기능을 분리하고 추상화
    추상화된 객체를 도입하여 기능을 분리하고 단순화 한다.
  • 시스템의 의존성 관리
    객체 간의 결합도를 낮출 수 있다.
  • 시스템 확장 관리
    향후 시스템의 기능을 확장하거나 변경하기 쉽다.

블로그 시스템을 구현한다고 가정한다면
사용자가 블로그 포스트를 작성하고 편집하는 경우,
이를 처리하는 객체가 필요하다.
이때 실제로는 데이터베이스와 통신하여 작성된 포스트를 저장하고 편집하는 역할을 하는 가상의 객체를 도입할 수 있다.

// Pure Fabrication - BlogService
class BlogService {
  constructor(private database: any) {
    // 실제로는 데이터베이스와 통신하는 코드가 있을 자리
  }

  createPost(title: string, content: string): void {
    // 포스트를 데이터베이스에 저장하는 메서드
    console.log(`Created post: ${title}`);
  }

  editPost(id: number, newTitle: string, newContent: string): void {
    // 포스트를 수정하는 메서드
    console.log(`Edited post ${id}: ${newTitle}`);
  }
}

// 사용자가 블로그 포스트를 작성하고 편집하는 예시
let blogService = new BlogService(/* pass your database instance here */);

// 블로그 포스트 작성
blogService.createPost(
  "Introduction to Pure Fabrication", 
  "This is a blog post about Pure Fabrication."
);

// 블로그 포스트 편집
blogService.editPost(
  1, 
  "Introduction to Pure Fabrication (Revised)", 
  "This is a revised version of the blog post about Pure Fabrication."
);

위의 예시에서는 BlogService라는 가상의 객체를 도입하여 실제로는 데이터베이스와 통신하지 않고 콘솔에 메시지를 출력하는 형태로 작성되어 있다.
이렇게 가상의 객체를 도입하여 시스템의 설계를 단순화하고 유연성을 높일 수 있다


8. Low Coupling

객체 간의 결합도(coupling)를 낮추는것을 목표

  • 의존성 주입(Dependency Injection)
    객체가 직접 필요한 의존 객체를 생성하는 것이 아니라 외부에서 주입받아 사용하는 방식.
    이를 통해 객체는 외부 의존성에 대해 알지 못하며, 결합도를 낮출 수 있다.

  • 중간자 패턴(Mediator Pattern)
    객체 간의 직접적인 상호 작용을 최소화하고 중간자를 통해 통신하는 방식.
    이를 통해 객체 간의 결합도를 낮출 수 있다.

  • 인터페이스 분리 원칙(Interface Segregation Principle, ISP)
    클라이언트는 자신이 직접 사용하지 않는 메서드에 의존하지 않아야 한다.
    이를 통해 클라이언트와 서비스 사이의 결합도를 낮출 수 있습니다.

  • 이벤트 기반 아키텍처(Event-Driven Architecture)
    객체 간의 통신을 이벤트에 기반하여 구현함으로써 결합도를 낮출 수 있다.
    이벤트 기반 아키텍처는 객체 간의 직접적인 호출을 피하고 이벤트를 통해 통신한다.


9. High Cohesion

객체나 모듈이 하나의 목적을 수행하기 위해 밀접하게 관련된 기능들을 함께 포함하고 있는 것을 강조
각 객체나 모듈이 책임을 명확히 수행하고 서로 관련된 기능들을 모아서 처리해야 한다는 원칙을 내포

높은 응집도를 갖는 클래스나 모듈은 비슷한 기능을 수행하는 메서드나 함수들을 함께 묶어 놓는다.
서로 관련이 깊고 연관성이 있는 기능들을 하나의 단위로 묶어주며, 객체의 목적을 명확하게 한다.
이는 객체 내부의 메서드나 기능들이 서로 긴밀하게 연결되어 있어야 한다는 것을 의미

높은 응집도를 갖는 클래스나 모듈
1. 단일 책임 원칙
2. 서로 관련된 기능 묶음
3. 불필요한 기능이나 관련없는 기능 제거

0개의 댓글