728x90
반응형

이슈 발생

Flutter 앱 개발 중 iOS 푸시알림 설정을 구현하면서 생긴 이슈이다. 잘 동작하던 앱이었는데 flutter_local_notifications나 flutter_foreground_task 같은 라이브러리를 추가하거나 iOS Deployment Target을 15.0으로 올린 이후 갑자기 FCM 메시지가 수신되지 않는 이슈가 발생했다.

 

문제 상황

- Podfile 업데이트 및 iOS 타겟 버전을 다른 라이브러리에 맞게 15.0으로 사향 조정

- APNS 토큰은 정상적으로 발급이 되나 onMessage, onBackgroundMessage에서 로그가 전혀 찍히지 않고 Firebase Console에서 테스트 메시지를 보내도 반응이 없었음


원인 분석

1. iOS 앱의 생명주기를 관리하는 AppDelegate는 하나뿐인데 firebase_messaging, flutter_local_notifications, flutter_foreground_task 등 여러 플러그인이 서로 "내가 알림 권한(Delegate)을 처리할게!" 라고 경쟁

기본적으로 Firebase는 Method Swizzling(자동으로 메서드를 가로채는 기술)을 통해 APNs 토큰을 가져가는데, iOS 버전이 올라가고 다른 플러그인들이 업데이트되면서 Firebase의 자동 연결 고리가 끊어진 것이라고 원인을 분석했다.

2. 또한, APNs 환경 불일치를 원인으로 생각했다. 

  • Debug 빌드: Apple의 Sandbox (개발용) APNs 서버를 사용
  • Release 빌드: Apple의 Production (배포용) APNs 서버를 사용

Firebase SDK가 업데이트되면서 자동으로 환경을 감지하지 못하고, Debug 빌드임에도 Production 서버로 메시지를 보내는 경우가 발생했다. 애플 서버 입장에서는 "샌드박스용 토큰인데 왜 프로덕션으로 보냄?" 하고 메시지를 폐기해버린 것이 문제의 원인이라고 생각했다.

 

해결 방법 

Firebase의 Method Swozzling 기능을 수동으로 명확하게 처리해줬다. 

 

STEP 1.

일단 Xcode 설정을 재확인해보자. Target 버전을 변경하면 그에 따라 Xcode 설정이 풀리는 경우가 있다. 

Runner -> Signing & Capabilities 탭에서 다음 두 가지가 켜져 있는지 아래 이미지처럼 확인해본다.

1) Push Notifications: 없으면 + Capability로 추가.

2) Background Modes: Remote notifications 체크 확인.

 

STEP 2.

ios/Runner/Info.plist 파일을 열어 Firebase가 자동으로 AppDelegate를 건드리지 못하게 설정했다. 

<key>FirebaseAppDelegateProxyEnabled</key>
<false/>

 

STEP 3.

AppDelegate.swift에서 APNs 토큰을 직접 Firebase에 꽂아준다. 이 때, Debug 모드일 때는 .sandbox라고 명시해주는 것이 핵심이다.

 

import Flutter
import UIKit
import flutter_local_notifications
import flutter_foreground_task
// 👇 Firebase 모듈 추가
import Firebase
import FirebaseMessaging

@main
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    
    // 플러그인 등록
    GeneratedPluginRegistrant.register(with: self)
    
    // Foreground Task 등록 (사용하는 경우)
    SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback { registry in
        GeneratedPluginRegistrant.register(with: registry)
    }

    // 알림 Delegate 설정
    if #available(iOS 10.0, *) {
      UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
    }

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  // APNs 토큰 수동 매핑 로직 추가
  override func application(_ application: UIApplication,
                  didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
      
      // 1. 빌드 모드에 따라 토큰 타입(Sandbox/Prod) 강제 지정
      #if DEBUG
          // 개발(Debug) 모드 -> Sandbox (aps-environment: development)
          Messaging.messaging().setAPNSToken(deviceToken, type: .sandbox)
      #else
          // 배포(Release) 모드 -> Prod
          Messaging.messaging().setAPNSToken(deviceToken, type: .prod)
      #endif

      // 2. 다른 플러그인들도 토큰을 쓸 수 있게 부모 메서드 호출 
      super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
  }
}

 

 

마무리

이제 기존앱을 삭제하고 flutter clean, flutter run 후 확인해보고ㅓ 

728x90
반응형

'Flutter' 카테고리의 다른 글

[Flutter] Widget, Element, RenderObject 정리  (0) 2025.11.17
728x90
반응형

 

왜 환경 분리를 할까?

- 개발 중인데 서버 데이터를 날린다던가 테스트 데이터가 쌓이네... 

- API 키가 테스트 키, 배포키를 매번 바꿔야 하네...

앱을 개발용, 배포용으로 설치하면 되지 않을까? 라는 접근

 

Product Flavors 란?

말 그대로 '하나의 프로젝트로 여러 가지 맛(버전)의 앱을 만드는 기능' 이다. 소스 코드는 하나지만, 설정을 통해 '개발용 맛(Dev)'과 '배포용 맛(Prod)'을 다르게 찍어내는 것이다. 단순히 변수 몇 개 바꾸는 걸 넘어서, 아예 패키지명을 다르게 해서 폰에 앱 2개를 동시에 깔거나, 실행 환경(서버 URL, 아이콘 등)을 완전히 분리할 수 있게 해주는 안드로이드의 강력한 빌드 기능이다.

 

프로젝트에 적용해보기

 

1. app 수준의 Gradle 설정하기 (build.gradle.kts)

가장 먼저 app 수준의 build.gradle.kts 파일에서 맛(Flavor)을 정의해야 합니다. 여기서는 dev (개발용)prod (배포용) 두 가지로 나눈다.

 

2. 앱 이름 동적 적용 (AndroidManifest.xml)

Gradle에서 만든 app_name_flavor라는 변수를 매니페스트에 연결해준다. 이렇게 하면 빌드할 때 환경에 따라 앱 이름이 자동으로 바뀐다.

  • 기존: android:label="@string/app_name" (고정된 이름)
  • 변경: android:label="@string/app_name_flavor" (동적 이름)

 

3. 환경 전환 방법 (Build Variants)

설정을 마쳤다면, 개발 중에 어떻게 환경을 왔다 갔다 할까? 안드로이드 스튜디오의 Build Variants 기능을 사용하면 된다.

안드로이드 스튜디오 좌측 하단(혹은 좌측 메뉴)에 Build Variants라는 탭이 있다. 상황에 맞는 것을 선택하면 된다. (안 보이면 메뉴 View > Tool Windows > Build Variants)


4. 폴더 구조로 리소스 분리하기

코드뿐만 아니라 파일(이미지, 설정 파일)도 환경별로 다르게 가져갈 수 있다. 특히 Firebase(google-services.json)를 분리할 때 필수적이다. 

안드로이드 프로젝트는 기본적으로 src/main을 바라보지만, Flavor 이름과 똑같은 폴더가 있으면 그 안의 내용을 우선순위(Override)로 둔다.

 

파이어베이스를 추후 적용한다면 각 환경에 맞는 google-services.json 를 넣어두면 된다!!

 

이제 applicationIdSuffix 덕분에 내 폰에 개발용 앱실제 앱 두 개가 동시에 설치된다. 더 이상 테스트할 때 기존 앱을 지우거나 로그인을 다시 할 필요가 없어진거지... 처음 설정이 조금 귀찮을 수 있지만 어차피 해야한다. 나중에 하면 더 고통이다...

 

 

728x90
반응형
728x90
반응형

 

🔥 Flutter 핵심 개념 정리: 위젯·엘리먼트·렌더오브젝트 완전 이해하기

 

Flutter를 공부하다 보면 꼭 이해해야 하는 3가지 개념이 있다.

 

👉 Widget

👉 Element

👉 RenderObject

 

처음에는 모두 비슷해 보이지만, Flutter의 UI 렌더링 구조를 정확히 이해하려면 이 세 가지가 어떤 역할을 하는지 명확하게 구분할 필요가 있다.

 

이 글에서는

위젯 → 엘리먼트 → 렌더오브젝트의 흐름

가장 쉬운 비유와 실제 예시, 그리고 트리 구조로 이해할 수 있도록 설명한다.

 


 

1. Widget — UI의 “설계도”

 

Widget은 UI가 어떻게 보여야 하는지 정의하는 ‘설계도’이다.

 

  • 불변(immutable)
  • 화면의 실제 위치나 크기 정보 없음
  • 단순히 “이런 모습으로 만들어줘!” 라는 데이터 덩어리

 

예를 들어, 아래 코드는 모두 “위젯”이다.

Center()
Padding(padding: ...)
Column()
Text("Hello")
Icon(Icons.star)

하지만 이 위젯들은 실제로 화면에 그려지지 않는다.

설계도이기 때문이다.

 


 

2. Element — 위젯의 “실제 위치 정보”

 

Element는 위젯의 인스턴스이자,

UI 트리에서 ‘현재 위젯이 어디에 존재하는지’를 나타내는 객체이다.

 

즉,

 

  • 위젯이 어디에 붙어 있는지 (부모/자식 관계)
  • 위젯이 가진 상태(State)
  • 위젯의 생명주기

 

등은 모두 Element가 관리한다.

 

 

BuildContext의 정체 = Element

 

위젯의 build() 메서드에서 받는 BuildContext context

사실 해당 위젯의 Element 자신을 가리키는 참조이다.

 

예:

@override
Widget build(BuildContext context) {
  // context = 이 위젯의 Element
}

그래서 context를 통해

 

  • Theme.of(context)
  • Navigator.of(context)
  • MediaQuery.of(context)

 

처럼 조상 위젯이나 환경 정보에 접근할 수 있는 것이다.

 

 

모든 위젯은 각각 고유한 Element를 가진다

 

PostEmptyView, Center, Padding, Column, Text, Icon…

모두 각각의 Element를 갖는다.

 


 

3. RenderObject — 실제로 그리는 “엔진 객체”

 

RenderObject는 Flutter 엔진과 직접 연결되어

실제 화면에 그림을 그리는 역할을 한다.

 

RenderObject의 역할은 다음과 같다.

 

  • 레이아웃 계산 (size, position)
  • 페인팅 (draw)
  • Hit Test (터치 이벤트 전달)
  • 스크롤, 정렬, 배치 등 물리적 동작

 

예:

 

  • Text → RenderParagraph
  • Column → RenderFlex
  • SizedBox → RenderConstrainedBox

 

RenderObject는 UI를 실제로 구성하는 “진짜 물체(knob, pixel)”이다.

 


 

4. 세 가지의 관계를 한 번에 이해하기

 

정리하면 이렇게 흐른다:

Widget (설계도)
   ↓ createElement()
Element (위치 + 상태 + 트리 정보)
   ↓ createRenderObject()
RenderObject (레이아웃 + 그리기)

 

비유하면?

개념비유역할

Widget 집의 설계도 UI 구조 정의
Element 실제 방의 위치 위젯이 트리에서 어디 있는지 저장
RenderObject 집의 벽/창문 실제 화면에 그려짐

 


 

5. 트리로 보면 더 명확해진다

 

아래 예시를 보자.

PostEmptyView
 └─ Center
      └─ Padding
           └─ Column
                ├─ Icon
                ├─ SizedBox
                ├─ Text
                ├─ SizedBox
                └─ Text

이건 위젯 트리다.

Flutter는 이 위젯 트리를 기반으로 동일한 구조의 Element 트리를 생성하고,

그 Element에서 필요한 것들만 RenderObject 트리를 만든다.

 


 

6. Rebuild 과정에서는 무엇이 재사용될까?

 

Flutter가 성능이 빠른 이유는

모든 것을 새로 만드는 것이 아니라 필요한 것만 업데이트하기 때문이다.

구분상태설명

Widget 매번 새로 생성됨 가벼운 데이터 객체라 부담 없음
Element 재사용됨 (트리 유지) 트리 구조와 State 유지
RenderObject 대부분 재사용됨 속성 변경만 업데이트

이 구조 덕분에 Flutter는 빠르게 rebuild가 가능하다.

 


 

7. BuildContext는 “최상위”가 아니다

 

많은 초보자들이 오해하는 부분이 있다.

 

BuildContext는 “해당 위젯의 위치(Element)”를 의미한다.

 

 

  • PostEmptyView build() 안에서는 PostEmptyView의 context
  • Column 내부 Builder에서는 Column 내부의 context
  • Text 내부에서는 TextElement의 context

 

따라서 위젯마다 각자의 BuildContext(Element)가 존재한다.

“최상위 context 하나만 존재하는 구조”가 아니다.

 


 

8. 정리: 한 장으로 끝내는 개념 총정리

📘 Widget
- UI 정의 (설계도)
- 불변
- 위치 정보 없음

🧩 Element
- 위젯의 실제 인스턴스
- 트리의 구조 담당 (부모/자식)
- 상태(State) 보관
- BuildContext의 정체

🎥 RenderObject
- 화면에 실제로 그리는 객체
- 레이아웃 / 그리기 엔진
- 필요한 위젯에서만 생성됨

 


 

마무리

 

Flutter의 핵심 구조는

Widget → Element → RenderObject

이 세 가지가 어떻게 협력하느냐를 이해하는 것이다.

 

이 원리를 제대로 이해하면:

 

✔ Flutter 렌더링 구조가 명확해지고

✔ BuildContext의 역할이 분명해지고

✔ Rebuild / Layout / Render 단계가 잡히며

✔ 성능 최적화까지 자연스럽게 이해된다.

728x90
반응형
728x90
반응형

🔍 BFS(Breadth-First Search) 완전 정복

 

코딩테스트의 기본 탐색 알고리즘

 

그래프 탐색 문제에서 DFS와 함께 항상 등장하는 알고리즘이 BFS(너비 우선 탐색).

특히 BFS는 가장 가까운 노드부터 탐색하는 특성 덕분에 최단 거리 문제에서 자주 사용되는 방식.

 

 


 

✅ BFS란?

 

BFS(Breadth-First Search)는 시작 노드로부터 가까운 순서대로 탐색하는 그래프 탐색 기법

같은 깊이의 노드들을 먼저 방문한 뒤, 점점 더 멀리 있는 노드를 탐색하는 방식

 

DFS가 깊게 내려가는 구조라면, BFS는 넓게 펼쳐지며 탐색하는 구조라고 이해하면 쉬운 형태

 


 

✔ 핵심 특징

 

  • 큐(Queue) 자료구조 사용
  • 방문한 노드를 체크하기 위한 방문 배열 필요
  • 모든 정점을 한 번씩 방문하며 모든 간선을 확인
  • 시간복잡도: O(V + E)
  • 그래프는 인접 리스트로 표현하는 것이 효율적인 방식

 


 

🧩 동작 방식 시각화 (간단한 이미지 느낌으로 설명)

 

다음 구조의 그래프가 있다고 가정:

1
├─ 2
│  ├─ 4
│  └─ 5
└─ 3

BFS 탐색 순서는 다음과 같은 형태:

 

1 → 2 → 3 → 4 → 5

 

즉, 1과 가까운 2, 3을 먼저 방문,

그 다음 거리인 4, 5를 방문하는 구조

 

DFS는 깊이로 내려가는 방식

BFS는 레벨(층단)별로 방문하는 방식

 


 

⚙ BFS 구현 과정

 

 

1. 탐색 준비

 

  • 그래프를 인접 리스트로 초기화
  • 방문 배열 생성
  • 시작 노드를 큐에 삽입

 

 

2. 큐에서 노드를 하나 꺼내기

 

  • 꺼낸 노드를 방문하고
  • 해당 노드의 인접 노드 중 방문하지 않은 노드만 큐에 삽입

 

 

3. 큐가 empty 상태가 될 때까지 반복

 

  • 큐가 비어있다는 것은 더 이상 탐색할 노드가 없다는 의미
  • 방문 배열을 통해 중복 방문을 방지하는 구조

 


 

📘 예제: 백준 1260 DFS/BFS 문제

 

문제: https://www.acmicpc.net/problem/1260

 

아래 코드는 DFS와 BFS를 모두 구현한 코드

BFS의 기본 흐름을 이해하기 좋은 형태

package c5_search.bfs;

import java.util.*;

public class P1260_4 {
    static boolean visited[];
    static ArrayList<Integer>[] arr;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int e = sc.nextInt();
        int start = sc.nextInt();

        // 1. 인접리스트 초기화
        arr = new ArrayList[n+1];
        for(int i = 1; i <= n; i++){
            arr[i] = new ArrayList<>();
        }

        // 간선 입력
        for(int i = 0; i < e; i++){
            int first = sc.nextInt();
            int last = sc.nextInt();
            arr[first].add(last);
            arr[last].add(first);
        }

        // 방문 배열 초기화
        visited = new boolean[n+1];

        // 번호가 작은 노드부터 방문하기 위한 정렬
        for(int i = 1; i <= n; i++){
            Collections.sort(arr[i]);
        }

        // DFS 출력
        dfs(start);
        System.out.println();

        // BFS 방문 배열 초기화
        visited = new boolean[n+1];

        // BFS 출력
        bfs(start);
    }

    // DFS (재귀 방식)
    public static void dfs(int node){
        System.out.print(node + " ");
        visited[node] = true;

        for(int next : arr[node]){
            if(!visited[next]) dfs(next);
        }
    }

    // BFS (큐 방식)
    public static void bfs(int node){
        Queue<Integer> queue = new LinkedList<>();
        queue.add(node);
        visited[node] = true;

        while(!queue.isEmpty()){
            int now = queue.poll();
            System.out.print(now + " ");

            for(int next : arr[now]){
                if(!visited[next]){
                    queue.add(next);
                    visited[next] = true;
                }
            }
        }
    }
}

 


 

📝 BFS에서 꼭 기억해야 할 포인트

 

 

✔ 1. BFS는 큐 기반 구조

 

핵심은 선입선출(FIFO)

먼저 발견된 노드를 먼저 방문하는 방식

 

 

✔ 2. 방문 체크는 노드를 큐에 넣을 때 즉시 처리

 

중복 방문 방지 및 불필요한 삽입 차단

 

 

✔ 3. 그래프는 인접 리스트 사용

 

인접 리스트는 메모리 효율성과 순회 성능 모두 최적

 

 

✔ 4. 정렬(Optional)

 

문제 조건에 따라 “작은 번호 먼저 방문” 요구 시 정렬 필수

 


 

💡 BFS는 언제 사용할까?

 

  • 그래프에서 최단 거리를 구해야 하는 경우
  • 미로 탐색
  • 레벨 단위 탐색이 필요한 문제
  • 네트워크/연결 관계를 넓게 탐색하는 문제
  • 토마토 문제처럼 “전파” 형태의 시뮬레이션

 

DFS와 함께 코딩테스트 탐색 문제에서 기본적으로 요구되는 알고리즘 형태

 


 

📌 마무리

 

BFS는 가까운 노드부터 차례로 탐색하는 대표적인 그래프 알고리즘

큐 기반의 구조만 이해하면 구현 난이도가 낮고,

최단 거리·단계별 탐색 문제에서 강력한 효과를 발휘하는 방식

 

DFS와 함께 반드시 익혀야 하는 코딩테스트 기본 탐색 알고리즘

 


 

728x90
반응형
728x90
반응형

🔍 DFS(Depth-First Search) 완전 정복

 

코딩테스트의 기본 탐색 알고리즘

 

그래프 탐색 문제를 풀다 보면 DFS(깊이 우선 탐색) 은 반드시 등장하는 핵심 알고리즘.

지금부터 DFS가 무엇인지, 어떤 특징을 가지는지, 어떻게 구현하는지 차근차근 정리해보는 시간을 가져보자.

 


 

✅ DFS란?

 

DFS(Depth-First Search)는 최대한 깊숙한 곳까지 탐색한 뒤 다시 되돌아오는 방식의 탐색 기법

완전탐색(Brute Force)의 한 종류로, 그래프 문제를 해결할 때 가장 많이 사용되는 탐색 방식

 

 

✔ 핵심 특징

 

  • 재귀 함수를 사용해 구현하는 경우가 많음
  • 또는 스택(Stack) 자료구조를 활용
  • 모든 정점을 한 번씩 방문하고 모든 간선을 모두 확인
  • 시간복잡도: O(V + E)
  • 그래프는 보통 인접 리스트로 표현

 


 

🧩 동작 방식 시각화 (간단한 이미지 느낌으로 설명)

 

예를 들어 다음과 같은 그래프가 있다고 가정:

1
├─ 2
│  ├─ 4
│  └─ 5
└─ 3

DFS는 이렇게 방문:

1 → 2 → 4 → (되돌아옴) → 5 → (되돌아옴) → 3

즉, 한 방향으로 끝까지 내려간 뒤 다시 되돌아가서 다른 갈래로 이동하는 방식

 


 

⚙ DFS 구현 과정

 

 

1. 탐색 준비

 

  • 그래프를 인접 리스트로 초기화
  • 방문 여부를 표시할 배열 생성
  • 시작 노드를 스택(또는 재귀)로 삽입

 

 

2. 스택(또는 재귀 호출)에서 노드를 꺼내기

 

꺼낸 노드의 인접 노드를 확인해 아직 방문하지 않았다면 스택에 넣는 과정 반복

 

 

3. 스택이 empty 상태가 될 때까지 반복

 

이미 방문한 노드는 다시 방문하지 않음

방문 배열을 활용하여 중복 방문 방지

 


 

📘 예제: 백준 1260 DFS/BFS 문제

 

문제: https://www.acmicpc.net/problem/1260

 

아래 코드는 DFS와 BFS를 모두 구현한 기본 예제

DFS의 방식이 어떻게 작동하는지 이해하기 좋은 형태

package c5_search.bfs;

import java.util.*;

public class P1260_4 {
    static boolean visited[];
    static ArrayList<Integer>[] arr;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int e = sc.nextInt();
        int start = sc.nextInt();

        // 1. 인접리스트 초기화
        arr = new ArrayList[n+1];
        for(int i = 1; i <= n; i++){
            arr[i] = new ArrayList<>();
        }

        // 간선 정보 입력
        for(int i = 0; i < e; i++){
            int first = sc.nextInt();
            int last = sc.nextInt();
            arr[first].add(last);
            arr[last].add(first);
        }

        // 방문 배열 초기화
        visited = new boolean[n+1];

        // 번호가 작은 순서대로 방문하기 위한 정렬
        for(int i = 1; i <= n; i++){
            Collections.sort(arr[i]);
        }

        // DFS 출력
        dfs(start);
        System.out.println();

        // BFS 방문 배열 초기화
        visited = new boolean[n+1];

        // BFS 출력
        bfs(start);
    }

    // DFS (재귀 방식)
    public static void dfs(int node){
        System.out.print(node + " ");
        visited[node] = true;

        for(int next : arr[node]){
            if(!visited[next]) dfs(next);
        }
    }

    // BFS (큐 방식)
    public static void bfs(int node){
        Queue<Integer> queue = new LinkedList<>();
        queue.add(node);
        visited[node] = true;

        while(!queue.isEmpty()){
            int now = queue.poll();
            System.out.print(now + " ");

            for(int next : arr[now]){
                if(!visited[next]){
                    queue.add(next);
                    visited[next] = true;
                }
            }
        }
    }
}

 


 

📝 DFS에서 꼭 기억해야 할 포인트

 

 

✔ 1. DFS는 재귀 구현이 가장 직관적

 

재귀 호출 구조가 스택을 그대로 흉내낸 형태

 

 

✔ 2. 방문 체크는 가장 먼저 처리

 

visited[node] = true 를 방문하자마자 수행해야 무한 루프 방지

 

 

✔ 3. 그래프는 인접 리스트 사용

 

인접 리스트가 메모리 절약 + 반복문 처리 효율적

 

 

✔ 4. 정렬(Optional)

 

문제에서 “번호가 작은 순서대로 방문” 조건이 있을 경우 정렬 필수

 


 

💡 DFS는 언제 사용할까?

 

  • 그래프의 한 갈래를 끝까지 탐색해야 하는 문제
  • 모든 경우의 수를 깊게 탐색해야 하는 경우
    • 조합/순열 백트래킹
    • 미로 탐색
    • 연결 요소 찾기
    • 트리 구조 탐색
  •  

 

코딩테스트에서 매우 자주 등장하는 유형이므로 반드시 익혀야 하는 기본 알고리즘

 


 

📌 마무리

 

DFS는 알고리즘 문제 풀이의 기초이자, 다양한 문제의 기반이 되는 탐색 알고리즘

재귀, 스택, 인접 리스트 구조에 익숙해진다면 탐색 문제는 훨씬 수월하게 해결 가능

728x90
반응형
728x90
반응형

커맨드 패턴이란?

- 요구사항을 객체로 캡슐화해서 객체를 서로 다른 요구사항에 따라 매개변수화할 수 있다.

 

클래스 다이어그램을 살펴보자.

- Client : 커맨드 객체 생성

- Command : 어떤 Recevier를 실행할 지 연결

- Invoker : 주문을 받아서, 실행하기 위해 Command 인터페이스 연결

- Receiver : 실제 명령을 수행

 

간단한 예제를 보면서 살펴보자.

 

문제점

 집의 불을 켜주고, 차고 문을 열어주고, 스피커 전원도 켜주는 등 다양한 기능을 하는 최첨단 만능 IOT 리모컨이 존재한다. 

public class Light {
    public void on(){
        System.out.println("불을 켭니다.");
    }
    public void off(){
        System.out.println("불을 끕니다.");
    }
}

 

public class GarageDoor {
    public void open(){
        System.out.println("차고 문을 엽니다.");
    }
    public void close(){
        System.out.println("차고 문을 닫습니다.");
    }

}

 

public class Stereo {
    public void up(){
        System.out.println("볼륨을 올립니다.");
    }
    public void down(){
        System.out.println("볼륨을 내립니다.");
    }
}

사용하는 객체는 많은데 on/off, up/down 등 객체마다 사용하는 메소드가 달라 복잡하다...

 

해결책

Command 패턴을 적용해 Command 인터페이스를 추가하고 API를 일치시켜준다. 

public interface Command {
    public void execute();
    public void undo();

}

 

public class GarageDoorCommand implements Command{
    GarageDoor gd;
    public GarageDoorCommand(GarageDoor gd){
        this.gd = gd;
    }
    @Override
    public void execute(){
        gd.open();
    }

    @Override
    public void undo() {
        gd.close();
    }
}

 

public class LightCommand implements Command{
    Light light;
    public LightCommand(Light light){
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }

    @Override
    public void undo() {
        light.off();
    }
}

 

public class StereoCommand implements Command{
    Stereo stereo;

    public StereoCommand(Stereo stereo){
        this.stereo = stereo;
    }
    @Override
    public void execute() {
        stereo.up();
    }

    @Override
    public void undo() {
        stereo.down();
    }
}

 불을 켜는 것도, 스테레오의 볼륨을 조절하는 것도, 차고 문을 여는 것도 execute()라는 메소드로 일치시킬 수 있다. 

물론 클래스가 많아진다는 단점이 있지만 객체 사용에 복잡성을 제거하고 감출 수 있게 된다.

728x90
반응형

+ Recent posts