<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>roel_dev.log</title>
        <link>https://velog.io/</link>
        <description>나만의 개발 일기</description>
        <lastBuildDate>Mon, 24 Nov 2025 02:25:17 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>roel_dev.log</title>
            <url>https://velog.velcdn.com/images/roel_dev/profile/d215c2fd-3751-4479-9bd2-8fe89e5c2d6b/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. roel_dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/roel_dev" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Spring AOP 개념 정리: 핵심 기능과 공통 기능의 분리]]></title>
            <link>https://velog.io/@roel_dev/Spring-AOP-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC-%ED%95%B5%EC%8B%AC-%EA%B8%B0%EB%8A%A5%EA%B3%BC-%EA%B3%B5%ED%86%B5-%EA%B8%B0%EB%8A%A5%EC%9D%98-%EB%B6%84%EB%A6%AC</link>
            <guid>https://velog.io/@roel_dev/Spring-AOP-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC-%ED%95%B5%EC%8B%AC-%EA%B8%B0%EB%8A%A5%EA%B3%BC-%EA%B3%B5%ED%86%B5-%EA%B8%B0%EB%8A%A5%EC%9D%98-%EB%B6%84%EB%A6%AC</guid>
            <pubDate>Mon, 24 Nov 2025 02:25:17 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-1-aop란">📌 1. AOP란?</h2>
<p>AOP(Aspect Oriented Programming)는</p>
<p><strong>핵심 관심사(Core Concern)</strong> 와</p>
<p><strong>횡단 관심사(Cross-cutting Concern)</strong> 를 분리하여 프로그램을 구성하는 기법이다.</p>
<blockquote>
<p>💡 핵심 로직에 집중하면서, 반복되는 공통 기능(로깅, 보안, 트랜잭션)을 따로 모듈화할 수 있도록 도와주는 기술</p>
</blockquote>
<p>즉, 객체 지향이 놓치는 “중복되는 부가 기능을 하나로 모아 관리하는 방법”.</p>
<hr>
<h1 id="📌-2-왜-aop가-필요한가">📌 2. 왜 AOP가 필요한가?</h1>
<p>서비스 클래스를 여러 개 만들다 보면</p>
<p>모든 클래스에 이런 코드가 흩어져 나타난다:</p>
<ul>
<li>로그 출력</li>
<li>인증/인가 체크</li>
<li>트랜잭션 시작/종료</li>
<li>실행 시간 측정</li>
<li>예외 로깅</li>
</ul>
<p>이런 코드들이 <strong>여러 곳(C, D, E…)에 똑같이 흩어져 있으면</strong> 문제가 생긴다.</p>
<h3 id="❌-문제점">❌ 문제점</h3>
<ul>
<li>공통 기능이 여러 클래스에 중복 작성됨</li>
<li>공통 기능 수정 시 관련 파일 여러 개를 수정해야 함</li>
<li>핵심 비즈니스 로직이 공통 코드 때문에 지저분해짐</li>
<li>유지보수성 저하</li>
</ul>
<p>그래서 공통 기능을 한 곳에 모아서 처리하는 방식이 필요해졌고,</p>
<p>그것이 바로 AOP다.</p>
<hr>
<h1 id="📌-3-핵심-관심사-vs-횡단-관심사">📌 3. 핵심 관심사 vs 횡단 관심사</h1>
<h2 id="✔-핵심-관심사core-concern">✔ 핵심 관심사(Core Concern)</h2>
<ul>
<li>각 클래스가 수행해야 할 <strong>비즈니스 고유 기능</strong></li>
<li>예:<ul>
<li>주문 서비스 → 주문 생성</li>
<li>DB 강의 → DB 설계</li>
<li>알고리즘 강의 → 알고리즘 풀이</li>
</ul>
</li>
</ul>
<h2 id="✔-횡단-관심사cross-cutting-concern">✔ 횡단 관심사(Cross-cutting Concern)</h2>
<ul>
<li>모든 클래스에 공통적으로 필요한 기능</li>
<li>예:<ul>
<li>로그인 체크</li>
<li>로그 출력</li>
<li>예외 처리</li>
<li>트랜잭션 관리</li>
<li>보안 검사</li>
</ul>
</li>
</ul>
<h3 id="🔥-핵심-포인트">🔥 핵심 포인트</h3>
<p>횡단 관심사는 <strong>어떤 모듈에도 포함되지만, 실제 핵심 로직과 직접 연관되지 않음</strong></p>
<p>→ 그러나 모든 곳에서 함께 실행됨</p>
<p>→ 그래서 “흩어진 관심사”라고 부름</p>
<hr>
<h1 id="📌-4-생활-속-예시로-보는-aop">📌 4. 생활 속 예시로 보는 AOP</h1>
<h2 id="✔-예시-1-대학-강의-시스템">✔ 예시 1: 대학 강의 시스템</h2>
<h3 id="공통된-프로세스">공통된 프로세스</h3>
<pre><code>출석 확인 → 강의 진행 → 중간/기말고사 → 성적 처리</code></pre><p>이 흐름은 모든 과목에 동일하게 적용됨.</p>
<h3 id="핵심-관심사는-과목마다-다름">핵심 관심사는 과목마다 다름</h3>
<ul>
<li>알고리즘 강의 → 알고리즘 문제 풀이</li>
<li>데이터베이스 강의 → 테이블 설계</li>
<li>운영체제 강의 → 프로세스 관리</li>
</ul>
<h3 id="횡단-관심사는-모든-강의에-동일">횡단 관심사는 모든 강의에 동일</h3>
<ul>
<li>출석 체크</li>
<li>시험 관리</li>
<li>성적 계산</li>
</ul>
<p>바로 이런 공통 로직을 AOP가 묶어서 처리해준다.</p>
<hr>
<h2 id="✔-예시-2-쇼핑-과정">✔ 예시 2: 쇼핑 과정</h2>
<p>가게가 달라도 다음 과정은 같다:</p>
<pre><code>물건 고르기 → 장바구니 담기 → 결제하기 → 영수증 받기</code></pre><p>여기서</p>
<ul>
<li>‘물건 고르기’는 가게마다 다르므로 <strong>핵심 관심사</strong></li>
<li>장바구니 담기, 결제, 영수증 받기 같은 과정은 <strong>모든 가게에서 공통 → 횡단 관심사</strong></li>
</ul>
<p>AOP는 <strong>공통 단계(횡단 관심사)를 하나로 모아 관리</strong>한다.</p>
<hr>
<h1 id="📌-5-aop가-주는-장점">📌 5. AOP가 주는 장점</h1>
<h3 id="✔-유지보수성-향상">✔ 유지보수성 향상</h3>
<ul>
<li>코드가 더 깔끔하고 핵심 비즈니스 로직만 남음</li>
<li>수정해야 할 공통 기능의 위치가 명확해짐</li>
</ul>
<h3 id="✔-코드-재사용성-증가">✔ 코드 재사용성 증가</h3>
<ul>
<li>로깅, 보안, 트랜잭션 같은 기능을 한 번만 구현하면 여러 클래스에서 재사용 가능</li>
</ul>
<h3 id="✔-테스트-용이성">✔ 테스트 용이성</h3>
<ul>
<li>핵심 로직을 공통 로직에서 분리하여 독립적으로 테스트 가능</li>
</ul>
<h3 id="✔-디버깅-쉬움">✔ 디버깅 쉬움</h3>
<ul>
<li>공통 기능이 중앙 집중되어 있어 추적이 빠름</li>
</ul>
<h3 id="✔-시스템-확장성-증가">✔ 시스템 확장성 증가</h3>
<ul>
<li>새로운 공통 기능을 추가하더라도 기존 핵심 로직에 영향이 거의 없음</li>
</ul>
<h3 id="✔-책임-분리-명확">✔ 책임 분리 명확</h3>
<ul>
<li>각 모듈이 담당해야 할 역할이 분명해짐</li>
</ul>
<hr>
<h1 id="📌-6-aop-주요-용어">📌 6. AOP 주요 용어</h1>
<table>
<thead>
<tr>
<th>용어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Aspect</strong></td>
<td>부가 기능(횡단 관심사)를 모듈화한 것, 로깅/보안/트랜잭션 등</td>
</tr>
<tr>
<td><strong>Advice</strong></td>
<td>언제 실행할지 정의한 코드(메서드) Before / After / Around 등</td>
</tr>
<tr>
<td><strong>Join Point</strong></td>
<td>Advice가 삽입될 수 있는 지점 (메서드 실행 등)</td>
</tr>
<tr>
<td><strong>Pointcut</strong></td>
<td>Advice가 실제로 적용될 위치를 지정하는 필터 조건</td>
</tr>
</tbody></table>
<hr>
<h1 id="📌-7-aop-핵심-매커니즘-한-번에-정리">📌 7. AOP 핵심 매커니즘 한 번에 정리</h1>
<h3 id="✔-aspect--부가-기능을-모듈화한-클래스">✔ Aspect = 부가 기능을 모듈화한 클래스</h3>
<p>예: LoggingAspect, TransactionAspect</p>
<h3 id="✔-pointcut--어디에-적용할지-정의">✔ Pointcut = 어디에 적용할지 정의</h3>
<p>예:</p>
<pre><code class="language-java">execution(* com.example.service.*.*(..))</code></pre>
<h3 id="✔-advice--언제-실행할지-정의">✔ Advice = 언제 실행할지 정의</h3>
<p>예:</p>
<ul>
<li>@Before</li>
<li>@AfterReturning</li>
<li>@Around</li>
</ul>
<h3 id="✔-join-point--적용-가능한-모든-지점">✔ Join Point = 적용 가능한 모든 지점</h3>
<p>스프링에서는 주로 “메서드 실행 시점”</p>
<hr>
<h1 id="📌-8-실제-적용-개념-요약">📌 8. 실제 적용 개념 요약</h1>
<p>AOP는 아래와 같은 방식으로 동작한다.</p>
<pre><code>핵심 기능 호출 전   → @Before
핵심 기능 실행       → 비즈니스 로직
핵심 기능 실행 후   → @After, @AfterReturning
예외 발생 시         → @AfterThrowing
전체 제어 필요 시     → @Around</code></pre><p>공통 관심사를 핵심 로직에 섞지 않고도</p>
<p><strong>필요한 순간에 적절하게 섞어서 실행</strong>할 수 있게 해주는 기술이다.</p>
<hr>
<h1 id="📌-9-aop-최종-핵심-정리">📌 9. AOP 최종 핵심 정리</h1>
<pre><code>✔ 핵심 비즈니스 로직과 공통 기능을 분리하는 프로그래밍 방법
✔ 공통 기능(로깅/보안/트랜잭션 등)을 Aspect로 모듈화
✔ 핵심 코드가 깔끔해지고 유지보수성이 좋아짐
✔ 코드 재사용성 크게 증가
✔ 스프링의 대표적인 장점 중 하나</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Spring DI 정리: 왜 의존성 주입이 중요한가?]]></title>
            <link>https://velog.io/@roel_dev/Spring-DI-%EC%A0%95%EB%A6%AC-%EC%99%9C-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85%EC%9D%B4-%EC%A4%91%EC%9A%94%ED%95%9C%EA%B0%80</link>
            <guid>https://velog.io/@roel_dev/Spring-DI-%EC%A0%95%EB%A6%AC-%EC%99%9C-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85%EC%9D%B4-%EC%A4%91%EC%9A%94%ED%95%9C%EA%B0%80</guid>
            <pubDate>Mon, 24 Nov 2025 02:21:25 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-1-의존성dependency란">📌 1. 의존성(Dependency)란?</h2>
<p>어떤 객체가 작업을 수행하기 위해 <strong>다른 객체를 직접 생성하거나 사용해야 할 때</strong> 그 관계를 ‘의존한다’고 표현한다.</p>
<p>예를 들어, Roel 객체가 Galaxy 객체를 직접 생성하고 사용한다면,</p>
<p>Roel → Galaxy 로 의존성이 존재한다.</p>
<pre><code>💡 요약
한 객체의 코드에서 다른 객체를 생성하거나 호출하면 의존성이 생긴 것이다.</code></pre><hr>
<h2 id="📌-2-결합도coupling와-di의-필요성">📌 2. 결합도(Coupling)와 DI의 필요성</h2>
<p>의존성의 강도를 ‘결합도(Coupling)’라고 한다.</p>
<table>
<thead>
<tr>
<th>결합도</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td><strong>강한 결합 (Tight Coupling)</strong></td>
<td>특정 구현 클래스에 직접 의존 → 수정 어려움</td>
</tr>
<tr>
<td><strong>느슨한 결합 (Loose Coupling)</strong></td>
<td>인터페이스 기반으로 의존 → 유연하고 교체 쉬움</td>
</tr>
</tbody></table>
<h3 id="🔥-강한-결합의-예시">🔥 강한 결합의 예시</h3>
<pre><code class="language-java">
public class Roel {
    private Galaxy phone;

    public Roel() {
        this.phone = new Galaxy();
    }
}</code></pre>
<p>만약 Galaxy 대신 IPhone을 사용하고 싶다면?</p>
<p>→ Roel 내부 전체 코드를 수정해야 한다.</p>
<p>이처럼 <strong>구현 클래스에 강하게 묶여 있으면 변경에 취약</strong>하다.</p>
<hr>
<h2 id="📌-3-느슨한-결합을-위한-방법-인터페이스-사용">📌 3. 느슨한 결합을 위한 방법: 인터페이스 사용</h2>
<p>인터페이스를 도입하면 구현(Mobile 기종)이 바뀌어도</p>
<p>Roel 클래스는 <strong>Phone 인터페이스만 알고 있으면 된다.</strong></p>
<h3 id="🔽-phone-인터페이스">🔽 Phone 인터페이스</h3>
<pre><code class="language-java">
public interface Phone {
    void powerOn();
    void payment();
}</code></pre>
<h3 id="🔽-구현-클래스들">🔽 구현 클래스들</h3>
<pre><code class="language-java">
public class Galaxy implements Phone { ... }
public class IPhone implements Phone { ... }</code></pre>
<h3 id="🔽-느슨한-결합의-roel-클래스">🔽 느슨한 결합의 Roel 클래스</h3>
<pre><code class="language-java">
public class Roel {
    private final Phone phone;

    public Roel(Phone phone) {
        this.phone = phone;
    }
}</code></pre>
<p>Roel은 더 이상 Galaxy, IPhone이 무엇인지 알 필요가 없다.</p>
<p>→ <strong>유연한 구조, 유지보수성 UP!</strong></p>
<hr>
<h1 id="📌-4-didependency-injection란">📌 4. DI(Dependency Injection)란?</h1>
<p>DI는 말 그대로 <strong>의존성(Dependency)을 외부에서 주입(Injection)받는 방식</strong>이다.</p>
<p>즉, “필요한 객체를 스스로 new 로 만들지 않고, 외부에서 넣어주는 방식이다.”</p>
<pre><code>💡 DI란?
객체가 사용할 객체의 생성 책임을 외부(스프링 컨테이너)에 넘기고
자신은 주입받은 객체만 사용한다.</code></pre><p>이는 스프링이 지향하는 <strong>IoC(제어의 역전)</strong> 개념을 구체화한 것이다.</p>
<p>그래서 스프링을 <strong>IoC 컨테이너 혹은 DI 컨테이너</strong>라고도 부른다.</p>
<hr>
<h1 id="📌-5-di의-장점">📌 5. DI의 장점</h1>
<h3 id="✔-1-결합도-감소-→-변경에-강한-코드">✔ 1) 결합도 감소 → 변경에 강한 코드</h3>
<ul>
<li>구현체를 교체해도 생성자만 바뀌면 됨</li>
<li>유지보수가 쉬움</li>
</ul>
<h3 id="✔-2-테스트-용이성-증가">✔ 2) 테스트 용이성 증가</h3>
<ul>
<li>Mock 객체를 주입할 수 있어 단위 테스트가 쉬워짐</li>
</ul>
<hr>
<h1 id="📌-6-spring-di-주입-방법-3가지">📌 6. Spring DI 주입 방법 (3가지)</h1>
<p>스프링에서는 다음과 같은 방식으로 객체를 주입한다.</p>
<hr>
<h2 id="✔-1-생성자constructor-주입--⭐가장-권장">✔ (1) 생성자(Constructor) 주입 — ⭐가장 권장</h2>
<pre><code class="language-java">
@Component
public class Roel {

    private final Phone phone;

    @Autowired
    public Roel(Phone phone) {
        this.phone = phone;
    }
}</code></pre>
<h3 id="🔍-특징">🔍 특징</h3>
<ul>
<li><strong>불변(immutable)</strong> 의존성에 적합</li>
<li>스프링이 객체 만들 때 한 번만 호출되므로 안정적</li>
<li><code>final</code>로 선언 가능 → 값 변경 방지</li>
<li>필수 의존성 주입에 가장 적합</li>
<li>생성자가 하나라면 <code>@Autowired</code> 생략 가능</li>
</ul>
<pre><code>✔ 스프링 공식 권장 방식
✔ 반드시 필요하고 변경되면 안 되는 의존성에 적합
✔ 순환 참조를 애플리케이션 구동 시점에 잡아줌</code></pre><hr>
<h2 id="✔-2-setter수정자-주입--선택적-의존성에-적합">✔ (2) Setter(수정자) 주입 — 선택적 의존성에 적합</h2>
<pre><code class="language-java">
@Component
public class Roel {

    private Phone phone;

    @Autowired
    public void setPhone(Phone phone) {
        this.phone = phone;
    }
}</code></pre>
<h3 id="🔍-특징-1">🔍 특징</h3>
<ul>
<li>나중에 값 변경이 필요한 경우 사용</li>
<li>선택적(optional) 의존성에 적합</li>
<li>테스트나 일부 동적 상황에서 유용</li>
<li>단점: setter가 public 이어야 하고, 외부 변경 위험 존재</li>
</ul>
<hr>
<h2 id="✔-3-필드field-주입--❌-비권장">✔ (3) 필드(Field) 주입 — ❌ 비권장</h2>
<pre><code class="language-java">
@Component
public class Roel {

    @Autowired
    private Phone phone;
}</code></pre>
<h3 id="🔍-특징-2">🔍 특징</h3>
<ul>
<li><strong>가장 간편하지만 재사용성과 테스트성이 낮아 비권장</strong></li>
<li>DI 컨테이너 없이는 절대 동작하지 않음</li>
<li>순수 자바 코드로 테스트 불가</li>
<li>어떤 객체가 어떻게 주입되는지 코드만 보고 확인 어려움</li>
</ul>
<pre><code>⚠ 스프링 공식적으로 권장하지 않는 방식
(테스트 불가 + 유지보수 어려움)</code></pre><hr>
<h1 id="📌-7-왜-생성자-주입을-써야-할까">📌 7. 왜 생성자 주입을 써야 할까?</h1>
<h3 id="✔-1-불변성-보장">✔ 1) 불변성 보장</h3>
<ul>
<li>객체 생성 시 단 한 번만 호출</li>
<li>이후 변경 불가능 → 안정적</li>
</ul>
<h3 id="✔-2-순환-참조를-구동-시점에-해결">✔ 2) 순환 참조를 구동 시점에 해결</h3>
<p>Setter/필드 주입은 객체 생성 이후 주입되므로</p>
<p>순환 참조가 나중에 발생하는 반면,</p>
<p><strong>생성자 주입은 애플리케이션 시작과 함께 에러를 즉시 감지</strong>한다.</p>
<h3 id="✔-3-객체의-필수-의존성을-강제할-수-있음">✔ 3) 객체의 필수 의존성을 강제할 수 있음</h3>
<p>필요한 의존성이 없으면 객체 생성 자체가 불가능하기 때문에</p>
<p>설계 오류를 예방할 수 있다.</p>
<hr>
<h1 id="📌-8-di의-최종-핵심-정리">📌 8. DI의 최종 핵심 정리</h1>
<pre><code>✔ 객체 간 의존성을 외부에서 주입받는 구조
✔ 강한 결합을 느슨한 결합으로 전환
✔ 유지보수성과 테스트 용이성 향상
✔ 생성자 주입이 가장 권장됨
✔ 인터페이스 기반 설계로 유연한 구조 가능</code></pre><hr>
<h1 id="di가-왜-중요한가">DI가 왜 중요한가?</h1>
<p>DI는 단순히 “객체를 대신 넣어주는 기술”이 아니라, <strong>스프링이 추구하는 설계 철학 전체를 지탱하는 핵심 원리</strong>다.</p>
<p>객체 간 결합을 최소화하면 시스템은 자연스럽게 더 유연해지고, 변경에 강해지고, 테스트가 쉬워진다.</p>
<p>이는 곧 <strong>개발 생산성 향상</strong>, <strong>유지보수 비용 절감</strong>, <strong>안정적인 서비스 운영</strong>으로 이어진다.</p>
<p>또한 DI는 인터페이스 기반 설계를 자연스럽게 유도하기 때문에, 애플리케이션 구조를 확장 가능하고 견고하게 만든다.</p>
<p>로직 변경, 기능 추가, 의존성 교체가 잦은 실제 개발 환경에서 DI는 개발자에게 <strong>변화에 흔들리지 않는 코드 기반</strong>을 제공한다.</p>
<p>결국 DI는 “스프링을 스프링답게 만드는 근본 개념”이며, 스프링을 활용하는 프로젝트에서 <strong>핵심 설계 중심축</strong> 역할을 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래밍 언어(JAVA)]]></title>
            <link>https://velog.io/@roel_dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%96%B8%EC%96%B4JAVA</link>
            <guid>https://velog.io/@roel_dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%96%B8%EC%96%B4JAVA</guid>
            <pubDate>Mon, 17 Nov 2025 12:22:39 GMT</pubDate>
            <description><![CDATA[<h1 id="java-소개">Java 소개</h1>
<h2 id="🔹-java란">🔹 Java란?</h2>
<ul>
<li>객체지향 프로그래밍 언어(OOP).</li>
<li>“Write Once, Run Anywhere” 철학 → 한 번 작성하면 어떤 플랫폼에서도 실행 가능.</li>
<li>플랫폼 독립성(JVM 위에서 동작), 보안성, 자동 메모리 관리가 강점.</li>
</ul>
<hr>
<h2 id="🔹-java의-역사-요약">🔹 Java의 역사 요약</h2>
<ul>
<li><strong>1995</strong>: Sun Microsystems에서 발표 (제임스 고슬링 주도 개발)</li>
<li><strong>2006</strong>: Java 핵심 코드 오픈소스화</li>
<li><strong>2010</strong>: Oracle이 Sun Microsystems 인수 → Java 관리 주체 변경</li>
<li><strong>2018 이후</strong>: 6개월 주기의 정기 릴리즈 체계 도입</li>
</ul>
<hr>
<h2 id="🔹-java의-특징-핵심-정리">🔹 Java의 특징 핵심 정리</h2>
<ul>
<li><strong>객체지향</strong>: 클래스 기반, 객체 중심</li>
<li><strong>플랫폼 독립적</strong>: JVM 위에서 실행</li>
<li><strong>자동 메모리 관리</strong>: Garbage Collection 지원</li>
<li><strong>보안성 강함</strong>: 바이트코드 검증 및 안전한 실행 환경 제공</li>
<li><strong>멀티스레드 지원</strong>: 효율적인 동시성 처리</li>
<li><strong>풍부한 라이브러리</strong>: 표준 라이브러리 + 다양한 오픈소스 생태계</li>
<li><strong>네트워크 친화적</strong>: 네트워크 기반 애플리케이션에 최적화</li>
<li><strong>동적 로딩</strong>: 필요한 클래스를 동적으로 로딩해 효율 증가</li>
</ul>
<h1 id="java-타입">Java 타입</h1>
<p>Java의 변수 타입은 크게 <strong>기본형(Primitive Type)</strong> 과 <strong>참조형(Reference Type)</strong> 으로 나뉜다.</p>
<hr>
<h1 id="1-기본형-타입-primitive-type">1) 기본형 타입 (Primitive Type)</h1>
<table>
<thead>
<tr>
<th>구분</th>
<th>메모리 저장 방식</th>
<th>종류</th>
<th>크기</th>
<th>표현 범위</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td><strong>정수형</strong></td>
<td>실제 값을 Stack에 저장</td>
<td>byte, short, int, long</td>
<td>1B, 2B, 4B, 8B</td>
<td>-128~127 … ±2^63-1</td>
<td>정수만 저장</td>
</tr>
<tr>
<td><strong>실수형</strong></td>
<td>실제 값을 Stack에 저장</td>
<td>float, double</td>
<td>4B, 8B</td>
<td>IEEE 754 표준</td>
<td>소수 저장 가능</td>
</tr>
<tr>
<td><strong>문자형</strong></td>
<td>실제 값을 Stack에 저장</td>
<td>char</td>
<td>2B</td>
<td>0~65535 (유니코드)</td>
<td>문자 1개 저장</td>
</tr>
<tr>
<td><strong>논리형</strong></td>
<td>실제 값을 Stack에 저장</td>
<td>boolean</td>
<td>1B</td>
<td>true / false</td>
<td>논리 값만</td>
</tr>
</tbody></table>
<hr>
<h2 id="🔹-정수형-상세">🔹 정수형 상세</h2>
<table>
<thead>
<tr>
<th>타입</th>
<th>크기</th>
<th>표현 범위</th>
</tr>
</thead>
<tbody><tr>
<td>byte</td>
<td>1바이트</td>
<td>-128 ~ 127</td>
</tr>
<tr>
<td>short</td>
<td>2바이트</td>
<td>-32,768 ~ 32,767</td>
</tr>
<tr>
<td>int</td>
<td>4바이트</td>
<td>-2,147,483,648 ~ 2,147,483,647</td>
</tr>
<tr>
<td>long</td>
<td>8바이트</td>
<td>약 ±9.22×10^18</td>
</tr>
</tbody></table>
<hr>
<h2 id="🔹-실수형-상세">🔹 실수형 상세</h2>
<table>
<thead>
<tr>
<th>타입</th>
<th>크기</th>
<th>표현 범위</th>
<th>리터럴 표기</th>
</tr>
</thead>
<tbody><tr>
<td>float</td>
<td>4바이트</td>
<td>1.4×10^-45 ~ 3.4×10^38</td>
<td>F / f</td>
</tr>
<tr>
<td>double</td>
<td>8바이트</td>
<td>4.9×10^-324 ~ 1.7×10^308</td>
<td>D / d (생략 가능)</td>
</tr>
</tbody></table>
<hr>
<h2 id="🔹-문자형--논리형">🔹 문자형 / 논리형</h2>
<table>
<thead>
<tr>
<th>타입</th>
<th>크기</th>
<th>범위</th>
</tr>
</thead>
<tbody><tr>
<td>char</td>
<td>2바이트</td>
<td>0 ~ 65535 (유니코드)</td>
</tr>
<tr>
<td>boolean</td>
<td>1바이트</td>
<td>true, false</td>
</tr>
</tbody></table>
<hr>
<h1 id="2-참조형-타입-reference-type">2) 참조형 타입 (Reference Type)</h1>
<table>
<thead>
<tr>
<th>구분</th>
<th>저장 방식</th>
</tr>
</thead>
<tbody><tr>
<td>참조형</td>
<td><strong>실제 데이터는 Heap에 저장</strong>, 변수에는 <strong>주소(참조값)</strong> 저장</td>
</tr>
</tbody></table>
<h3 id="종류">종류</h3>
<ul>
<li><strong>array</strong> (배열)</li>
<li><strong>enum</strong></li>
<li><strong>class</strong></li>
<li><strong>interface</strong></li>
</ul>
<h3 id="특징">특징</h3>
<ul>
<li>인스턴스를 생성한 후 Heap 공간에 저장</li>
<li>Stack에는 주소값(참조값)이 저장됨</li>
<li>참조형은 new 키워드를 통해 주로 생성됨</li>
</ul>
<hr>
<h1 id="3-stack-메모리-vs-heap-메모리">3) Stack 메모리 vs Heap 메모리</h1>
<h3 id="🔹-stack-메모리">🔹 Stack 메모리</h3>
<ul>
<li>메서드 내부의 <strong>지역 변수 · 매개변수</strong> 저장</li>
<li>메서드 호출 시 생성, 종료 시 함께 제거</li>
<li>메모리 관리가 빠르고 단순함</li>
</ul>
<h3 id="🔹-heap-메모리">🔹 Heap 메모리</h3>
<ul>
<li><strong>객체 인스턴스</strong> 저장 공간</li>
<li>Garbage Collector가 관리</li>
<li>메모리 관리가 복잡함</li>
</ul>
<hr>
<h1 id="4-값-저장-방식-요약">4) 값 저장 방식 요약</h1>
<table>
<thead>
<tr>
<th>타입</th>
<th>저장 위치</th>
<th>저장되는 값</th>
</tr>
</thead>
<tbody><tr>
<td><strong>기본형</strong></td>
<td>Stack</td>
<td>실제 값</td>
</tr>
<tr>
<td><strong>참조형</strong></td>
<td>Stack + Heap</td>
<td>Stack에는 참조값, 실제 객체는 Heap</td>
</tr>
</tbody></table>
<hr>
<h1 id="5-기본-데이터-타입-선언-방식">5) 기본 데이터 타입 선언 방식</h1>
<pre><code class="language-java">int x;
int y = 3;

double pi, rate, avg;
double height = 180.5, weight = 75.0;</code></pre>
<h1 id="1-연산자의-전체-종류">1. 연산자의 전체 종류</h1>
<table>
<thead>
<tr>
<th>연산자 종류</th>
<th>연산자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>형변환 연산자</strong></td>
<td>(cast)</td>
<td>하나의 값을 지정된 타입으로 변환</td>
</tr>
<tr>
<td><strong>산술 연산자</strong></td>
<td>+, -, *, /, %, +=, -=, *=, /=, %=</td>
<td>기본 사칙연산 및 대입 복합연산</td>
</tr>
<tr>
<td><strong>관계 연산자</strong></td>
<td>&gt;, &lt;, &gt;=, &lt;=, ==, !=</td>
<td>비교 결과를 boolean값으로 반환</td>
</tr>
<tr>
<td><strong>비트 연산자</strong></td>
<td>&amp;,</td>
<td>, ^, ~, &lt;&lt;, &gt;&gt;, &gt;&gt;&gt;</td>
</tr>
<tr>
<td><strong>논리 연산자</strong></td>
<td>&amp;&amp;,</td>
<td></td>
</tr>
</tbody></table>
<hr>
<h1 id="2-산술-연산자-arithmetic-operators">2. 산술 연산자 (Arithmetic Operators)</h1>
<h2 id="🔹-종류-및-의미">🔹 종류 및 의미</h2>
<table>
<thead>
<tr>
<th>연산자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>+</td>
<td>두 피연산자 더함</td>
</tr>
<tr>
<td>-</td>
<td>두 피연산자 뺌</td>
</tr>
<tr>
<td>*</td>
<td>곱셈</td>
</tr>
<tr>
<td>/</td>
<td>나눗셈 (정수/정수 → 정수)</td>
</tr>
<tr>
<td>%</td>
<td>나머지 연산</td>
</tr>
<tr>
<td>+=, -=, …</td>
<td>복합 대입 연산</td>
</tr>
</tbody></table>
<hr>
<h2 id="🔹-산술-연산자-특징">🔹 산술 연산자 특징</h2>
<ul>
<li>정수/정수 → 정수</li>
<li>실수 계산 시 주의 (1 / 1000.0 = 0.001)</li>
<li>나머지 연산은 정수형에 주로 사용</li>
<li>정수형 나눗셈은 소수점 이하 버림</li>
</ul>
<hr>
<h2 id="🔹-예시">🔹 예시</h2>
<pre><code class="language-java">int intNum = 13;
double number = 1.1;

number + number → 2.2
intNum * 2 → 26
</code></pre>
<hr>
<h1 id="3-나누기--곱하기-주의사항">3. 나누기 &amp; 곱하기 주의사항</h1>
<ul>
<li>정수 나누기 → 항상 정수</li>
<li>실수 나누기 → 실수</li>
<li>연산할 때 자료형에 따라 결과 타입이 결정됨</li>
<li>float 타입 나눗셈 시 소수점 이하 오차 발생 가능</li>
<li>overflow/underflow 여부 확인 필요</li>
</ul>
<hr>
<h1 id="4-자동-형변환-promotion">4. 자동 형변환 (Promotion)</h1>
<h2 id="🔹-작은-타입-→-큰-타입-자동-변환">🔹 작은 타입 → 큰 타입 자동 변환</h2>
<p>byte → short → int → long → float → double</p>
<ul>
<li>연산 시 피연산자 중 큰 자료형으로 자동 변환됨</li>
<li>Object로 저장 후 <code>.getClass().getName()</code>으로 실제 타입 확인 가능</li>
</ul>
<p>예:</p>
<pre><code>short → java.lang.Short
long → java.lang.Long
double → java.lang.Double
</code></pre><hr>
<h1 id="5-오버플로우--언더플로우">5. 오버플로우 &amp; 언더플로우</h1>
<h2 id="🔹-오버플로우overflow">🔹 오버플로우(Overflow)</h2>
<ul>
<li>정수 범위를 초과할 때 발생</li>
<li>값이 최소값 또는 최대값으로 순환(wrap-around)</li>
</ul>
<p>예:</p>
<pre><code>int result = Integer.MAX_VALUE + 1;
→ -2147483648</code></pre><hr>
<h2 id="🔹-언더플로우underflow">🔹 언더플로우(Underflow)</h2>
<ul>
<li>표현 가능한 최소 실수 범위보다 더 작아서 0.0이 되는 현상</li>
<li>float, double에서 발생</li>
</ul>
<p>예:</p>
<pre><code>1e-300 * 1e-300 → 0.0</code></pre><hr>
<h1 id="6-단항-연산자-unary-operators">6. 단항 연산자 (Unary Operators)</h1>
<h2 id="🔹-종류">🔹 종류</h2>
<table>
<thead>
<tr>
<th>연산자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>++i</td>
<td>먼저 증가 후 계산</td>
</tr>
<tr>
<td>i++</td>
<td>계산 후 증가</td>
</tr>
<tr>
<td>--i</td>
<td>먼저 감소 후 계산</td>
</tr>
<tr>
<td>i--</td>
<td>계산 후 감소</td>
</tr>
<tr>
<td>+i</td>
<td>양수 유지</td>
</tr>
<tr>
<td>-i</td>
<td>음수 전환</td>
</tr>
</tbody></table>
<hr>
<h2 id="🔹-예시-1">🔹 예시</h2>
<pre><code>int i = 3;
i++; → 4
++i; → 5
--i; → 4
</code></pre><hr>
<h1 id="7-관계-연산자-comparison-operators">7. 관계 연산자 (Comparison Operators)</h1>
<h2 id="🔹-종류-1">🔹 종류</h2>
<table>
<thead>
<tr>
<th>연산자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>&gt;</td>
<td>왼 &gt; 오른</td>
</tr>
<tr>
<td>&lt;</td>
<td>왼 &lt; 오른</td>
</tr>
<tr>
<td>&gt;=</td>
<td>왼 ≥ 오른</td>
</tr>
<tr>
<td>&lt;=</td>
<td>왼 ≤ 오른</td>
</tr>
<tr>
<td>==</td>
<td>같다</td>
</tr>
<tr>
<td>!=</td>
<td>다르다</td>
</tr>
</tbody></table>
<hr>
<h2 id="🔹-예시-2">🔹 예시</h2>
<pre><code class="language-java">firstNumber = 10;
secondNumber = 20;

10 &gt; 20 → false
(10 + 30) &lt;= 20 → false
10 != 20 → true
</code></pre>
<hr>
<h1 id="8-논리-연산자-logical-operators">8. 논리 연산자 (Logical Operators)</h1>
<h2 id="🔹-종류-및-설명">🔹 종류 및 설명</h2>
<table>
<thead>
<tr>
<th>연산자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>&amp;&amp;</td>
<td>AND, 둘 다 true일 때 true</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>!</td>
<td>NOT</td>
</tr>
<tr>
<td>^</td>
<td>XOR (서로 다르면 true)</td>
</tr>
<tr>
<td>?:</td>
<td>조건 부 연산자</td>
</tr>
</tbody></table>
<hr>
<h2 id="🔹-short-circuit단락-평가">🔹 Short-Circuit(단락 평가)</h2>
<ul>
<li>&amp;&amp;: 왼쪽이 false면 오른쪽 평가 안 함</li>
<li>||: 왼쪽이 true면 오른쪽 평가 안 함</li>
</ul>
<p>예:</p>
<pre><code class="language-java">(num1 &gt; num2) &amp;&amp; (num1 &lt; num2)</code></pre>
<hr>
<h2 id="🔹-삼항-연산자-형태">🔹 삼항 연산자 형태</h2>
<pre><code>조건식 ? 값1 : 값2</code></pre><hr>
<h1 id="9-비트-연산자-bitwise-operators">9. 비트 연산자 (Bitwise Operators)</h1>
<h2 id="🔹-비트란">🔹 비트란?</h2>
<ul>
<li>정수를 2진수 형태로 저장</li>
<li>메모리 최소 단위(0 또는 1)</li>
</ul>
<p>예:</p>
<pre><code>int bTemp = 21
0001 0101</code></pre><hr>
<h2 id="🔹-비트-연산자-종류">🔹 비트 연산자 종류</h2>
<table>
<thead>
<tr>
<th>연산자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>&amp;</td>
<td>비트 AND</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>^</td>
<td>비트 XOR</td>
</tr>
<tr>
<td>~</td>
<td>비트 반전</td>
</tr>
<tr>
<td>&lt;&lt;</td>
<td>왼쪽 시프트</td>
</tr>
<tr>
<td>&gt;&gt;</td>
<td>오른쪽 시프트(부호 유지)</td>
</tr>
<tr>
<td>&gt;&gt;&gt;</td>
<td>오른쪽 시프트(부호 무시)</td>
</tr>
</tbody></table>
<h1 id="10-비트-연산자-and-or">10. 비트 연산자 (AND, OR)</h1>
<p>비트 단위로 데이터를 처리하는 연산자.</p>
<p>특정 비트 정보를 추출하거나, ON/OFF 처리할 때 자주 사용.</p>
<hr>
<h2 id="🔹-and-연산-">🔹 AND 연산 (&amp;)</h2>
<ul>
<li>두 비트 모두 1인 경우에만 1 반환</li>
<li>특정 비트만 추출할 때 사용</li>
</ul>
<p>예)</p>
<pre><code>0x15 &amp; 0x04 → 0x04</code></pre><hr>
<h2 id="🔹-or-연산-">🔹 OR 연산 (|)</h2>
<ul>
<li>두 비트 중 하나라도 1이면 1 반환</li>
<li>특정 비트를 켜고 싶을 때 사용</li>
</ul>
<p>예)</p>
<pre><code>0x15 | 0x02 → 0x17</code></pre><hr>
<h1 id="11-비트-연산자-예제-and-or">11. 비트 연산자 예제 (AND, OR)</h1>
<p>권한과 같은 플래그 값을 다룰 때 흔히 사용됨.</p>
<h3 id="권한-정의-예시">권한 정의 (예시)</h3>
<ul>
<li>read : 1 (0b001)</li>
<li>write : 2 (0b010)</li>
<li>exec : 4 (0b100)</li>
</ul>
<h3 id="🔹-권한-추가-or">🔹 권한 추가 (OR)</h3>
<pre><code class="language-java">userPermission |= readPermission;</code></pre>
<h3 id="🔹-권한-확인-and">🔹 권한 확인 (AND)</h3>
<pre><code class="language-java">userPermission &amp; readPermission</code></pre>
<h3 id="🔹-권한-제거-and--not">🔹 권한 제거 (AND + NOT)</h3>
<pre><code class="language-java">userPermission &amp;= ~readPermission;</code></pre>
<hr>
<h1 id="12-시프트-연산자---">12. 시프트 연산자 (&lt;&lt;, &gt;&gt;, &gt;&gt;&gt;)</h1>
<p>정수의 모든 비트를 <strong>왼쪽/오른쪽으로 이동</strong>시키는 연산자.</p>
<table>
<thead>
<tr>
<th>연산자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>&lt;&lt;</td>
<td>왼쪽 시프트(값이 <em>2배</em> 증가하는 효과)</td>
</tr>
<tr>
<td>&gt;&gt;</td>
<td>오른쪽 시프트(부호 유지)</td>
</tr>
<tr>
<td>&gt;&gt;&gt;</td>
<td>오른쪽 시프트(부호 없는 시프트, 앞을 0으로 채움)</td>
</tr>
</tbody></table>
<p>예)</p>
<pre><code>0x80000039 &lt;&lt; 3 → 0x000001C8</code></pre><hr>
<h1 id="13-시프트-연산-예제">13. 시프트 연산 예제</h1>
<h3 id="🔹-암호화에-xor-사용">🔹 암호화에 XOR 사용</h3>
<pre><code class="language-java">encrypted = original ^ key
decrypted = encrypted ^ key</code></pre>
<p>XOR은 (A ^ K) ^ K = A 의 성질을 이용하기 때문.</p>
<hr>
<h1 id="14-xor-연산자-">14. XOR 연산자 (^)</h1>
<ul>
<li>두 비트가 같으면 0</li>
<li>다르면 1</li>
<li>암호화나 비트 토글 등에 흔히 사용됨</li>
</ul>
<p>예)</p>
<pre><code>0x15 ^ 0x2C → 0x39</code></pre><hr>
<h1 id="15-비트-연산자-예제-시프트--xor">15. 비트 연산자 예제 (시프트 + XOR)</h1>
<h3 id="부호-유지-시프트">&gt;&gt; (부호 유지 시프트)</h3>
<ul>
<li>음수 유지됨</li>
</ul>
<h3 id="부호-없는-시프트">&gt;&gt;&gt; (부호 없는 시프트)</h3>
<ul>
<li>앞자리를 0으로 채움 → 양수처럼 보임</li>
</ul>
<h3 id="왼쪽-시프트">&lt;&lt; (왼쪽 시프트)</h3>
<ul>
<li>4 &lt;&lt; 3 = 32</li>
</ul>
<hr>
<h1 id="16-연산자-우선순위">16. 연산자 우선순위</h1>
<p>여러 연산자가 함께 쓰이면, <strong>우선순위가 높은 연산자부터 계산</strong>됨.</p>
<hr>
<h2 id="🔹-우선순위-표">🔹 우선순위 표</h2>
<table>
<thead>
<tr>
<th>우선순위</th>
<th>연산자</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>(), [], {}, ++/--(postfix)</td>
</tr>
<tr>
<td>2</td>
<td>!, ~, ++/--(prefix), (cast)</td>
</tr>
<tr>
<td>3</td>
<td>*, /, %</td>
</tr>
<tr>
<td>4</td>
<td>+, -</td>
</tr>
<tr>
<td>5</td>
<td>&lt;&lt;, &gt;&gt;, &gt;&gt;&gt;</td>
</tr>
<tr>
<td>6</td>
<td>&lt;, &lt;=, &gt;, &gt;=</td>
</tr>
<tr>
<td>7</td>
<td>==, !=</td>
</tr>
<tr>
<td>8</td>
<td>&amp;</td>
</tr>
<tr>
<td>9</td>
<td>^</td>
</tr>
<tr>
<td>10</td>
<td></td>
</tr>
<tr>
<td>11</td>
<td>&amp;&amp;</td>
</tr>
<tr>
<td>12</td>
<td></td>
</tr>
<tr>
<td>13</td>
<td>?:</td>
</tr>
<tr>
<td>14</td>
<td>=, +=, -=, *=, /=, %=, &lt;&lt;=, &gt;&gt;=, &amp;=, ^=,</td>
</tr>
</tbody></table>
<hr>
<h2 id="🔹-예제-1">🔹 예제 1</h2>
<pre><code class="language-java">c = a + b * d;
// 곱셈이 더하기보다 우선 → b*d 먼저 계산</code></pre>
<hr>
<h2 id="🔹-예제-2">🔹 예제 2</h2>
<pre><code class="language-java">(condition || false &amp;&amp; false) → true</code></pre>
<pre><code>c = e &amp; f ^ d;</code></pre><ul>
<li>&amp;가 ^보다 먼저 수행</li>
</ul>
<hr>
<h1 id="17-참조형-데이터-타입과-기본값">17. 참조형 데이터 타입과 기본값</h1>
<p>기본형과 다르게, 참조형 값은 <strong>주소를 저장하며 null 가능</strong>.</p>
<hr>
<h2 id="🔹-참조형-종류">🔹 참조형 종류</h2>
<ul>
<li>클래스 (Class)</li>
<li>인터페이스 (Interface)</li>
<li>배열 (Array)</li>
<li>열거형 (Enum)</li>
</ul>
<hr>
<h2 id="🔹-메모리-크기와-기본값">🔹 메모리 크기와 기본값</h2>
<table>
<thead>
<tr>
<th>타입</th>
<th>메모리 크기</th>
<th>기본값</th>
</tr>
</thead>
<tbody><tr>
<td>클래스</td>
<td>4바이트</td>
<td>null</td>
</tr>
<tr>
<td>인터페이스</td>
<td>4바이트</td>
<td>null</td>
</tr>
<tr>
<td>배열</td>
<td>4바이트</td>
<td>null</td>
</tr>
<tr>
<td>열거</td>
<td>4바이트</td>
<td>null</td>
</tr>
</tbody></table>
<hr>
<h2 id="🔹-기본형과-참조형의-기본값-비교">🔹 기본형과 참조형의 기본값 비교</h2>
<table>
<thead>
<tr>
<th>데이터 타입</th>
<th>기본값</th>
</tr>
</thead>
<tbody><tr>
<td>byte</td>
<td>0</td>
</tr>
<tr>
<td>char</td>
<td>&#39;\u0000&#39;</td>
</tr>
<tr>
<td>short</td>
<td>0</td>
</tr>
<tr>
<td>int</td>
<td>0</td>
</tr>
<tr>
<td>long</td>
<td>0L</td>
</tr>
<tr>
<td>float</td>
<td>0.0F</td>
</tr>
<tr>
<td>double</td>
<td>0.0</td>
</tr>
<tr>
<td>boolean</td>
<td>false</td>
</tr>
<tr>
<td>배열/클래스/인터페이스</td>
<td>null</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[AI 챗봇은 어떻게 대화를 '기억'할까?]]></title>
            <link>https://velog.io/@roel_dev/AI-%EC%B1%97%EB%B4%87%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8C%80%ED%99%94%EB%A5%BC-%EA%B8%B0%EC%96%B5%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@roel_dev/AI-%EC%B1%97%EB%B4%87%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8C%80%ED%99%94%EB%A5%BC-%EA%B8%B0%EC%96%B5%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Mon, 27 Oct 2025 04:53:43 GMT</pubDate>
            <description><![CDATA[<h2 id="🤖-ai-챗봇의-기억-작동-방식">🤖 AI 챗봇의 &#39;기억&#39; 작동 방식</h2>
<p>LLM(거대 언어 모델) 자체는 <strong>&#39;기억&#39;이 없습니다(Stateless)</strong>. 매번의 API 호출은 완전히 독립적입니다. &quot;안녕&quot;이라고 말한 뒤 &quot;내 이름이 뭐야?&quot;라고 물으면, LLM은 &quot;안녕&quot;이라는 이전 대화를 모르기 때문에 대답할 수 없습니다.</p>
<p>&#39;기억&#39;을 구현한다는 것은 <strong>&quot;이전 대화 내용을 현재 프롬프트에 포함시켜서&quot;</strong> LLM에게 &#39;문맥(Context)&#39;을 함께 전달하는 것을 의미합니다.</p>
<p>핵심 질문은 &quot;이전 대화 중 <em>어떤 것*을 *어떻게</em> 프롬프트에 넣을 것인가?&quot;입니다.</p>
<hr>
<h2 id="1-기본-기억-방법-프롬프트에-연결하기">1. 기본 기억 방법: 프롬프트에 &#39;연결&#39;하기</h2>
<p>가장 고전적이고 기본적인 방법입니다.</p>
<h3 id="a-전체-대화-내역-전달-conversationbuffermemory">A. 전체 대화 내역 전달 (ConversationBufferMemory)</h3>
<ul>
<li><p><strong>작동 방식:</strong> 사용자와 AI가 나눈 <strong>모든 대화 기록</strong>을 순서대로 프롬프트에 전부 집어넣습니다.</p>
</li>
<li><p><strong>예시 프롬프트:</strong></p>
<p>  <code>Human: 안녕?
  AI: 안녕하세요!
  Human: 오늘 날씨 어때?
  AI: 서울은 맑습니다.
  Human: 어제 내가 뭐 물어봤는지 기억해?
  AI: ... (이제 LLM이 &#39;오늘 날씨&#39;를 물어본 것을 보고 대답함)</code></p>
</li>
<li><p><strong>장점:</strong> 완벽한 기억력.</p>
</li>
<li><p><strong>단점:</strong> 대화가 길어지면 프롬프트가 <strong>컨텍스트 창(Context Window) 한계</strong> (예: 4K, 32K 토큰)를 초과하고, API 비용이 폭발적으로 증가합니다.</p>
</li>
</ul>
<h3 id="예시-코드">예시 코드</h3>
<pre><code class="language-python">from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory

# 메모리 포함 체인 생성
memory = ConversationBufferMemory()
chain = ConversationChain(llm = llm, memory=memory)

# 대화 시작
print(chain.run(&quot;안녕? 나는 기영이야.&quot;)) # 기영아 안녕
print(chain.run(&quot;내 이름이 뭐라고?&quot;)) # 너의 이름은 기영이야.</code></pre>
<h3 id="b-최근-대화만-전달-conversationbufferwindowmemory">B. 최근 대화만 전달 (ConversationBufferWindowMemory)</h3>
<ul>
<li><strong>작동 방식:</strong> <code>A</code>의 단점을 보완. 전체 대화가 아닌, <strong>최근 <code>k</code>개의 대화</strong>만 잘라서 프롬프트에 넣습니다. (예: 최근 5번의 턴)</li>
<li><strong>장점:</strong> 토큰 한계와 비용 문제를 조절할 수 있습니다.</li>
<li><strong>단점:</strong> <code>k</code>개 이전의 중요한 정보(예: &quot;내 이름은 OOO이야&quot;)를 잊어버립니다.</li>
</ul>
<h3 id="예시-코드-1">예시 코드</h3>
<pre><code class="language-python">from langchain.memory import ConversationBufferWindowMemory

# 최근 2턴만 기억하는 메모리
memory = ConversationBufferWindowMemory(k=2)
chain = ConversationChain(llm=llm, memory=memory)

# 대화
print(chain.run(&quot;나는 기영이야.&quot;))
memory.load_memory_variables({})

print(chain.run(&quot;내가 좋아하는 색은 파란색이야.&quot;))
memory.load_memory_variables({})

print(chain.run(&quot;나는 영화보는 것을 좋아해&quot;))
memory.load_memory_variables({})

print(chain.run(&quot;지금 배고픈데 뭘 먹을까?&quot;))
memory.load_memory_variables({})

print(chain.run(&quot;내가 누구라고?&quot;)) # 누군지 기억못함.
memory.load_memory_variables({})</code></pre>
<h3 id="c-대화-요약본-전달-conversationsummarymemory">C. 대화 요약본 전달 (ConversationSummaryMemory)</h3>
<ul>
<li><strong>작동 방식:</strong> 대화가 진행될 때마다, LLM을 이용해 <strong>이전 대화 내용을 요약</strong>합니다. 그리고 새 질문과 함께 <strong>&#39;요약본&#39;</strong>을 프롬프트에 넣습니다.</li>
<li><strong>장점:</strong> 긴 대화도 압축해서 기억할 수 있습니다.</li>
<li><strong>단점:</strong> 요약 과정에서 세부 정보가 손실될 수 있고, 요약을 위한 추가 LLM 호출 비용이 듭니다.</li>
</ul>
<h3 id="예시-코드-2">예시 코드</h3>
<pre><code class="language-python">from langchain.memory import ConversationSummaryMemory

# 요약 메모리 생성 (요약용 LLM 필요)
memory = ConversationSummaryMemory(llm = llm)

# 체인 구성
chain = ConversationChain(llm = llm, memory = memory)

# 대화
print(chain.run(&quot;오늘은 운동하고, 친구랑 밥도 먹고, 강의도 들었어.&quot;))
print(chain.run(&quot;내가 오늘 뭐했는지 기억나?&quot;))
print(chain.run(&quot;너는 어떻게 지냈어?&quot;))
# 담긴 메모리 확인하기
memory.chat_memory.messages
# 내용 요약
print(memory.buffer)</code></pre>
<h3 id="요약">요약</h3>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/e3b89a17-0f92-4198-9d9c-09bd9b12a35f/image.png" alt=""></p>
<hr>
<h2 id="2-고급-기억-방법-rag-검색-기반-기억">2. 고급 기억 방법: RAG (검색) 기반 기억</h2>
<p>최근 가장 많이 사용되는 방식이며, RAG(검색 증강 생성)의 원리를 대화 기억에 적용한 것입니다.</p>
<h3 id="rag-기반-기억-vectorstore-backed-memory">RAG 기반 기억 (VectorStore-backed Memory)</h3>
<ul>
<li><strong>작동 방식:</strong><ol>
<li>모든 대화(Human/AI)를 <strong>Vector DB (예: Chroma)</strong>에 실시간으로 저장(임베딩)합니다.</li>
<li>사용자가 새 질문을 하면, 그 질문과 <strong>&#39;가장 관련성 높은&#39;</strong> 과거 대화 조각들을 DB에서 검색(Search)합니다.</li>
<li>프롬프트에 <strong>[최근 <code>k</code>개 대화] + [검색된 관련 과거 대화] + [새 질문]</strong>을 넣어 LLM에 전달합니다.</li>
</ol>
</li>
<li><strong>장점:</strong><ul>
<li><strong>장기 기억:</strong> 대화가 아무리 길어져도 상관없습니다.</li>
<li><strong>관련성:</strong> &quot;3주 전에 말했던 내 프로젝트 이름 뭐야?&quot; 같은 질문에도 3주 전의 관련 기록을 찾아와 대답할 수 있습니다.</li>
<li><strong>효율성:</strong> 토큰 한계를 걱정할 필요가 없습니다.</li>
</ul>
</li>
<li><strong>단점:</strong> 구현이 다소 복잡하고, Vector DB가 필요합니다.</li>
</ul>
<h3 id="예시-코드-3">예시 코드</h3>
<pre><code class="language-python">from langchain.vectorstores import Chroma

# split_texts, embedding_model 정의 
...

# ChromaDB를 만들면서 저장
vectorstore = Chroma.from_texts(split_texts, embedding_model, persist_directory=&quot;./chroma_db&quot;)

query = &quot;농촌 계몽운동에 대한 내용&quot;
retrieved_docs = vectorstore.similarity_search(query, k=3)

# 결과 출력
print(&quot;검색 결과:&quot;)
for doc in retrieved_docs:
    print(doc.page_content)
    print(&#39;-&#39;*200)</code></pre>
<h3 id="💡-chroma-vs-faiss-간단-비교">💡 Chroma vs. FAISS: 간단 비교</h3>
<p>둘 다 벡터를 저장하고 검색하는 핵심 역할을 하지만, 지향점이 약간 다릅니다.</p>
<ul>
<li><strong>Chroma (ChromaDB):</strong><ul>
<li>*&quot;데이터베이스 (Database)&quot;**에 가깝습니다.</li>
<li>벡터뿐만 아니라 <strong>메타데이터(문서 출처, 시간 등) 저장 및 필터링</strong> 기능을 기본으로 제공합니다.</li>
<li>데이터 저장, 관리, API 제공 등 RAG에 필요한 기능이 잘 갖춰져 있어 <strong>개발 및 프로토타이핑이 매우 편리</strong>합니다.</li>
<li>LangChain, LlamaIndex 등과 통합이 쉽습니다.</li>
</ul>
</li>
<li><strong>FAISS (Facebook AI Similarity Search):</strong><ul>
<li>*&quot;검색 라이브러리 (Library)&quot;**에 가깝습니다.</li>
<li>Meta(페이스북)에서 개발했으며, 오직 <strong>매우 빠르고 효율적인 벡터 검색(유사도 검색)</strong> 자체에만 초점을 맞춥니다.</li>
<li>수십억 개 단위의 <strong>초대규모 벡터</strong>를 처리할 때 최고의 성능을 보입니다.</li>
<li>하지만 FAISS 자체는 데이터를 영구 저장(persistence)하거나 메타데이터를 관리하는 기능을 직접 제공하지 않습니다. (사용자가 직접 파일로 저장/로드하거나, 다른 DB와 조합해서 써야 함)</li>
</ul>
</li>
</ul>
<h3 id="예시-코드-4">예시 코드</h3>
<pre><code class="language-python">from langchain.vectorstores import FAISS

# split_docs, embedding_model 정의 
...

vectorstore = FAISS.from_documents(split_docs, embedding_model)
vectorstore.save_local(&quot;faiss_index&quot;)  # &#39;faiss_index&#39; 폴더에 저장됨

# 저장된 벡터DB를 로딩하기
new_vectorstore = FAISS.load_local(
    &quot;faiss_index&quot;,
    embedding_model,
    allow_dangerous_deserialization=True
)

new_docs = [
    Document(page_content=&quot;조선시대의 교육 제도는 성균관 중심이었다.&quot;, metadata={&quot;source&quot;: &quot;추가&quot;}),
    Document(page_content=&quot;한국 전통 사회에서 글을 읽는 능력은 권력의 상징이었다.&quot;, metadata={&quot;source&quot;: &quot;추가&quot;})
]

# 문서 추가
vectorstore.add_documents(new_docs)</code></pre>
<h3 id="요약-1">요약</h3>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/9e6b81fa-4959-4a53-a3cd-2e10d3b29599/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/273240df-640d-4dbb-8a2a-134b1e0ac0b0/image.png" alt=""></p>
<hr>
<h2 id="3-상태-저장소-memorysaver의-역할">3. 상태 저장소: <code>MemorySaver</code>의 역할</h2>
<p>사용자님이 언급하신 <code>from langgraph.checkpoint.memory import MemorySaver</code>는 위 1, 2번과는 약간 다른 차원의 이야기입니다.</p>
<p><code>MemorySaver</code>는 &quot;기억을 프롬프트로 만드는 방법&quot;이 아니라, <strong>&quot;대화의 상태(State)를 어디에 저장(Persistence)할 것인가&quot;</strong>에 대한 도구입니다.</p>
<ul>
<li><strong><code>MemorySaver</code> (인메모리):</strong> 가장 기본. 대화 기록을 그냥 <strong>RAM(메모리)</strong>에 저장합니다. 챗봇 서버가 재시작되면 모든 대화 기록이 사라집니다. (테스트용)</li>
<li><strong><code>RedisSaver</code> / <code>PostgresSaver</code> 등:</strong> 대화 기록을 <strong>외부 DB(Redis, Postgres 등)</strong>에 저장합니다.</li>
</ul>
<p>LangGraph 같은 에이전트 프레임워크에서 <code>MemorySaver</code>는 단순히 채팅 기록뿐만 아니라, <strong>에이전트의 현재 작업 상태(State)</strong> 전체(예: &#39;A&#39; 작업 완료, &#39;B&#39; 작업 대기 중)를 저장하는 <strong>&#39;체크포인터(Checkpointer)&#39;</strong> 역할을 합니다.</p>
<hr>
<h2 id="📈-요즘-대세는">📈 요즘 대세는?</h2>
<ul>
<li><strong>&quot;RAG 기반 기억 (VectorStore) + 상태 저장소 (DB Checkpointer)&quot;</strong>의 조합입니다.</li>
</ul>
<ol>
<li><strong>단기 기억 (Window):</strong> 최근 3~5개의 대화는 프롬프트에 항상 포함시킵니다. (가장 즉각적인 문맥)</li>
<li><strong>장기 기억 (RAG):</strong> 사용자의 질문과 관련성 높은 과거 대화 기록을 Vector DB에서 검색하여 프롬프트에 추가합니다.</li>
<li><strong>대화 저장 (Persistence):</strong> 이 모든 대화 기록과 에이전트의 상태는 <code>MemorySaver</code> (혹은 LangChain의 <code>ChatMessageHistory</code> 인터페이스)를 통해 <strong>Redis나 Postgres 같은 외부 DB에 영구 저장</strong>합니다.</li>
</ol>
<h2 id="결론"><strong>결론</strong></h2>
<p>단순한 챗봇은 <strong>&#39;최근 대화만 기억(Window)&#39;</strong>하는 방식을,
고급 챗봇이나 에이전트는 <strong>&#39;RAG(검색)로 장기 기억&#39;</strong>을 구현하고,
LangGraph를 쓴다면 <code>MemorySaver</code>를 이용해 이 모든 상태를 <strong>&#39;DB에 저장&#39;</strong>하는 것이 현재의 표준 방식입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[머신러닝 기본 개념 정리(AICE 시험대비)]]></title>
            <link>https://velog.io/@roel_dev/%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%ACAICE-%EC%8B%9C%ED%97%98%EB%8C%80%EB%B9%84</link>
            <guid>https://velog.io/@roel_dev/%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%ACAICE-%EC%8B%9C%ED%97%98%EB%8C%80%EB%B9%84</guid>
            <pubDate>Mon, 20 Oct 2025 07:50:46 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요. AICE 시험을 준비하며 꼭 알아야 할 머신러닝 핵심 개념들을 정리해 보았습니다.</p>
<p><strong>1. 데이터 스케일링 (Scaling) 3대장</strong></p>
<p>데이터를 모델에 학습시키기 전, 피처들의 단위를 맞춰주는 전처리 과정입니다. 왜 할까요? 만약 &#39;키(cm)&#39;와 &#39;나이(세)&#39;를 함께 사용한다면, &#39;키&#39;의 숫자 범위가 훨씬 커서 모델이 &#39;키&#39; 피처를 더 중요하다고 잘못 판단할 수 있기 때문입니다.</p>
<p><strong>1) StandardScaler (표준화)</strong></p>
<ul>
<li><strong>개념:</strong> 가장 널리 쓰이는 스케일러입니다. 각 피처의 <strong>평균(Mean)을 0, 표준편차(Standard Deviation)를 1</strong>로 변환합니다. (이를 Z-점수 정규화라고도 합니다.)</li>
<li><strong>특징:</strong><ul>
<li>데이터가 정규분포(가우시안 분포)를 따른다고 가정할 때 효과가 좋습니다.</li>
<li>아웃라이어(이상치)에 민감하게 반응할 수 있습니다.</li>
</ul>
</li>
<li><strong>언제 쓸까?</strong><ul>
<li>대부분의 머신러닝 모델(특히 선형 회귀, 로지스틱 회귀, SVM 등)의 <strong>기본 스케일러</strong>로 사용하기 좋습니다.</li>
<li>데이터가 정규분포에 가까울 때.</li>
</ul>
</li>
</ul>
<pre><code class="language-python">from sklearn.preprocessing import StandardScaler

# 1. 스케일러 객체 생성
scaler_std = StandardScaler()

# 2. 데이터에 맞게 &#39;학습(fit)&#39;하고 &#39;변환(transform)&#39;
X_scaled_std = scaler_std.fit_transform(X)

print(&quot;StandardScaler 변환 결과:\n&quot;, X_scaled_std)</code></pre>
<p><strong>2) MinMaxScaler (정규화)</strong></p>
<ul>
<li><strong>개념:</strong> 모든 피처 값을 <strong>0과 1 사이의 값</strong>으로 압축시킵니다. (최솟값은 0, 최댓값은 1이 됩니다.)</li>
<li><strong>특징:</strong><ul>
<li><code>StandardScaler</code>와 달리 <strong>아웃라이어(이상치)에 매우 민감</strong>합니다. 예를 들어 [1, 2, 3, 100]이 있다면, 1, 2, 3은 0에 가깝게, 100은 1에 가깝게 매핑되어 버립니다.</li>
</ul>
</li>
<li><strong>언제 쓸까?</strong><ul>
<li>이미지 데이터(픽셀 값이 0<del>255 범위일 때)를 0</del>1 사이로 변환할 때.</li>
<li>데이터의 범위가 명확하게 정해져 있을 때.</li>
<li><strong>주의:</strong> 아웃라이어가 있다면 사용에 유의해야 합니다.</li>
</ul>
</li>
</ul>
<pre><code class="language-python">from sklearn.preprocessing import MinMaxScaler

# 1. 스케일러 객체 생성
scaler_mms = MinMaxScaler()

# 2. 데이터에 맞게 &#39;학습(fit)&#39;하고 &#39;변환(transform)&#39;
X_scaled_mms = scaler_mms.fit_transform(X)

print(&quot;MinMaxScaler 변환 결과:\n&quot;, X_scaled_mms)</code></pre>
<p><strong>3) RobustScaler (로버스트 스케일링)</strong></p>
<ul>
<li><strong>개념:</strong> 이름(Robust)처럼 <strong>아웃라이어에 강건한</strong> 스케일러입니다. 평균/표준편차 대신 <strong>중앙값(Median)</strong>과 사분위 범위(IQR)를 사용합니다.</li>
<li><strong>특징:</strong><ul>
<li>아웃라이어의 영향을 거의 받지 않고 데이터의 분포를 스케일링합니다.</li>
</ul>
</li>
<li><strong>언제 쓸까?</strong><ul>
<li><strong>데이터에 아웃라이어(이상치)가 많다고 판단될 때</strong> 가장 우선적으로 고려합니다.</li>
</ul>
</li>
</ul>
<pre><code class="language-python">from sklearn.preprocessing import RobustScaler

# 1. 스케일러 객체 생성
scaler_rs = RobustScaler()

# 2. 데이터에 맞게 &#39;학습(fit)&#39;하고 &#39;변환(transform)&#39;
X_scaled_rs = scaler_rs.fit_transform(X)

print(&quot;RobustScaler 변환 결과:\n&quot;, X_scaled_rs)</code></pre>
<p>정리</p>
<table>
<thead>
<tr>
<th>스케일러</th>
<th>기준 통계량</th>
<th>이상치(Outlier) 영향</th>
<th>주요 사용처</th>
</tr>
</thead>
<tbody><tr>
<td>StandardScaler</td>
<td>평균(0), 표준편차(1)</td>
<td>민감함</td>
<td>일반적인 모델, 정규분포형 데이터</td>
</tr>
<tr>
<td>MinMaxScaler</td>
<td>최소~최대 → [0,1]</td>
<td>매우 민감</td>
<td>이미지, 명확한 범위</td>
</tr>
<tr>
<td>RobustScaler</td>
<td>중앙값, IQR</td>
<td>강건함</td>
<td>이상치 많은 데이터</td>
</tr>
</tbody></table>
<p><strong>2. 예측의 두 갈래: 회귀 vs. 분류</strong></p>
<p>머신러닝(지도학습)은 크게 &#39;회귀&#39;와 &#39;분류&#39;로 나뉩니다.</p>
<ul>
<li><strong>회귀 (Regression):</strong> 연속적인 값(숫자)을 예측하는 문제입니다.
 ◦ 예: 내일의 기온(25.5℃), 3년 뒤 집값(5억 3천만 원), 주가 예측</li>
<li><strong>분류 (Classification / 식별):</strong> <strong>정해진 카테고리(범주)</strong> 중 하나로 예측하는 문제입니다.<ul>
<li>예: 이 메일은 스팸인가/아닌가? (이진 분류), 이 사진은 개인가/고양이인가/기타인가? (다중 분류)</li>
</ul>
</li>
</ul>
<p><strong>3. 주요 회귀(Regression) 모델</strong></p>
<p><strong>1) 선형 회귀 (Linear Regression)</strong></p>
<ul>
<li><strong>개념:</strong> 가장 기본적이고 단순한 회귀 모델입니다. 데이터 간의 <strong>직선 관계</strong>($y = wx + b$)를 찾아내어 값을 예측합니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/0c09fbec-23c8-493d-808a-22f2dde2a824/image.png" alt=""></p>
<ul>
<li><strong>언제 쓸까?</strong><ul>
<li>데이터가 선형적인 관계(x가 증가할 때 y도 증가/감소)를 보일 때.</li>
<li>모델의 <strong>해석</strong>이 매우 중요할 때 (어떤 피처가 결과에 얼마나 영향을 주는지 명확히 보임).</li>
</ul>
</li>
</ul>
<pre><code class="language-python">from sklearn.linear_model import LinearRegression

model_lr = LinearRegression()

model_lr.fit(X_train, y_train)</code></pre>
<p><strong>2) 로지스틱 회귀 (Logistic Regression)</strong></p>
<ul>
<li><strong>개념:</strong> <strong>이름은 &#39;회귀&#39;지만, 실제로는 &#39;분류&#39; 모델입니다.</strong><ul>
<li>선형 회귀처럼 직선을 찾지만, 그 결과를 <strong>시그모이드(Sigmoid) 함수</strong>에 통과시켜 0과 1 사이의 &#39;확률&#39; 값으로 변환합니다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>💡 보통 0.5를 기준으로 0.5보다 크면 1 (True), 작으면 0 (False)으로 분류합니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/e5315cdf-28d8-42f5-87e4-e42f80b01d91/image.png" alt=""></p>
<ul>
<li><strong>언제 쓸까?</strong><ul>
<li><strong>이진 분류(Binary Classification)</strong> 문제(예: 합격/불합격, 생존/사망)의 기본 모델로 널리 쓰입니다.</li>
<li>성능도 준수하고, 선형 회귀처럼 해석력이 좋습니다.</li>
</ul>
</li>
</ul>
<pre><code class="language-python">from sklearn.linear_model import LogisticRegression

model_lr = LogisticRegression()

model_lr.fit(X_train, y_train)</code></pre>
<p><strong>1) DecisionTree (결정 트리)</strong></p>
<ul>
<li><p><strong>개념:</strong> 데이터를 &#39;스무고개&#39; 하듯이 <strong>&#39;If-Then&#39; 규칙</strong>으로 쪼개나가며 예측하는 모델입니다.</p>
</li>
<li><p><strong>특징:</strong></p>
<ul>
<li><strong>장점:</strong> 모델의 예측 과정을 눈으로 쉽게 확인할 수 있어 <strong>해석력(Explainability)이 매우 높습니다.</strong></li>
<li><strong>단점:</strong> 트리를 너무 깊게 만들면 <strong>과적합(Overfitting)</strong>되기 매우 쉽습니다. (데이터의 사소한 노이즈까지 다 학습해버림)</li>
</ul>
</li>
</ul>
<pre><code class="language-python">from sklearn.tree import DecisionTreeClassifier

model_dt = DecisionTreeClassifier(max_depth=10, random_state=42)

model_dt.fit(X_train_scaled, y_train)</code></pre>
<p><strong>2) RandomForest (랜덤 포레스트)</strong></p>
<ul>
<li><strong>개념:</strong> &#39;과적합되기 쉽다&#39;는 결정 트리의 단점을 보완한 모델입니다. <strong>앙상블(Ensemble)</strong> 기법 중 <strong>배깅(Bagging)</strong>을 사용합니다.<ul>
<li><strong>작동 방식:</strong><ol>
<li>데이터를 무작위로 샘플링하여(중복 허용) 여러 개의 작은 데이터셋을 만듭니다.</li>
<li>각 데이터셋으로 <strong>여러 개의 결정 트리</strong>를 만듭니다. (이때 피처도 무작위로 선택)</li>
<li>최종 예측 시, 이 모든 트리의 결과를 <strong>&#39;다수결 투표&#39;(Voting)</strong>하여 결정합니다. (회귀는 &#39;평균&#39;)</li>
</ol>
</li>
</ul>
</li>
<li><strong>언제 쓸까?</strong><ul>
<li>결정 트리보다 훨씬 성능이 좋고 과적합에도 강합니다.</li>
<li>특별한 튜닝 없이도 준수한 성능을 내는 <strong>범용적인 모델</strong>로 쓰기 좋습니다.</li>
</ul>
</li>
</ul>
<pre><code class="language-python">from sklearn.ensemble import RandomForestClassifier

model_rf = RandomForestClassifier(n_estimators=100, random_state=42)

model_rf.fit(X_train_scaled, y_train)</code></pre>
<p><strong>3) XGBoost (eXtreme Gradient Boosting)</strong></p>
<ul>
<li><strong>개념:</strong> 캐글(Kaggle) 같은 데이터 경진대회를 휩쓸었던 고성능 모델입니다. 앙상블 기법 중 부스팅(Boosting)을 사용합니다.</li>
<li><strong>작동 방식 (vs. 랜덤 포레스트):</strong><ul>
<li>랜덤 포레스트는 여러 트리를 &#39;동시에&#39; 만들고 투표합니다. (병렬적)</li>
<li>부스팅은 트리를 &#39;순차적으로&#39; 만듭니다.<ol>
<li>첫 번째 트리를 만듭니다.</li>
<li>첫 번째 트리가 <strong>틀린 문제(오차)</strong>에 대해 <strong>가중치</strong>를 줍니다.</li>
<li>두 번째 트리는 이 <strong>틀린 문제를 더 잘 맞히는 방향</strong>으로 학습합니다.</li>
<li>이 과정을 반복하며 점점 모델을 강력하게 &#39;부스팅&#39;합니다.</li>
</ol>
</li>
</ul>
</li>
<li><strong>언제 쓸까?</strong><ul>
<li><strong>높은 예측 정확도</strong>가 필요할 때. (특히 정형 데이터에서 최고 수준의 성능을 보임)</li>
<li>병렬 처리를 지원하여 학습 속도가 빠릅니다.</li>
</ul>
</li>
</ul>
<pre><code class="language-python">from xgboost import XGBClassifier

model_xgb = XGBClassifier(n_estimators=5, random_state=42)

model_xgb.fit(X_train_scaled, y_train)</code></pre>
<p><strong>4) LightGBM (Light Gradient Boosting Machine)</strong></p>
<ul>
<li><strong>개념:</strong> XGBoost와 마찬가지로 <strong>부스팅(Boosting)</strong> 계열의 모델입니다. (MS에서 개발)</li>
<li><strong>특징 (vs. XGBoost):</strong><ul>
<li>XGBoost보다 <strong>학습 속도가 더 빠르고 메모리 사용량이 적습니다.</strong></li>
<li>트리를 성장시키는 방식(Leaf-wise)이 달라서 속도 이점을 가집니다.</li>
</ul>
</li>
<li><strong>언제 쓸까?</strong><ul>
<li><strong>대용량 데이터</strong>를 다룰 때 XGBoost보다 유리합니다.</li>
<li>빠른 학습 속도와 높은 정확도를 동시에 잡고 싶을 때.</li>
</ul>
</li>
</ul>
<pre><code class="language-python">from lightgbm import LGBMClassifier

model_lgbm = LGBMClassifier(n_estimators=3, random_state=42)

model_lgbm.fit(X_train_scaled, y_train)</code></pre>
<p>이 패턴은 제가 아까 설명드린 <strong>모든 트리 기반 모델에 동일하게 적용</strong>됩니다:</p>
<table>
<thead>
<tr>
<th><strong>분류 (Classification)</strong></th>
<th><strong>회귀 (Regression)</strong></th>
</tr>
</thead>
<tbody><tr>
<td><code>DecisionTreeClassifier</code></td>
<td><code>DecisionTreeRegressor</code></td>
</tr>
<tr>
<td><code>RandomForestClassifier</code></td>
<td><code>RandomForestRegressor</code></td>
</tr>
<tr>
<td><code>XGBClassifier</code></td>
<td><code>XGBRegressor</code></td>
</tr>
<tr>
<td><code>LGBMClassifier</code></td>
<td><code>LGBMRegressor</code></td>
</tr>
</tbody></table>
<h3 id="트리-기반-모델-계보"><strong>트리 기반 모델 계보</strong></h3>
<blockquote>
<p>트리 모델 구조 요약</p>
</blockquote>
<pre><code>DecisionTree  →  RandomForest  →       XGBoost       →      LightGBM
  (단일 트리)      (다수 트리 병렬)     (부스팅, 순차적 개선)     (부스팅 + 속도최적화)</code></pre><p><strong>마치며</strong></p>
<p>AICE 시험 준비에 이 정리 내용이 도움이 되길 바랍니다!</p>
<ul>
<li><strong>스케일러:</strong> 아웃라이어 유무(Robust)와 데이터 범위(MinMax)를 기준으로 선택! (기본은 Standard)</li>
<li><strong>회귀/분류:</strong> 연속된 값이면 &#39;회귀&#39;, 카테고리면 &#39;분류&#39;! (단, 로지스틱 회귀는 분류 모델!)</li>
<li><strong>트리 모델:</strong><ul>
<li>해석이 필요하면 <code>DecisionTree</code>.</li>
<li>범용적으로 쓰기 좋으면 <code>RandomForest</code>.</li>
<li>최고 성능과 속도가 필요하면 <code>XGBoost</code> 또는 <code>LightGBM</code>.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] 📅 Kotlinx.datetime을 활용한 PhotoCalendar 오픈소스 기여하기]]></title>
            <link>https://velog.io/@roel_dev/Android-Kotlinx.datetime%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-Photo-Calendar-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%EA%B8%B0%EC%97%AC</link>
            <guid>https://velog.io/@roel_dev/Android-Kotlinx.datetime%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-Photo-Calendar-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%EA%B8%B0%EC%97%AC</guid>
            <pubDate>Sun, 16 Mar 2025 19:31:56 GMT</pubDate>
            <description><![CDATA[<h1 id="🔍-프로젝트-개요">🔍 프로젝트 개요</h1>
<p>PhotoCalendar는 Kotlin 기반의 오픈소스 캘린더 라이브러리로, 날짜 및 일정 관리를 위한 다양한 기능을 제공한다. 기존 코드베이스는 java.util.time을 사용하고 있었지만, 특정 버전에서는 호환되지 않는 문제가 발생했기에 kotlinx.datetime으로 마이그레이션하는 작업을 진행했다.</p>
<h1 id="🛠-해결-방안">🛠 해결 방안</h1>
<p>기존 java.util.time 를 kotlinx.datetime으로 대체하기 위해 주요 변경 사항은 다음과 같다.</p>
<h2 id="1️⃣-localdatenow-대체">1️⃣ LocalDate.now() 대체</h2>
<p>java.util.time의 LocalDate.now()를 대체하기 위해 Clock.System.now()와 TimeZone.currentSystemDefault()를 활용하여 현재 날짜를 가져오는 확장 함수를 구현했다.</p>
<pre><code class="language-kotlin">fun LocalDate.Companion.now(): LocalDate {
    return Clock.System.now()
        .toLocalDateTime(TimeZone.currentSystemDefault())
        .date
}</code></pre>
<h2 id="2️⃣-yearmonth-대체-커스텀-dateyearmonth-클래스-구현">2️⃣ YearMonth 대체: 커스텀 DateYearMonth 클래스 구현</h2>
<p>기존 java.util.time.YearMonth를 kotlinx.datetime에서 제공하지 않기 때문에 <strong>커스텀 데이터 클래스 DateYearMonth</strong>를 생성하여 연-월을 다룰 수 있도록 했다.</p>
<pre><code class="language-kotlin">data class DateYearMonth(val year: Int, val month: Int)</code></pre>
<p>이를 통해 YearMonth와 동일한 기능을 제공하며, 연-월 연산을 수행할 수 있도록 확장했다.</p>
<h2 id="3️⃣-확장-함수-구현">3️⃣ 확장 함수 구현</h2>
<p>java.util.time에서 제공하던 주요 기능들을 유지하기 위해 확장 함수를 활용하여 대체했다.</p>
<p>예를 들어, 특정 요일을 기준으로 날짜를 계산하는 previousOrSame(target: DayOfWeek) 함수와, DateYearMonth 객체에서 월을 더하는 plusMonths(months: Int) 함수를 직접 구현했다.</p>
<pre><code class="language-kotlin">internal fun LocalDate.previousOrSame(target: DayOfWeek): LocalDate { ... }
internal fun DateYearMonth.plusMonths(months: Int): DateYearMonth { ... }</code></pre>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/1c293f71-cd90-4eae-a0b5-d7d1b1466c99/image.png" alt=""></p>
<blockquote>
</blockquote>
<p><a href="https://github.com/DongChyeon/PhotoCalendar/pull/4">해당 PR 링크</a></p>
<h1 id="🎯-적용-결과">🎯 적용 결과</h1>
<p>kotlinx.datetime을 사용하여 Kotlin 멀티플랫폼 지원 강화
java.util.time 종속성을 제거하고, 보다 가볍고 효율적인 날짜 처리 가능
기존 기능을 유지하면서, Kotlin 스타일에 맞는 API로 개선</p>
<h1 id="🔗-마무리">🔗 마무리</h1>
<p>앞으로도 Kotlin 생태계에 맞춰 개선을 진행할 계획이며, PhotoCalendar 오픈소스에 기여할 수 있어 뜻깊은 경험이었다.</p>
<h1 id="참고">참고</h1>
<blockquote>
</blockquote>
<p><a href="https://github.com/DongChyeon/PhotoCalendar">PhotoCalendar Repo</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[😵‍💫Preview 때문에 코드가 너무 길어요! (PreviewParameter)]]></title>
            <link>https://velog.io/@roel_dev/Preview-%EB%95%8C%EB%AC%B8%EC%97%90-%EC%BD%94%EB%93%9C%EA%B0%80-%EB%84%88%EB%AC%B4-%EA%B8%B8%EC%96%B4%EC%9A%94-PreviewParameter</link>
            <guid>https://velog.io/@roel_dev/Preview-%EB%95%8C%EB%AC%B8%EC%97%90-%EC%BD%94%EB%93%9C%EA%B0%80-%EB%84%88%EB%AC%B4-%EA%B8%B8%EC%96%B4%EC%9A%94-PreviewParameter</guid>
            <pubDate>Sun, 02 Feb 2025 18:37:04 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요! 오늘은 Compose를 사용하면서 Preview 코드가 너무 길어져 고민했던 경험과 이를 개선한 방법에 대해 공유하려고 합니다.</p>
<h2 id="preview가-길어진-이유">Preview가 길어진 이유</h2>
<p>버튼 컴포넌트를 만들고, 피그마 디자인에 있는 모든 버튼 스타일을 Preview로 확인할 수 있도록 코드를 작성하다 보니 Preview 코드가 점점 길어지기 시작했습니다.</p>
<p>PR을 올릴 때는 리뷰어분들께 <strong>&quot;버튼 컴포넌트에 Preview가 많아 코드가 길 수 있다&quot;</strong> 는 메시지를 남겨야 할 정도였죠.</p>
<p>하지만 코드 리뷰 중 한 리뷰어분께서 <code>PreviewParameter</code>라는 유용한 기능을 알려주셨고, 이를 활용해 코드 길이를 획기적으로 줄일 수 있었습니다.</p>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/28b1f969-805f-4496-93bf-c5794a541d25/image.png" alt=""></p>
<h2 id="previewparameter를-활용한-코드-간소화">PreviewParameter를 활용한 코드 간소화</h2>
<p>Compose의 <code>PreviewParameter</code>는 Preview를 위한 매개변수를 제공하는 기능입니다. 이를 활용하면 같은 컴포넌트를 다양한 상태로 Preview할 때 반복되는 코드를 줄일 수 있습니다.</p>
<blockquote>
<p><a href="https://developer.android.com/develop/ui/compose/tooling/previews?hl=ko#preview-data">컴포저블 미리보기로 UI 미리보기  |  Jetpack Compose  |  Android Developers</a></p>
</blockquote>
<h3 id="previewparameterprovider-정의">PreviewParameterProvider 정의</h3>
<p>먼저, Preview에 사용될 버튼 스타일을 정의하는 <code>PreviewParameterProvider</code>를 생성합니다.</p>
<p>여기서 각 버튼 스타일을 제공하는 <code>ButtonStyleProvider</code>를 작성했어요.</p>
<pre><code class="language-kotlin">private class ButtonStyleProvider : PreviewParameterProvider&lt;ButtonStyle&gt; {
    override val values: Sequence&lt;ButtonStyle&gt; = sequenceOf(
        ButtonStyle.Primary,
        ButtonStyle.Secondary,
        ButtonStyle.Tertiary
    )
}</code></pre>
<p>제가 작성한 예시 코드를 보며 설명할게요!</p>
<h3 id="1--previewparameterprovider-를-통한-buttonstyleprovider-클래스-정의">1.  PreviewParameterProvider 를 통한 ButtonStyleProvider <strong>클래스 정의</strong></h3>
<p>먼저, <code>ButtonStyleProvider</code>는 Preview에 전달할 값을 관리하는 클래스입니다. 이 클래스는 <code>PreviewParameterProvider&lt;T&gt;</code>를 상속받아 <code>values</code> 속성을 통해 Preview에서 사용할 매개변수의 목록을 제공합니다.</p>
<pre><code class="language-kotlin">private class ButtonStyleProvider : PreviewParameterProvider&lt;ButtonStyle&gt; {
    override val values: Sequence&lt;ButtonStyle&gt; = sequenceOf(
        ButtonStyle.Primary,
        ButtonStyle.Secondary,
        ButtonStyle.Tertiary
    )
}</code></pre>
<ul>
<li><strong><code>PreviewParameterProvider&lt;T&gt;</code></strong>: Preview에 전달할 데이터 타입 <code>T</code>를 명시합니다. 여기서는 <code>ButtonStyle</code> 타입을 사용합니다.</li>
<li><strong><code>values</code></strong>: <code>Sequence</code> 타입으로 Preview에서 사용할 여러 값을 정의합니다.<ul>
<li><code>sequenceOf()</code>를 사용해 필요한 버튼 스타일들을 나열했습니다.</li>
<li>여기서는 <code>ButtonStyle.Primary</code>, <code>ButtonStyle.Secondary</code>, <code>ButtonStyle.Tertiary</code> 세 가지 스타일을 제공합니다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="2-previewparameter-애노테이션-사용">2. <strong><code>PreviewParameter</code> 애노테이션 사용</strong></h3>
<p>이제 <code>ButtonStyleProvider</code>를 활용해 Preview 함수에서 다양한 스타일을 미리 볼 수 있습니다.</p>
<h3 id="기본-사용법">기본 사용법</h3>
<p><code>@PreviewParameter</code> 애노테이션을 사용해, Preview 함수의 매개변수로 데이터를 전달받을 수 있습니다.</p>
<pre><code class="language-kotlin">@Preview
@Composable
fun SpoonyButtonEnabledPreview(
    @PreviewParameter(ButtonStyleProvider::class) style: ButtonStyle
) {
    SpoonyAndroidTheme {
        Column(
            modifier = Modifier.fillMaxWidth(),
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            // 여기서 전달받은 style을 사용
            ButtonSize.entries.forEach { size -&gt;
                SpoonyButton(
                    text = &quot;버튼&quot;,
                    style = style, // 전달받은 style
                    size = size,   // 반복문으로 모든 크기 처리
                    onClick = { }
                )
            }
        }
    }
}</code></pre>
<h3 id="동작-방식">동작 방식</h3>
<ol>
<li><strong>매개변수 <code>style</code>에 값 전달</strong>:<ul>
<li><code>@PreviewParameter(ButtonStyleProvider::class)</code>는 <code>ButtonStyleProvider</code>에서 정의한 <code>values</code>를 순회하면서 Preview를 생성합니다.</li>
<li><code>values</code>에서 제공된 각 <code>ButtonStyle</code>(<code>Primary</code>, <code>Secondary</code>, <code>Tertiary</code>)에 대해 별도의 Preview 화면이 렌더링됩니다.</li>
</ul>
</li>
<li><strong>Preview 생성</strong>:<ul>
<li>Compose는 각 <code>ButtonStyle</code>에 대해 한 번씩 Preview를 생성하여, 동일한 UI를 다양한 스타일로 확인할 수 있도록 합니다.</li>
</ul>
</li>
</ol>
<h3 id="preview의-결과">Preview의 결과</h3>
<p>위 코드를 사용하면 버튼 스타일(<code>Primary</code>, <code>Secondary</code>, <code>Tertiary</code>) 각각에 대해 Preview 화면이 따로 생성됩니다. 결과적으로, 스타일별 Preview를 작성할 때 중복된 코드를 작성할 필요가 없어집니다.</p>
<h3 id="코드-변경-전">코드 변경 전</h3>
<p>기존에는 버튼 스타일과 크기별로 Preview를 모두 작성했기 때문에 코드가 아래처럼 길어졌습니다.</p>
<pre><code class="language-kotlin">@Preview
@Composable
private fun SpoonyButtonPrimaryEnabledPreview() {
    SpoonyAndroidTheme {
        Column(
            modifier = Modifier.fillMaxWidth(),
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            SpoonyButton(
                text = &quot;버튼&quot;,
                onClick = {},
                style = ButtonStyle.Primary,
                size = ButtonSize.Xlarge
            )
            SpoonyButton(
                text = &quot;버튼&quot;,
                onClick = {},
                style = ButtonStyle.Primary,
                size = ButtonSize.Large
            )
            SpoonyButton(
                text = &quot;버튼&quot;,
                onClick = {},
                style = ButtonStyle.Primary,
                size = ButtonSize.Medium
            )
            SpoonyButton(
                text = &quot;버튼&quot;,
                onClick = {},
                style = ButtonStyle.Primary,
                size = ButtonSize.Small
            )
            SpoonyButton(
                text = &quot;버튼&quot;,
                onClick = {},
                style = ButtonStyle.Primary,
                size = ButtonSize.Xsmall
            )
        }
    }
}

@Preview
@Composable
private fun SpoonyButtonPrimaryDisabledPreview() {
    SpoonyAndroidTheme {
        Column(
            modifier = Modifier.fillMaxWidth(),
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            SpoonyButton(
                text = &quot;버튼&quot;,
                style = ButtonStyle.Primary,
                size = ButtonSize.Xlarge,
                enabled = false
            )
            SpoonyButton(
                text = &quot;버튼&quot;,
                style = ButtonStyle.Primary,
                size = ButtonSize.Large,
                enabled = false
            )
            SpoonyButton(
                text = &quot;버튼&quot;,
                style = ButtonStyle.Primary,
                size = ButtonSize.Medium,
                enabled = false
            )
            SpoonyButton(
                text = &quot;버튼&quot;,
                style = ButtonStyle.Primary,
                size = ButtonSize.Small,
                enabled = false
            )
            SpoonyButton(
                text = &quot;버튼&quot;,
                style = ButtonStyle.Primary,
                size = ButtonSize.Xsmall,
                enabled = false
            )
        }
    }
}
...</code></pre>
<h3 id="코드-변경-후">코드 변경 후</h3>
<p><code>PreviewParameter</code>를 사용해 스타일을 매개변수로 전달받도록 개선했습니다.</p>
<p>버튼 크기별로도 반복문을 활용해 코드의 중복을 줄였어요.</p>
<pre><code class="language-kotlin">@Preview
@Composable
fun SpoonyButtonEnabledPreview(
    @PreviewParameter(ButtonStyleProvider::class) style: ButtonStyle
) {
    SpoonyAndroidTheme {
        Column(
            modifier = Modifier,
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            ButtonSize.entries.forEach { size -&gt;
                SpoonyButton(
                    text = &quot;버튼&quot;,
                    style = style,
                    size = size,
                    onClick = { }
                )
            }
        }
    }
}</code></pre>
<h3 id="코드--커밋-후-코드-줄-수-변화">코드  커밋 후 코드 줄 수 변화</h3>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/c6f5982a-a335-4722-a293-bb6b24ba3269/image.png" alt=""></p>
<p>위와 같이 코드를 수정하니 Preview가 훨씬 간결해졌습니다. 버튼 스타일과 크기별 Preview를 하나의 함수로 처리할 수 있어 유지보수도 편리해졌고, 코드 가독성도 크게 향상되었죠.</p>
<p>저는 해당 리뷰를 통해 <code>PreviewParameter</code>라는 기능을 알게 된 것도 큰 수확이었습니다.</p>
<p>Preview가 너무 길어져 고민 중이라면, 저처럼 <code>PreviewParameter</code>를 활용해보는 건 어떨까요? 코드가 훨씬 깔끔해질 거예요! 😊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[안드로이드] Android 지도앱으로 길찾기 연동하기(NaverMap) 트러블 슈팅]]></title>
            <link>https://velog.io/@roel_dev/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Android-%EC%A7%80%EB%8F%84%EC%95%B1%EC%9C%BC%EB%A1%9C-%EA%B8%B8%EC%B0%BE%EA%B8%B0-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0NaverMap</link>
            <guid>https://velog.io/@roel_dev/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Android-%EC%A7%80%EB%8F%84%EC%95%B1%EC%9C%BC%EB%A1%9C-%EA%B8%B8%EC%B0%BE%EA%B8%B0-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0NaverMap</guid>
            <pubDate>Tue, 07 Jan 2025 18:10:48 GMT</pubDate>
            <description><![CDATA[<p>아래 내용은 네이버 지도 앱으로 특정 위치(좌표)를 표시하려는 기능을 구현하는 과정에서 발생한 문제와, 이를 해결하는 과정을 정리한 글입니다.</p>
<h1 id="문제-상황">문제 상황</h1>
<p>기존 코드는 다음과 같은 방식으로, 기기에 런처 아이콘이 있는 앱들을 전부 조회(queryIntentActivities)한 뒤,
결과 목록이 비어 있으면 “네이버 지도 설치 필요”로 판단하여 마켓으로 이동하고,
그렇지 않으면 “네이버 지도 실행”을 시도했습니다.</p>
<pre><code class="language-kotlin">val installCheck = if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.TIRAMISU) {
    context.packageManager.queryIntentActivities(
        Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER),
        PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
    )
} else {
    context.packageManager.queryIntentActivities(
        Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER),
        PackageManager.GET_META_DATA
    )
}

if (installCheck.isEmpty()) {
    // 네이버 지도 없다고 판단 → 플레이스토어
} else {
    // 네이버 지도 있다고 판단 → 인텐트 실행
}</code></pre>
<p>문제는 실제로 네이버 지도 앱이 삭제되었는데도 installCheck 결과가 비어 있지 않은 경우가 발생한다는 점이었습니다.
예를 들어, 런처에 존재하는 다른 앱들(카메라, 갤러리 등)이 목록에 남아서 “목록이 비어 있지 않다”고 판단되어,
실제로는 네이버 지도가 없는데도 네이버 지도 인텐트를 실행하려고 시도하면서 앱이 강제 종료되는 이슈가 생겼습니다.</p>
<h1 id="해결-방법">해결 방법</h1>
<p>문제를 근본적으로 해결하기 위해, <strong>특정 패키지(네이버 지도)</strong>가 존재하는지를 직접 확인하도록 코드를 수정했습니다.</p>
<p>안드로이드에서는 특정 패키지가 설치되어 있는지 판단하려면 PackageManager.getPackageInfo (또는 안드로이드 13 이상의 getPackageInfo(packageName, PackageManager.PackageInfoFlags))를 사용하는 것이 가장 간단하다고하여 사용했습니다.</p>
<p>수정된 코드</p>
<pre><code class="language-kotlin">private fun openPlaceNaverMap(
    latitude: Double,
    longitude: Double,
    placeName: String,
    context: Context
) {
    val url = &quot;nmap://place?lat=$latitude&amp;lng=$longitude&amp;name=$placeName&amp;appname=${context.packageName}&quot;
    val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
        addCategory(Intent.CATEGORY_BROWSABLE)
    }

    // 1) &quot;com.nhn.android.nmap&quot; 패키지 존재 여부 확인
    val isInstalled = try {
        if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.TIRAMISU) {
            context.packageManager.getPackageInfo(
                &quot;com.nhn.android.nmap&quot;,
                PackageManager.PackageInfoFlags.of(0)
            )
        } else {
            @Suppress(&quot;DEPRECATION&quot;)
            context.packageManager.getPackageInfo(&quot;com.nhn.android.nmap&quot;, 0)
        }
        true
    } catch (e: PackageManager.NameNotFoundException) {
        false
    }

    // 2) 설치되어 있지 않다면 → Play 스토어로 이동
    if (!isInstalled) {
        val marketIntent = Intent(Intent.ACTION_VIEW).apply {
            data = Uri.parse(&quot;market://details?id=com.nhn.android.nmap&quot;)
        }
        context.startActivity(marketIntent)
    } else {
        // 3) 설치되어 있다면 → 네이버 지도 앱 실행
        context.startActivity(intent)
    }
}</code></pre>
<p>try 구문 안에서 getPackageInfo(&quot;com.nhn.android.nmap&quot;, 0)를 호출했을 때 NameNotFoundException이 발생하지 않으면(= <strong>앱이 존재</strong>) true, 예외가 발생하면(= <strong>앱이 설치되지 않음</strong>) false를 반환합니다.
그 결과에 따라 Play 스토어로 이동할지, 네이버 지도 앱을 실행할지를 나눠 처리할 수 있습니다.</p>
<h3 id="핵심-흐름">핵심 흐름</h3>
<p><strong>네이버 지도 스킴(nmap://place)</strong>으로 장소 정보를 담은 Intent를 준비한다.
<strong>getPackageInfo</strong>로 <strong>네이버 지도 패키지(com.nhn.android.nmap)</strong>의 설치 여부를 판단한다.
설치되어 있다면 → 해당 Intent를 통해 네이버 지도 앱을 실행한다.
설치되어 있지 않다면 → Play 스토어(또는 마켓)으로 이동하여 앱 설치를 유도한다.</p>
<h1 id="요약">요약</h1>
<h3 id="문제-원인">문제 원인</h3>
<p>queryIntentActivities는 런처 아이콘이 있는 모든 앱을 가져오는 로직이므로, 앱이 “삭제”되었음에도 다른 앱들로 인해 리스트가 비어있지 않아 오탐 발생.</p>
<h3 id="해결-방법-1">해결 방법</h3>
<p>특정 패키지명(com.nhn.android.nmap)을 직접 getPackageInfo로 조회하여, 앱 설치 여부를 정확히 판단.</p>
<h3 id="핵심-포인트">핵심 포인트</h3>
<p>“런처 아이콘 조회” 대신 “패키지명 조회” 를 통해, 네이버 지도 존재 여부를 명확히 체크해야 한다.
이렇게 코드를 바꾸면, 실제로 네이버 지도 앱이 삭제된 경우에는 즉시 예외가 발생해 “앱이 없다”고 올바르게 판단하며, 강제 종료 없이 플레이스토어로 안전하게 이동할 수 있습니다.</p>
<p>이 방법은 네이버 지도뿐 아니라 특정 패키지가 설치되어 있는지 확인할 때도 동일하게 사용할 수 있어,
스킴 호출 전에 반드시 앱 설치 여부를 체크해야 하는 로직에 유용합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] Hilt @Binds와 @Provides]]></title>
            <link>https://velog.io/@roel_dev/Android-Hilt-Binds%EC%99%80-Provides</link>
            <guid>https://velog.io/@roel_dev/Android-Hilt-Binds%EC%99%80-Provides</guid>
            <pubDate>Mon, 18 Nov 2024 10:03:01 GMT</pubDate>
            <description><![CDATA[<p><strong>Hilt</strong> 라이브러리를 이용해 의존성을 주입하는 경우에 <strong>Module</strong>에서 </p>
<p><strong>abstract class</strong>에는 <strong>@Binds</strong> 어노테이션이, </p>
<p><strong>object</strong>에는 <strong>@Provides</strong> 키워드가 붙게 되는데 어떤 이유에서인지 알아보겠습니다.</p>
<p>작성한 예시 코드를 보여드리며 설명하겠습니다.</p>
<h1 id="1-datamodule-binds-사용">1. <strong>DataModule (@Binds 사용)</strong></h1>
<pre><code class="language-java">@Module
@InstallIn(SingletonComponent::class)
abstract class DataModule {

    @Binds
    abstract fun bindsDataStoreDataSource(
        dataStoreDataSource: DataStoreDataSourceImpl
    ): DataStoreDataSource

    @Binds
    abstract fun bindsDataStoreRepository(
        dataStoreRepository: DataStoreRepositoryImpl
    ): DataStoreRepository

    @Binds
    abstract fun bindsUserDataSource(
        authDataSource: UserDataSourceImpl
    ): UserDataSource

    @Binds
    abstract fun bindsUserRepository(
        authRepository: UserRepositoryImpl
    ): UserRepository
    ...
}</code></pre>
<h3 id="설명"><strong>설명</strong></h3>
<ul>
<li><code>@Binds</code>는 <strong>인터페이스와 구현체 간의 관계를 바인딩</strong>하기 위해 사용됩니다.</li>
<li>위 코드에서 <code>DataStoreDataSource</code>, <code>DataStoreRepository</code>, <code>UserDataSource</code>, <code>UserRepository</code>는 <strong>인터페이스</strong>, 그리고 <code>DataStoreDataSourceImpl</code>, <code>DataStoreRepositoryImpl</code>, <code>UserDataSourceImpl</code>, <code>UserRepositoryImpl</code>는 <strong>구현체</strong>입니다.</li>
<li>추상 메서드를 통해, <code>Hilt</code>는 자동으로 인터페이스의 구현체를 의존성 주입에 사용할 수 있도록 설정합니다.</li>
</ul>
<h3 id="특징"><strong>특징</strong></h3>
<ol>
<li><strong>단순한 인터페이스-구현체 바인딩</strong>:<ul>
<li>인터페이스와 구현체만 연결하면 되므로 객체 생성 로직이 필요하지 않습니다.</li>
<li>Hilt가 직접 생성자를 호출해 필요한 객체를 생성합니다.</li>
</ul>
</li>
<li><strong>효율적</strong>:<ul>
<li>구현체의 기본 생성자가 있거나, 생성자 주입(@Inject)을 이미 정의한 경우 사용할 수 있습니다.</li>
</ul>
</li>
<li><strong>추상 클래스만 허용</strong>:<ul>
<li><code>@Binds</code>를 사용하는 모듈은 반드시 <code>abstract</code> 키워드를 사용해야 합니다.</li>
</ul>
</li>
</ol>
<h3 id="장점"><strong>장점</strong></h3>
<ul>
<li>코드가 간결하며, 불필요한 생성 로직이 없습니다.</li>
<li>성능적으로 더 효율적입니다.</li>
</ul>
<h1 id="2-networkmodule-provides-사용">2. <strong>NetworkModule (@Provides 사용)</strong></h1>
<pre><code class="language-java">@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun providesOkHttpClient(
        @ApplicationContext context: Context,
        tokenProvider: () -&gt; String
    ): OkHttpClient {
        val builder = OkHttpClient.Builder()
            .addInterceptor(AuthInterceptor(tokenProvider))
            .addNetworkInterceptor(
                HttpLoggingInterceptor().apply {
                    if (BuildConfig.DEBUG) {
                        level = HttpLoggingInterceptor.Level.BODY
                    }
                }
            )

        if (BuildConfig.DEBUG) {
            builder.addNetworkInterceptor(ChuckerInterceptor(context))
        }

        return builder.build()
    }

    @Provides
    @Singleton
    fun providesRetrofit(okHttpClient: OkHttpClient): Retrofit =
        Retrofit.Builder()
            .client(okHttpClient)
            .baseUrl(BuildConfig.BASE_URL)
            .addCallAdapterFactory(ApiResultCallAdapterFactory())
            .addConverterFactory(GsonConverterFactory.create())
            .build()

    @Provides
    @Singleton
    fun providesUserService(retrofit: Retrofit): UserService =
        retrofit.create(UserService::class.java)
    ...
}
</code></pre>
<h3 id="설명-1"><strong>설명</strong></h3>
<ul>
<li><code>@Provides</code>는 복잡한 로직을 포함한 객체를 생성하거나, <strong>외부 라이브러리에서 제공하는 객체</strong>를 의존성으로 주입해야 할 때 사용됩니다.</li>
<li>예를 들어, <code>OkHttpClient</code>, <code>Retrofit</code>, 그리고 <code>UserService</code> 는 간단한 생성자 호출만으로 생성되지 않으며, 추가 설정이나 생성 과정이 필요합니다.</li>
</ul>
<h3 id="특징-1"><strong>특징</strong></h3>
<ol>
<li><strong>구체적인 생성 로직 포함 가능</strong>:<ul>
<li><code>OkHttpClient</code>의 경우, <code>Interceptor</code>, <code>Logging</code> 등을 추가로 설정하며 객체를 생성합니다.</li>
<li><code>Retrofit</code>은 <code>OkHttpClient</code>와 함께 특정 설정을 추가해 객체를 만듭니다.</li>
</ul>
</li>
<li><strong>외부 라이브러리와 연동</strong>:<ul>
<li>Retrofit, OkHttp 등 외부 라이브러리 객체를 의존성 주입에 사용할 때 활용됩니다.</li>
</ul>
</li>
<li><strong>구현된 메서드 사용</strong>:<ul>
<li>반드시 객체를 반환하는 구체적인 메서드로 작성되어야 합니다.</li>
</ul>
</li>
</ol>
<h3 id="장점-1"><strong>장점</strong></h3>
<ul>
<li>복잡한 객체 생성 과정이 포함된 경우 적합합니다.</li>
<li>외부 의존성이나 동적으로 값을 가져와야 하는 경우에 유연합니다.</li>
</ul>
<h1 id="3-두-예제의-차이점-비교">3. <strong>두 예제의 차이점 비교</strong></h1>
<table>
<thead>
<tr>
<th><strong>특징</strong></th>
<th><strong>DataModule (@Binds)</strong></th>
<th><strong>NetworkModule (@Provides)</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>사용 목적</strong></td>
<td>인터페이스와 구현체 연결</td>
<td>복잡한 로직을 포함한 객체 생성</td>
</tr>
<tr>
<td><strong>메서드 타입</strong></td>
<td>추상 메서드 (abstract method)</td>
<td>구체적 메서드 (concrete method)</td>
</tr>
<tr>
<td><strong>코드 구조</strong></td>
<td>단순하며, 구현체와 인터페이스를 연결하는 데 적합</td>
<td>복잡한 로직이나 외부 라이브러리 객체 생성을 지원</td>
</tr>
<tr>
<td><strong>객체 생성 방식</strong></td>
<td>Hilt가 생성자를 호출해 자동으로 생성</td>
<td>메서드 내에서 객체 생성 로직을 직접 작성</td>
</tr>
<tr>
<td><strong>성능 및 효율성</strong></td>
<td>더 효율적 (추가 로직이 없음)</td>
<td>약간의 오버헤드가 발생</td>
</tr>
<tr>
<td><strong>사용 사례</strong></td>
<td>Repository, DataSource 같은 단순 구현체와 인터페이스의 연결</td>
<td>OkHttpClient, Retrofit, 외부 라이브러리 객체 생성</td>
</tr>
</tbody></table>
<h1 id="4-언제-어떤-것을-사용할까">4. <strong>언제 어떤 것을 사용할까?</strong></h1>
<ul>
<li><strong>@Binds</strong>:<ul>
<li>좀 더 정확히 말하면 <strong>인터페이스는 생성자를 정의할 수 없기 때문에 DataStoreRepository를 상속받는 구현체(DataStoreRepositoryImpl)를 Hilt에게 제공합니다.</strong> (<strong>Hilt</strong> 에게 <strong>DataStoreRepository</strong> 만 알려줘도 <strong>DataStoreRepositoryImpl</strong> 에서 override한 메소드를 쓸 수 있는 이유는 Module에서 정의되고 있었기 때문입니다.)</li>
<li>예: <code>Repository</code>, <code>DataSource</code> 같은 내부 객체 간의 바인딩.</li>
</ul>
</li>
<li><strong>@Provides</strong>:<ul>
<li><strong>@Provides</strong>는 Hilt에 의존성을 주입하려는 인스턴스 클래스가 <strong>외부 라이브러리(Retrofit, OkHttp, Room, DataStore 등)를 사용하는 경우 혹은 빌더 패턴으로 인스턴스를 생성하는 경우</strong>에 사용합니다.</li>
<li>예: <code>Retrofit</code>, <code>OkHttpClient</code> 같은 네트워크 관련 객체 생성.</li>
</ul>
</li>
</ul>
<h1 id="참고">참고</h1>
<blockquote>
</blockquote>
<p><a href="https://develop-oj.tistory.com/69">https://develop-oj.tistory.com/69</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SOPT] 35기 ANDROID 서류/면접 합격 후기]]></title>
            <link>https://velog.io/@roel_dev/SOPT-35%EA%B8%B0-ANDROID-%EC%84%9C%EB%A5%98%EB%A9%B4%EC%A0%91-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@roel_dev/SOPT-35%EA%B8%B0-ANDROID-%EC%84%9C%EB%A5%98%EB%A9%B4%EC%A0%91-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Mon, 11 Nov 2024 11:20:53 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/roel_dev/post/be21c8d1-0aaa-44bb-b449-aca4dc745d7b/image.png" alt=""></p>
<p>이제 4학년 1학기.. 마지막으로 연합동아리를 할 수 있는 기회.. 공고가 올라오자마자 바로 넣어야겠다 생각했다.</p>
<p>여러 연합동아리가 있었지만 SOPT라는 동아리가 유명하다는 것도 주변 사람들을 통해 알고 있었고 졸업 프로젝트를 SOPT OB분과 같이 했었는데 잘하셔서 꼭 한번 활동 해보고 싶었다.</p>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/b6779a6c-632e-41a8-9105-208cc17b011a/image.png" alt=""></p>
<p>서류를 지원하려고 보니 일단 내용이 빡셌다. 여러 문항의 글자수를 합치면 총 5600자에 + 포트폴리오 or Github 였다 😮.. 
그리고 나는 웹 개발을 하다가 최근 안드로이드로 기술 스택을 변경했다.
이번 솝트에도 안드로이드로 지원하는 것이었기 때문에 질문에 대한 답변에 어려움이 있었다.</p>
<p>그래도 핵심 가치를 알려줬기 때문에 이를 잘 연결지어서 열심히 작성했다.
<img src="https://velog.velcdn.com/images/roel_dev/post/21a3b4dc-b96c-459b-a0b2-c97356987e18/image.png" alt=""></p>
<p>아무튼 서류 붙음!
<img src="https://velog.velcdn.com/images/roel_dev/post/5b01b89e-46d5-424a-be21-a3656d7929a4/image.png" alt=""></p>
<p>근데 면접 날짜가 얼마 남지 않았기 때문에 빠르게 준비했다. 공통 질문, 예시 질문 등을 면접 후기 블로그나 주변 솝트 경험이 있는분들에게 조언을 구해가며 준비했다. 이것도 파트별 커리큘럼도 참고해서 면접 질문 준비를 했다.
<img src="https://velog.velcdn.com/images/roel_dev/post/568c517e-0c77-412a-bda6-e7fd47dd9ea0/image.png" alt=""></p>
<p>아래와 같이 노션으로 대부분의 질문과 답변을 정리해서 준비를 했었다..
<img src="https://velog.velcdn.com/images/roel_dev/post/5964b8c7-c178-40e6-850c-df07b4170b37/image.png" alt=""></p>
<p>면접은 건대에서 진행되었는데 11시에 주변에 가서 준비하다가 5시쯤 면접 시작이었다.
면접 전 솝트 분들과 아이스브레이킹이라고 해서 긴장을 푸는 시간을 갖고, 회장단 분들과 공통적인 면접을 진행하고 그 다음으로 파트장분과 기술쪽? 면접을 진행하는 방식으로 면접이 이루어졌다.</p>
<p>먼저 회장단 부터 들어가자마자 자기소개를 시키고 회장단에서는 지원서를 바탕으로 면접 질문을 해주셨다.</p>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/eff72074-8866-42a2-a766-b5060947c588/image.png" alt=""></p>
<p>끝나자마자 노션에 정리한 내용인데 일단 회장단 질문은 이정도였던거같다. 3명에서 봤는데 공통질문으로는 자기소개 마지막으로 할말이었고 중간 질문은 이력서의 기반으로 한명씩 질문하고 답변하는 방식이었다.</p>
<p>그 다음으로 파트장 분과 면접이었는데 이건 1대1로 이루어졌다.
<img src="https://velog.velcdn.com/images/roel_dev/post/25ef88eb-40ee-4711-903b-c08dafa4787b/image.png" alt=""></p>
<p>웹쪽 프로젝트가 많았고 안드로이드 경험이 많지 않았기 때문에 기술적인 질문은 안드로이드 4대 컴포넌트에 대한 질문이 많았다. 내 생각엔 파트장과 회장단 면접 모두 협업에 대해 어떤 생각을 갖고 있는지 궁금해하는 질문들이 많았던것 같다.</p>
<p>면접에 대한 답변은 대부분 <strong>핵심가치 ( 용기 몰입 화합 )</strong> 을 강조하며 프로젝트 협업 경험을 통해 이러한 상황에서는 ~식으로 행동할 것 같다라고 답변을 했다.</p>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/2bd16046-69c0-4e6a-a39c-e3201aa24d68/image.jpeg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/2c4be063-6613-4380-8bd8-c42ed97b572e/image.jpeg" alt=""></p>
<p>합격 결과를 받았을 때 너무 좋았다. 😆</p>
<p>이번 서류 작성과 면접 준비를 하면서 내가 해왔던 활동들을 정리하고 글로 써보는 경험을 하면서 부족한 점도 느끼고 그 동안 했던 활동들을 통해 많이 성장한 것을 느꼈다.</p>
<p>세미나도 기대되고 스터디도 기대된다. 사실 4주차 때 후기를 작성하는거라 지금 스터디도 8개 들어가져 있다. 스터디랑 세미나 잘 마무리 지어야겠다.</p>
<p>AND SOPT 화이팅 !!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] 멀티모듈이란?]]></title>
            <link>https://velog.io/@roel_dev/Android-%EB%A9%80%ED%8B%B0%EB%AA%A8%EB%93%88%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@roel_dev/Android-%EB%A9%80%ED%8B%B0%EB%AA%A8%EB%93%88%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Mon, 11 Nov 2024 10:10:24 GMT</pubDate>
            <description><![CDATA[<h1 id="멀티모듈">멀티모듈</h1>
<h2 id="멀티-모듈-구조의-기본-개념"><strong>멀티 모듈 구조의 기본 개념</strong></h2>
<p>멀티 모듈 구조란 <strong>하나의 안드로이드 앱 프로젝트를 여러 개의 모듈로 나누어 개발하는 구조에요</strong>. 각 모듈은 <strong>독립적</strong>으로 기능을 수행하며, 필요에 따라 서로 간에 데이터나 기능을 공유할 수 있어요.</p>
<p>일반적으로 멀티 모듈 구조는 도메인 모듈, 데이터 모듈, 프레젠테이션 모듈 등으로 나누어 개발해요. 도메인 모듈은 앱의 비즈니스 로직을 담당하며, 데이터 모듈은 데이터 관리와 네트워크 통신을 담당해요. 프레젠테이션 모듈은 사용자 인터페이스(UI)와 사용자 경험(UX)을 담당해요.</p>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/888b158d-c0dd-449c-bacd-ac68a6aea8d6/image.png" alt=""></p>
<h2 id="장점">장점</h2>
<h3 id="1-관심사의-분리">1. 관심사의 분리</h3>
<p>멀티 모듈 아키텍처에서 각 모듈은 <strong>독립적</strong>인 기능 단위로 구성돼요. 이러한 분리는 <strong>코드의 가독성</strong>을 향상시키고, <strong>유지 보수를 용이</strong>하게 해요. 예를 들어, 도메인 모듈, 데이터 모듈, 프레젠테이션 모듈 등등 각각의 기능을 별도의 모듈로 분리함으로써, 개발자는 특정 기능에 집중할 수 있고, 모듈 간의 상호 <strong>의존성을 최소화</strong>할 수 있어요.</p>
<h3 id="2-빌드-속도의-향상">2. 빌드 속도의 향상</h3>
<p>멀티 모듈 구조에서는 변경이 발생한 모듈만 재빌드 하면 되기 때문에, 전체 프로젝트의 빌드 시간이 크게 줄어들어요. 이는 큰 규모의 프로젝트나, 빠른 이터레이션을 필요로 하는 개발 환경에서 특히 유용해요.</p>
<h3 id="3-낮은-결합도와-높은-응집력">3. 낮은 결합도와 높은 응집력</h3>
<p>각 모듈이 고유의 기능만을 담당하므로, 모듈 간 <strong>결합도는 낮아</strong>지고, <strong>모듈 내 응집력은 높아져요</strong>. 이는 소프트웨어 GRASP 원칙 중 <strong>낮은 결합도(Low Coupling)</strong>와 <strong>높은 응집력(High Cohesion)</strong>의 원칙에 부합해요. 결과적으로, 각 모듈은 독립적으로 기능을 수행할 수 있으며, 시스템 전체의 <strong>안정성과 확장성</strong>이 향상돼요.</p>
<h3 id="4-협업의-효율성-증대">4. 협업의 효율성 증대</h3>
<p>멀티 모듈 구조에서는 여러 개발자가 동시에 다른 모듈에서 작업할 수 있어요. 이는 코드 베이스의 충돌을 최소화하고, 팀 간의 <strong>협업을 용이</strong>하게 만들어요. 각 팀은 자신의 모듈에만 집중할 수 있으므로, 전체 프로젝트의 진행에 있어 <strong>효율성</strong>이 증가해요.</p>
<h3 id="5-dynamic-feature-모듈의-활용">5. Dynamic Feature 모듈의 활용</h3>
<p><strong>Dynamic Feature 모듈</strong>을 사용하면, 사용자가 실제로 필요로 할 때만 특정 기능을 다운로드할 수 있어요. 이는 앱의 초기 다운로드 크기를 줄이는 데 큰 도움이 되며, 필요에 따라 기능을 추가하거나 제거할 수 있는 유연성을 제공해요. 직접 사용해보진 않았지만 이러한 기능이 있다고 하네요..!</p>
<h2 id="단점">단점</h2>
<h3 id="1-초기-환경-구축에-많은-시간-소요">1. 초기 환경 구축에 많은 시간 소요</h3>
<p>멀티 모듈 아키텍처를 처음 설정할 때는 각 모듈의 역할과 경계를 정의하고, 모듈 간의 의존성을 관리하는 구조를 설계하는 데 상당한 시간과 비용이 들어갈 수 있어요. </p>
<p>특히, 대규모 프로젝트나 복잡한 시스템에서는 이 과정이 더욱 복잡하고 시간이 많이 걸릴 수 있어요.</p>
<h3 id="2-모듈을-무분별하게-생성하면-오히려-복잡해질-수-있음">2. 모듈을 무분별하게 생성하면 오히려 복잡해질 수 있음</h3>
<p>멀티 모듈 아키텍처의 또 다른 주요 단점은 모듈을 너무 많이 또는 무분별하게 생성할 경우 시스템이 오히려 더 복잡해질 수 있다는 점이에요. </p>
<p>모듈을 너무 세분화하면, 각 모듈 간의 인터페이스 관리와 조정이 복잡해지며(유지보수성이 오히려 안좋아짐), 전체 시스템의 통합과 테스트 과정이 더 어려워져요.</p>
<h2 id="참고">참고</h2>
<blockquote>
</blockquote>
<p><a href="https://developer.android.com/guide/navigation/integrations/multi-module?hl=ko">https://developer.android.com/guide/navigation/integrations/multi-module?hl=ko</a>
<a href="https://velog.io/@paulus0617/Android-Refactoring-Multi-Modularizing">https://velog.io/@paulus0617/Android-Refactoring-Multi-Modularizing</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] DI, Hilt]]></title>
            <link>https://velog.io/@roel_dev/Android-DI-Hilt</link>
            <guid>https://velog.io/@roel_dev/Android-DI-Hilt</guid>
            <pubDate>Mon, 04 Nov 2024 09:29:11 GMT</pubDate>
            <description><![CDATA[<p>저는 이번 블로그 포스팅은 그 동안의 피드백 부분을 가지고 다시 한번 정리하면서 기록해볼까 합니다. </p>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/56160dd7-b7c5-45d8-99ba-f32795265d4b/image.png" alt=""></p>
<p>사실 이해가 잘 되지 않았습니다.. (그래서 공부하면서 정리하겠다고 했습니다..)</p>
<h2 id="그렇다면-의존성-주입이란-무엇일까">그렇다면 의존성 주입이란 무엇일까..?</h2>
<p>한 클래스는 종종 다른 클래스를 참조합니다. 예를 들면, <code>Car</code>라는 클래스는 <code>Engine</code>이라는 클래스를 참조하고 있습니다. 이렇게 클래스를 필요로하는 것을 의존성(dependency)이라고 합니다. (저도 이해할 수 있는) 좋은 예제가 있어서 들고 와봤습니다!</p>
<pre><code class="language-kotlin">class Car {
    private val engine = Engine()
    fun start() {
        engine.start()
    }
}
fun main(args: Array) {
    val car = Car()
    car.start()
}</code></pre>
<p>위처럼 코드를 작성하면 <code>Car</code> 와 <code>Engine</code> 의 결합이 강해지고 재사용성이 떨어지게 됩니다. 아래는 의존성 주입을 이용해서 수정한 코드입니다.</p>
<pre><code class="language-kotlin">class Car(private val engine: Engine) {
    fun start() {
        engine.start()
    }
}
fun main(args: Array) {
    val engine = Engine()
    val car = Car(engine)
    car.start()
}</code></pre>
<p>위 코드는 <code>Engine</code> 객체를 <code>main</code> 에서 생성하고 <code>Car</code> 생성자에게 전달하게 됩니다.</p>
<p>이를 통해 <strong>재사용성</strong>이 좋아지고 <strong>테스트 작성이 용이</strong>해집니다.</p>
<p>간단하게 의존성 주입에 대해서 이해했으니</p>
<h2 id="여기서-hilt-란-무엇인가">여기서 Hilt 란 무엇인가?</h2>
<p>Hilt는 Android 앱 개발에 사용되는 의존성 주입(Dependency Injection, DI) 라이브러리입니다. 의존성 주입을 통해 객체 간의 결합도를 낮추고, 코드의 재사용성 및 유지보수성을 향상시킬 수 있습니다. Hilt는 Google에서 제공하며, Dagger를 기반으로 하고 있어서 Dagger의 강력한 기능을 좀 더 쉽게 사용할 수 있도록 도와줍니다.</p>
<h3 id="주요-특징">주요 특징</h3>
<ol>
<li><strong>자동화된 의존성 관리</strong>: Hilt는 컴파일 타임에 의존성을 처리하여, 실행 시간에 발생할 수 있는 오류를 최소화합니다.</li>
<li><strong>표준화된 설정</strong>: Hilt를 사용하면 Android 애플리케이션의 표준적인 구조에 맞게 의존성을 설정할 수 있으며, 이는 앱 전체에서 일관된 방식으로 의존성을 관리할 수 있게 해줍니다.</li>
<li><strong>쉬운 통합</strong>: Hilt는 Android Jetpack과 긴밀하게 통합되어 있어서, 생명주기나 다른 Jetpack 기능들과의 호환성을 자연스럽게 제공합니다.</li>
</ol>
<h3 id="사용-방법">사용 방법</h3>
<p>Hilt를 사용하기 위해서는 먼저 의존성을 Gradle 파일에 추가하고, 애플리케이션 클래스에 <code>@HiltAndroidApp</code> 어노테이션을 적용해야 합니다. 이 어노테이션은 Hilt의 의존성 주입 기능을 앱 전체에 적용합니다. 그 다음으로, 필요한 곳에 <code>@Inject</code> 어노테이션을 사용하여 의존성을 주입받을 수 있습니다.</p>
<p>예를 들어, 액티비티, 프래그먼트, 뷰모델, 서비스 등에서 필요한 객체를 Hilt가 자동으로 주입해주도록 설정할 수 있습니다. 이 과정에서 개발자는 의존성을 생성하고 관리하는 복잡한 작업에서 벗어날 수 있어, 더욱 집중해서 애플리케이션의 비즈니스 로직을 개발할 수 있습니다.</p>
<h2 id="종속-항목-추가">종속 항목 추가</h2>
<p>build.gradle.kts(Module.app)</p>
<pre><code class="language-java">plugins {
    ...
    alias(libs.plugins.kotlin.kapt)
    alias(libs.plugins.hilt)
}

android {
  ...
}

dependencies {
  // Hilt 의존성
  implementation(libs.hilt)
  kapt(libs.hiltCompiler)
}
kapt {
  correctErrorTypes = true
}</code></pre>
<p>libs.versions.toml</p>
<pre><code class="language-java">[versions]
...
hilt = &quot;2.48&quot;
...

[plugins]
...
kotlin-kapt = { id = &quot;org.jetbrains.kotlin.kapt&quot; }
hilt = { id = &quot;com.google.dagger.hilt.android&quot;, version = &quot;2.48&quot; }</code></pre>
<h2 id="hilt-애플리케이션-클래스">Hilt 애플리케이션 클래스</h2>
<p>AndSopt.kt</p>
<pre><code class="language-kotlin">@HiltAndroidApp
class AndSopt : Application()</code></pre>
<h2 id="android-클래스에-종속-항목-삽입">Android 클래스에 종속 항목 삽입</h2>
<p>MainActivity.kt</p>
<pre><code class="language-kotlin">@AndroidEntryPoint
class MainActivity : ComponentActivity() { ... }</code></pre>
<h2 id="예시-사용">예시 사용</h2>
<p>PreferencesManager.kt</p>
<pre><code class="language-kotlin">class PreferencesManager @Inject constructor(@ApplicationContext context: Context) {
    private val sharedPreferences: SharedPreferences =</code></pre>
<p>SignUpViewModel.kt</p>
<pre><code class="language-kotlin">@HiltViewModel
class SignUpViewModel @Inject constructor(
    private val userManager: UserManager,
    @ApplicationContext private val context: Context
) : ViewModel() { 
        ...
        private fun validateEmail() {
        isEmailValid = userIdInput.isNotEmpty() &amp;&amp; isValidEmail(userIdInput)
        signUpEmailDescription = when {
            userIdInput.isEmpty() -&gt; context.getString(R.string.sign_up_email_default)
            userIdInput.length &lt; 5 -&gt; context.getString(R.string.sign_up_email_error1)
            !isEmailValid -&gt; context.getString(R.string.sign_up_email_error2)
            else -&gt; context.getString(R.string.sign_up_email_default)
        }
    } 
    ...
}</code></pre>
<p>SignUpScreen.kt</p>
<pre><code class="language-kotlin">@Composable
fun SignUpScreen(
    onNavigateToSignIn: () -&gt; Unit,
    modifier: Modifier,
    viewModel: SignUpViewModel = hiltViewModel()
) { ... }</code></pre>
<p>위의 SignUpViewModel 에서는 <code>@Inject</code> 생성자를 통해 <code>UserManager</code> 인스턴스와 애플리케이션 컨텍스트<code>Context</code>가 자동으로 주입됩니다. 이를 통해 <code>SignUpViewModel</code> 내에서는 <code>userManager</code>와 <code>context</code>를 직접 생성하거나 초기화할 필요 없이 바로 사용할 수 있습니다.</p>
<p>결론..</p>
<p>Hilt를 사용하여 <code>PreferencesManager</code> 클래스에서 <code>@ApplicationContext</code> 어노테이션을 통해 Context 를 주입받는 방법은 추가적인 모듈 설정 없이도 애플리케이션 전반의 Context를 자동으로 제공받을 수 있기 때문에 효율적입니다. 이로 인해 <code>PreferencesManager</code>와 같은 클래스에서 Context를 관리하기 위해 별도의 모듈을 제공할 필요가 없습니다. 이는 코드의 간결성을 높이며, 의존성 관리를 더욱 명확하게 해줍니다.</p>
<p>출처</p>
<blockquote>
</blockquote>
<p><a href="https://developer.android.com/training/dependency-injection/hilt-android">https://developer.android.com/training/dependency-injection/hilt-android</a>
<a href="https://developer.android.com/codelabs/android-hilt">https://developer.android.com/codelabs/android-hilt</a>
<a href="https://lovestudycom.tistory.com/entry/Android-Hilt%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85">https://lovestudycom.tistory.com/entry/Android-Hilt를-이용한-의존성-주입</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NoSQL과 MySQL]]></title>
            <link>https://velog.io/@roel_dev/NoSQL%EA%B3%BC-MySQL</link>
            <guid>https://velog.io/@roel_dev/NoSQL%EA%B3%BC-MySQL</guid>
            <pubDate>Mon, 28 Oct 2024 11:45:27 GMT</pubDate>
            <description><![CDATA[<p>여러분, 데이터베이스 선택이 얼마나 중요한지 아시나요? 현대 애플리케이션에서 데이터베이스는 그야말로 중심 역할을 합니다. 올바른 데이터베이스를 선택하는 것은 프로젝트의 성공을 결정짓는 중요한 요소 중 하나입니다. 저는 이번 글에서 대표적인 관계형 데이터베이스인 MySQL과 비관계형 데이터베이스인 MongoDB를 비교해 보려고 합니다. 각 데이터베이스의 특징을 살펴보고, 제가 직접 겪었던 경험도 공유해 보겠습니다.</p>
<h3 id="mysql과-mongodb의-주요-차이점">MySQL과 MongoDB의 주요 차이점</h3>
<h3 id="데이터-구조">데이터 구조</h3>
<p>먼저, MySQL과 MongoDB의 가장 큰 차이점은 데이터 구조입니다.</p>
<ul>
<li><strong>MySQL</strong>은 관계형 데이터베이스로, 데이터를 테이블 형태로 저장합니다. 스키마가 엄격하며, 데이터 무결성을 보장하기 위해 제약 조건이 많습니다. 각 테이블은 서로 관계를 맺고, 이 관계를 통해 데이터를 관리하죠.</li>
<li><strong>MongoDB</strong>는 비관계형 데이터베이스로, 데이터를 JSON과 유사한 문서(document) 형식으로 저장합니다. 이 방식 덕분에 스키마가 유연하여 데이터 구조를 쉽게 수정할 수 있습니다. 말하자면, 변화에 빠르게 대응할 수 있는 장점이 있습니다.</li>
</ul>
<h3 id="데이터-모델링">데이터 모델링</h3>
<p>데이터를 어떻게 모델링하는지도 두 데이터베이스 간의 큰 차이 중 하나입니다.</p>
<ul>
<li><strong>MySQL</strong>에서는 데이터를 여러 테이블로 나누고, 각 테이블 간의 관계를 정의합니다. 이를 통해 데이터 중복을 최소화하고 무결성을 유지합니다.</li>
<li><strong>MongoDB</strong>는 데이터를 중복 저장하는 방식으로, 하나의 문서에 관련된 데이터를 함께 저장하여 읽기 성능을 최적화합니다. 이 덕분에 특정 데이터를 조회할 때 여러 테이블을 참조할 필요가 없습니다.</li>
</ul>
<h3 id="join-기능">JOIN 기능</h3>
<p>이 부분에서 많은 차이가 있는데요, 바로 <strong>JOIN</strong> 기능입니다.</p>
<ul>
<li><strong>MySQL</strong>에서는 여러 테이블을 연결할 때 <code>JOIN</code> 기능을 사용합니다. 예를 들어, 사용자의 주문 정보와 해당 사용자의 정보를 한 번에 가져올 수 있죠.</li>
<li><strong>MongoDB</strong>는 기본적으로 <code>JOIN</code> 연산을 지원하지 않지만, <code>aggregation</code> 파이프라인의 <code>$lookup</code>을 사용하면 제한적으로 조인 기능을 구현할 수 있습니다. 하지만 이는 MySQL의 <code>JOIN</code>만큼 최적화되어 있지는 않아서 대량의 데이터를 다루는 경우 성능 문제가 발생할 수 있습니다.</li>
</ul>
<h3 id="nosql-mongodb를-선택하게-된-이유">NoSQL, MongoDB를 선택하게 된 이유</h3>
<p>저희 팀이 MongoDB를 선택하게 된 이유는 두 가지였습니다.</p>
<ul>
<li>첫째, <strong>유연한 데이터 구조</strong>였습니다. 변화하는 요구사항에 맞게 데이터 스키마를 쉽게 수정할 수 있었기 때문에, 빠르게 개발해야 하는 상황에서 큰 도움이 되었습니다.</li>
<li>둘째, <strong>수평적 확장성</strong> 덕분이었습니다. 데이터가 급격히 늘어나는 프로젝트였기 때문에, 여러 서버로 데이터를 쉽게 분산시킬 수 있는 MongoDB의 장점이 크게 작용했습니다.</li>
</ul>
<p>그렇지만 많은 데이터를 참조해야되는 인사관리 프로그래밍 특성상 맞지 않았고 이로 인해 직접적으로 문제와 그때 당시 해결했던 방법에 대해서 작성해볼까 합니다.</p>
<p>MongoDB를 사용하면서 가장 고생했던 순간 중 하나는 <strong>JOIN 기능의 부재</strong>였습니다. </p>
<p>당시 제가 맡았던 작업은 한 달 동안의 근무 기록을 가져와서 각 조직 내 근로자들의 하루 근무 데이터를 바탕으로 <strong>연장근무, 야간근무, 휴일근무</strong>를 계산해 보여주는 기능을 구현하는 것이었어요. 문제는 MongoDB에서 <code>JOIN</code>이 없기 때문에 각 데이터를 일일이 접근해야 했다는 점이었죠.</p>
<p>예를 들어, 한 조직에 대해서 <strong>user_id + company_id</strong>를 사용해서 <strong>userCompanyRecord(인사정보)</strong>를 가져오고, 다시 <strong>company_id 와 userCompanyRecord_id</strong>를 가지고 근태 기록을 가져와야 했죠. 이걸 매달 30일치(대략 한 달의 일수) 데이터를 조회하고, 이를 통해 연장근무, 야간근무, 휴일근무를 하나씩 계산해야 했습니다. 게다가 가장큰 조직은 회사 전체 조직으로 <strong>약 1000명</strong>의 근로자에 대한 데이터를 다루면서 각 인원의 근무 정보를 그래프로 시각화하는 기능도 포함되어 있었습니다.</p>
<p>이렇게 많은 데이터를 반복적으로 조회하다 보니, 개발 서버에서 테스트할 때 리소스 사용량이 급격히 증가했고, 속도도 매우 느려졌습니다. 결국 대량의 데이터를 실시간으로 처리하기에는 한계가 있었습니다. 외주 회사에서는 실시간성이 필요하지 않다고 했기 때문에, 이를 빠르게 해결하기 위해 자정에 하루 단위로 데이터를 미리 계산해 저장하고, 이를 조회하는 데이터 캐싱 방식으로 변경했습니다.</p>
<p>이 경험을 통해 대량의 데이터를 다루는 상황에서는 관계형 데이터베이스의 <code>JOIN</code>이 얼마나 효율적인지 다시 한 번 깨닫게 되었고, MongoDB를 사용할 때는 처음부터 데이터 조회 구조를 잘 설계하는 것이 얼마나 중요한지를 절실히 느꼈습니다.</p>
<p>추가적으로 그 당시에는 <code>$lookup</code> 기능을 사용하진 않았습니다. 아마 사용했으면 조금 더 좋아질 수는 있었겠지만 <code>JOIN</code> 과 완벽하게 동일하진 않기 때문에 여전히 문제가 지속될 가능성이 있습니다. (다음엔 기회가 된다면 <code>$lookup</code> 을 사용해 봐야겠습니다!)</p>
<p>이 부분에 대해서는 다음 기회에 더 자세히 다뤄보려 합니다!</p>
<blockquote>
</blockquote>
<p><a href="https://dev.mysql.com/doc/refman/8.0/en/join.html">MySQL JOIN 문서</a>
<a href="https://www.mongodb.com/ko-kr/docs/manual/reference/operator/aggregation/lookup/">MongoDB $lookup 문서</a></p>
<h3 id="mysql과-mongodb의-적합한-사용-사례">MySQL과 MongoDB의 적합한 사용 사례</h3>
<p>그렇다면 MySQL과 MongoDB는 어떤 경우에 적합할까요?</p>
<ul>
<li><strong>MySQL</strong>은 <strong>데이터 무결성</strong>이 중요하고, <strong>복잡한 관계</strong>를 관리해야 하는 금융 시스템이나 전자 상거래 시스템에 적합합니다.</li>
<li>반면, <strong>MongoDB</strong>는 <strong>유연한 데이터 구조</strong>가 필요하고, 빠른 개발 및 수평적 확장이 중요한 소셜 미디어 플랫폼이나 로그 데이터 저장에 적합합니다.</li>
</ul>
<h3 id="결론">결론</h3>
<p>결국, <strong>적절한 데이터베이스를 선택하는 것이 중요</strong>합니다. MongoDB와 MySQL은 각기 다른 장점과 단점을 가지고 있으며, 프로젝트의 요구사항에 따라 알맞은 데이터베이스를 선택하는 것이 성공의 열쇠입니다. 저 역시 이번 경험을 통해 JOIN 없이도 효율적으로 데이터를 다루기 위해 데이터 모델링 방식에 대해 깊이 고민하는 것이 얼마나 중요한지 깨달았습니다.</p>
<h3 id="마무리-질문">마무리 질문</h3>
<p>여러분은 어떠신가요? MongoDB나 MySQL을 사용하면서 겪었던 어려움이나 배운 점이 있다면 댓글로 공유해 주세요! 여러분의 경험을 통해 서로 배울 수 있기를 기대합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SOPT] Week1 - 필수과제 (Android Compose)]]></title>
            <link>https://velog.io/@roel_dev/SOPT-Week1Android-Compose</link>
            <guid>https://velog.io/@roel_dev/SOPT-Week1Android-Compose</guid>
            <pubDate>Tue, 08 Oct 2024 03:31:56 GMT</pubDate>
            <description><![CDATA[<p>SOPT 1주차 과제는 <strong>Wavve 클론 코딩</strong>이고, 3가지(필수과제, 심화과제, 도전과제)로 나누어져 있습니다.</p>
<p>그 중 가장 첫번째 <strong>필수과제</strong> 스터디 내용을 공유해보려고 합니다. </p>
<h2 id="work-description-✏️">Work Description ✏️</h2>
<ul>
<li>SignInActivity</li>
</ul>
<ol>
<li>Hide, Show 에 따라 비밀번호 visible 구현</li>
<li>putExtra로 MyActivity 에 이메일, 정보 넘겨줌</li>
<li>SharedPreference 사용해서 로그인 구현</li>
<li>로그인 실패하면 Dialog 띄우기</li>
<li>로그인 성공하면 Toast 메세지 사용</li>
<li>로그인 성공 시 SignInActivity finish 처리</li>
<li>회원가입 클릭시 SignUpActivity 로 이동</li>
</ol>
<ul>
<li>SignUpActivity</li>
</ul>
<ol>
<li>Hide, Show 에 따라 비밀번호 visible 구현</li>
<li>이메일, 비밀번호 정규식처리</li>
<li>이메일, 비밀번호 정규식이 올바를 때만 회원가입 하기 버튼 활성화</li>
<li>회원가입 성공시에 Toast 메세지 처리</li>
</ol>
<ul>
<li>MyActivity</li>
</ul>
<ol>
<li>getExtra 로 처리하거나 자동로그인 경우에는 SharedPreferences 사용하여 저장된 getUsername 값 가져오기
val email = intent.getStringExtra(&quot;email&quot;) ?: (preferencesManager.getUsername() ?: &quot;프로필1&quot;)</li>
</ol>
<ul>
<li>기타</li>
</ul>
<ol>
<li>utils/Validator.kt 사용해서 이메일, 비밀번호 정규식 따로 뺌</li>
<li>Topbar, TextField 컴포넌트화</li>
</ol>
<h2 id="screenshot-📸">Screenshot 📸</h2>
<h3 id="signupactivity">SignUpActivity</h3>
<div align="center">
  <img src="https://velog.velcdn.com/images/roel_dev/post/796991f6-72ef-4238-918c-48a2b44f1304/image.png" width="50%" height="n%">
  <img src="https://velog.velcdn.com/images/roel_dev/post/80a6540e-72d7-48aa-bf70-bbd561fa7ad1/image.png" width="50%" height="n%">
  <img src="https://velog.velcdn.com/images/roel_dev/post/f76a48b3-f14b-4085-87c2-5227aca83fcd/image.png" width="50%" height="n%">
  <img src="https://velog.velcdn.com/images/roel_dev/post/a5ce78fe-e2ea-4188-8e03-4404b44b6675/image.png" width="50%" height="n%">
  <img src="https://velog.velcdn.com/images/roel_dev/post/dd8cd22d-ec64-478c-ac22-9529d82241e3/image.png" width="50%" height="n%">
</div>
### SignInActivity

<div align="center">
  <img src="https://velog.velcdn.com/images/roel_dev/post/d2f307ca-9050-449b-ae7b-0f0b78872f85/image.png" width="50%" height="n%">
  <img src="https://velog.velcdn.com/images/roel_dev/post/89aada4b-9f70-4702-929c-61d1307ed55f/image.png" width="50%" height="n%">
  <img src="https://velog.velcdn.com/images/roel_dev/post/78e32a90-e473-483a-8dc8-b7c8a3cd9263/image.png" width="50%" height="n%">
</div>

<h3 id="myactivity">MyActivity</h3>
<div align="center">
  <img src="https://velog.velcdn.com/images/roel_dev/post/9695b697-f562-4b75-b0b4-06f10afe1c13/image.png" width="50%" height="n%">
</div>]]></description>
        </item>
        <item>
            <title><![CDATA[[SOPT] 솝보딩 “나야 Git”]]></title>
            <link>https://velog.io/@roel_dev/SOPT-%EC%86%9D%EB%B3%B4%EB%94%A9-%EB%82%98%EC%95%BC-Git</link>
            <guid>https://velog.io/@roel_dev/SOPT-%EC%86%9D%EB%B3%B4%EB%94%A9-%EB%82%98%EC%95%BC-Git</guid>
            <pubDate>Fri, 04 Oct 2024 18:04:38 GMT</pubDate>
            <description><![CDATA[<p>일단 먼저 SOPT 에서 협업을 하기 전에 git 과 github 에 대해서 간략하게 설명해주신다고 하셔서 듣게 되었습니다.</p>
<p>SOPT에서 배우고 싶은 부분이 깃과 깃허브 및 협업 방식이었기 때문에 달려갔습니다.</p>
<p>초기 내용을 요약하자면 Git 과 GitHub 에 대한 설명을 간단하게 해주셨습니다. 일단 <strong>분산형 버전 관리 시스템</strong> 이며 예시를 들어가며 중요한 점을 강조해 주셨습니다.</p>
<p>예시를 들어가며 <strong>Git과 Github 사용법</strong>에 대해서 설명해주셨습니다. 덕분에 더욱 쉽게 이해한것 같아요. </p>
<p>공부한 것들을 바탕으로 제 생각대로 정리했습니다.</p>
<ol>
<li><p><code>git init</code> 을 통해 Git 이 를 추적할 수 있도록 한다.<strong>(Untracked 상태)</strong></p>
</li>
<li><p>변화가 있을 시에 <code>git add &#39;파일명&#39;</code> 을 통해  <strong>Working Director 에 있던 것을 ⇒  Staging Area</strong> 로 보냅니다.<strong>(Tracked 상태)</strong></p>
</li>
<li><p><strong>Staging Area ⇒ Local Repository</strong> 로 보내기 위해 <code>git commit -m “[Feat] 어쩌고 UI 구현”</code> 명령어를 작성해줍니다! 만약 추적하고 있는 파일들에서 수정사항이 생긴다면 Modified 상태가 되고 아니면 Unmodified 상태가 됩니다. 
수정 사항 같은 경우에는 <code>git status</code> 로 확인하시면 됩니다.</p>
<blockquote>
<p>git commit -m 에서 -m 은 message 입니다.</p>
</blockquote>
</li>
<li><p>내 변경 사항들을 원격 저장소(origin or remote) 로 저장하기 위해서 <code>git push origin “브랜치명&quot;</code> 명령어를 작성해줍니다.</p>
</li>
<li><p>현재 브랜치에 있는 것에 다른 브랜치에서 개발한 것을 합치기 위해서는 <code>git merge</code> 를 사용하시면 됩니다.</p>
</li>
<li><p>현재 브랜치를 최신화하기 위해서는 <code>git fetch</code> 를 하시면 됩니다!</p>
</li>
</ol>
<p>이외에 다른 명령어들(rebase, branch, checkout...)도 많은데 나중에 다시 공부해서 정리하도록 할게요.</p>
<p>그리고 마지막에 Git 명령어 공부하기 좋은 사이트 추천해주셔서 풀어봤는데 재밌더라구요!</p>
<p>아래 링크는 Learn Git Branching 라는 사이트입니다.</p>
<blockquote>
<p><a href="https://learngitbranching.js.org/?locale=ko">https://learngitbranching.js.org/?locale=ko</a></p>
</blockquote>
<h2 id="후기">후기</h2>
<p>제가 Git 과 Github 에 대해서는 자세히 공부한 건 아니었기 때문에 간단한 예시를 들어가며 기본적인 부분부터 잡아주는 강의 방식이 너무 좋았어요.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] Greedy]]></title>
            <link>https://velog.io/@roel_dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Greedy</link>
            <guid>https://velog.io/@roel_dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Greedy</guid>
            <pubDate>Fri, 04 Oct 2024 10:31:29 GMT</pubDate>
            <description><![CDATA[<h2 id="그리디-알고리즘이란">그리디 알고리즘이란?</h2>
<p><strong>탐욕 알고리즘</strong> 또는 <strong>그리디 알고리즘</strong>(greedy algorithm)은 문제를 해결할 때 <strong>매 순간 가장 최적이라고 생각되는 선택</strong>을 하는 알고리즘 설계 기법입니다. 이러한 선택은 그 순간에 대해서는 <strong>지역적으로는 최적</strong>이지만, 그 선택들을 계속 수집하여 최종적(전역적)인 해답을 만들었다고 해서, 그것이 <strong>최적이라는 보장은 없습니다</strong>. 하지만 탐욕알고리즘을 적용할 수 있는 문제들은 지역적으로 최적이면서 전역적으로 최적인 문제들입니다.</p>
<p>그리디(greedy) 알고리즘은 말그대로 탐욕스럽게 미래를 생각하지 않고</p>
<p>현재에서 <strong>매 순간</strong> 최선의 선택을 하면서 답을 구하는 알고리즘입니다.</p>
<hr>
<h2 id="특징">특징</h2>
<ul>
<li><strong>단순성</strong>: 구현이 비교적 간단하며 이해하기 쉽습니다.</li>
<li><strong>빠른 실행 속도</strong>: 복잡한 연산이 필요하지 않아 실행 속도가 빠릅니다.</li>
<li><strong>최적해 보장 여부</strong>: 모든 문제에서 최적해를 보장하지는 않으므로 적용 가능 여부를 판단해야 합니다.</li>
</ul>
<hr>
<h2 id="💡-예시--거스름돈-문제">💡 예시 : 거스름돈 문제</h2>
<p><strong>문제 설명</strong>:</p>
<p>어떤 가게에서 거스름돈으로 줄 수 있는 동전의 종류는 500원, 100원, 50원, 10원입니다. 손님에게 거슬러줘야 할 금액이 N원일 때, 동전의 최소 개수를 구하는 프로그램을 작성하세요.</p>
<p><strong>그리디 알고리즘 적용 방법</strong>:</p>
<ol>
<li><strong>가장 큰 단위의 동전</strong>부터 사용합니다.</li>
<li>현재 동전으로 거슬러줄 수 있는 만큼 최대한 거슬러 줍니다.</li>
<li>남은 금액에 대해 다음으로 큰 단위의 동전으로 반복합니다.</li>
</ol>
<p><strong>예시 코드 (Python)</strong>:</p>
<pre><code class="language-python">def get_min_coin_count(n, coin_types):
    count = 0
    for coin in coin_types:
        count += n // coin  # 해당 동전으로 거슬러 줄 수 있는 개수
        n %= coin  # 거슬러 주고 남은 금액
    return count

n = 1260
coin_types = [500, 100, 50, 10]

print(get_min_coin_count(n, coin_types))  # 출력: 6</code></pre>
<p><strong>실행 결과 설명</strong>:</p>
<ul>
<li>500원 동전 2개: 1000원</li>
<li>100원 동전 2개: 200원</li>
<li>50원 동전 1개: 50원</li>
<li>10원 동전 1개: 10원</li>
<li><strong>총 동전 개수</strong>: 6개</li>
</ul>
<hr>
<h2 id="주의사항"><strong>주의사항</strong></h2>
<h3 id="그리디-알고리즘이-항상-최적해를-보장하지-않는-경우"><strong>그리디 알고리즘이 항상 최적해를 보장하지 않는 경우</strong></h3>
<p>동전 단위가 서로 배수 관계가 아닐 때는 그리디 알고리즘이 최적해를 보장하지 않습니다.</p>
<blockquote>
<p>최적해란? 주어진 문제에서 가장 최소 또는 최대 값을 구하는 것</p>
</blockquote>
<p><strong>예시 문제</strong>:</p>
<p>동전의 종류가 500원, 400원, 100원이고, 거스름돈이 800원이라면?</p>
<p><strong>그리디 알고리즘 적용 결과</strong>:</p>
<ul>
<li>500원 1개 사용 → 남은 금액 300원</li>
<li>100원 3개 사용 → 남은 금액 0원</li>
<li><strong>총 동전 개수</strong>: 4개</li>
</ul>
<p><strong>최적해</strong>:</p>
<ul>
<li>400원 동전 2개 사용 → 남은 금액 0원</li>
<li><strong>총 동전 개수</strong>: 2개</li>
</ul>
<p>그리디 알고리즘을 적용했을 때보다 동전 개수가 적으므로, 이 경우 그리디 알고리즘은 최적해를 구하지 못합니다.</p>
<hr>
<h2 id="그리디-알고리즘-적용-가능한-문제-유형">그리디 알고리즘 적용 가능한 문제 유형</h2>
<ul>
<li><strong>활동 선택 문제</strong>: 일정 시간에 가장 많은 활동을 선택하는 문제.</li>
<li><strong>최소 신장 트리</strong>: Kruskal 알고리즘, Prim 알고리즘.</li>
<li><strong>허프만 코딩</strong>: 데이터 압축에 사용하는 알고리즘.</li>
</ul>
<hr>
<h2 id="결론"><strong>결론</strong></h2>
<p>그리디 알고리즘은 매 순간 가장 좋아 보이는 선택을 하는 방법으로, 문제에 따라 매우 효율적이고 간단한 해결책을 제공합니다. 하지만 항상 최적해를 보장하지는 않으므로, 해당 알고리즘이 문제에 적합한지 판단하는 것이 중요합니다.</p>
<hr>
<h2 id="참고">참고</h2>
<blockquote>
</blockquote>
<p><a href="https://night-knight.tistory.com/entry/%EA%B7%B8%EB%A6%AC%EB%94%94Greedy-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%98%88%EC%8B%9C%EC%99%80-%EB%AC%B8%EC%A0%9C">https://night-knight.tistory.com/entry/그리디Greedy-알고리즘-예시와-문제</a>
<a href="https://ko.wikipedia.org/wiki/%ED%83%90%EC%9A%95_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">https://ko.wikipedia.org/wiki/탐욕_알고리즘</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] MVI 란?]]></title>
            <link>https://velog.io/@roel_dev/Android-MVI-%EB%9E%80</link>
            <guid>https://velog.io/@roel_dev/Android-MVI-%EB%9E%80</guid>
            <pubDate>Sun, 29 Sep 2024 16:09:49 GMT</pubDate>
            <description><![CDATA[<h1 id="디자인-패턴design-pattern">디자인 패턴(Design Pattern)</h1>
<p><strong>객체 지향 프로그래밍</strong> 설계 시 자주 발생하는 문제를 해결하기 위해 디자인 패턴을 사용합니다. 특히 여러 사람이 협업하는 개발 환경에서, 다른 사람이 작성한 코드나 기존 코드를 이해하고 수정하는 것은 매우 어려운 작업입니다. 코드를 변경하거나 새로은 기능을 추가할 때 의도하지 않은 결과나 버그를 발생할 수 있으며, 성능 최적화도 까다롭습니다. 이러한 문제를 방지하고 개발 시간 및 비용을 절약하기 위해 디자인 패턴을 활용합니다. 디자인 패턴은 공통 문제에 대한 검증된 해결책을 제공함으로써, 코드의 <strong>가독성</strong>과 <strong>재사용</strong>성을 높이고, 팀 내의 기술적 의사소통을 원활하게 합니다.</p>
<blockquote>
</blockquote>
<p>MVC, MVP, MVVM 패턴
<a href="https://velog.io/@roel_dev/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4MVC-MVP-MVVM-%ED%8C%A8%ED%84%B4">https://velog.io/@roel_dev/디자인-패턴MVC-MVP-MVVM-패턴</a></p>
<p><strong>MVC, MVP, MVVM</strong> 같은 패턴들은 관심사의 분리를 통해 테스트 코드의 작성을 용이하게 해주는 등, 대규모 개발에 있어서 필수 불가결한 요소가 되었습니다.</p>
<p><strong>MVC 패턴</strong>의 View와 Model 사이의 <strong>높은 결합도</strong>는 <strong>MVP, MVVM 패턴을 파생</strong>시켰고, <strong>데이터와 뷰를 분리함으로써 얻는 장점</strong>으로 인해 현재는 많은 프로젝트에 MVP, MVVM 패턴을 사용하고 있습니다. 패턴의 View와 Model 사이의 <strong>높은 결합도</strong>는 MVP, MVVM 패턴을 파생시켰고, 데이터와 뷰를 분리함으로써 얻는 장점으로 인해 현재는 많은 프로젝트에 <strong>MVP, MVVM 패턴</strong>을 사용하고 있습니다.</p>
<p><strong>하지만</strong>, 이들도 마냥 장점만 있는 것은 아니었습니다.</p>
<p>MVP, MVVM 패턴을 적용하면서 직면하는 <strong>문제</strong>로 <strong>두 가지</strong>가 발생하였습니다</p>
<h3 id="상태-문제">상태 문제</h3>
<ul>
<li>안드로이드는 어떻게 보면 <strong>상태의 집합</strong>입니다. 화면에 나타나는 모든 정보나, 버튼 활성화 등 <strong>상태</strong>들로 구성되어 있습니다.</li>
<li>상태들을 관리하기 힘들어지고, 의도하지 않는 방향으로 제어가 된다면 상태 문제가 됩니다.</li>
</ul>
<p>상태 문제의 좋은 예시로, 로그인이 실패했을 때 지속적으로 다음 화면이 보여 지는 상황이 발생합니다.</p>
<h3 id="부수-효과">부수 효과</h3>
<ul>
<li>네트워크 통신, 데이터베이스 접근 등의 부수 효과들로 인해 발생합니다.</li>
<li>다이얼로그, 토스트 메시지, 액티비티의 이동도 이에 포함됩니다.</li>
<li>여러 비동기 작업들이 섞여서 어떤 결과를 얻을 지 예상할 수 없고 이에 따라 상태변화에 어려움을 겪습니다.
위 문제들을 MVI에서 어떻게 풀어나가는지 확인해 보면서, MVI에 대해 알아보겠습니다.</li>
</ul>
<h2 id="mvi">MVI</h2>
<p>MVI ⇒ Model + View + Intent 를 합친 용어</p>
<p>MVI는 Model, View, Intent로 이루어져 있으며, 데이터베이스, 네트워크 통신등의 작업을 진행하기 위해 SideEffect를 포함합니다.</p>
<ul>
<li>사용자 또는 시스템의 Action을 포함하는 Intent가 발생합니다.</li>
<li>Intent를 통해 Model이 reducing되고, Model을 Observing 하고 있던 View는 Rendering을 통해 View를 업데이트 합니다.</li>
</ul>
<h3 id="intent">Intent</h3>
<ul>
<li>사용자 또는 앱 내의 상태를 바꾸려는 의도</li>
<li>모델은 인텐트를 통해서, 새로운 상태로 변화할 수 있다.<ul>
<li>MVI의 단점 중 하나로, 가벼운 변경 사항도 이를 적용해야 합니다.</li>
</ul>
</li>
</ul>
<p>기존의 MVC나 MVVM 같은 디자인 패턴은, 어떠한 형식에 따라 개발자가 직접 설계하는 방식이었습니다. MVI 패턴 같은 경우에도 직접 작성할 수 있으나, MVI 패턴은 제공되는 프레임워크를 통해 설계 하는것이 좋습니다.</p>
<p>참고</p>
<blockquote>
</blockquote>
<p><a href="https://small-stepping.tistory.com/1137">https://small-stepping.tistory.com/1137</a>
<a href="https://medium.com/@kimdohun0104/mvi-%ED%8C%A8%ED%84%B4%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0-%EC%9D%B4%EC%9C%A0%EC%99%80-%EB%B0%A9%EB%B2%95-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%ED%95%9C%EA%B3%84-767cc9973c98">https://medium.com/@kimdohun0104/mvi-패턴에-대한-고찰-이유와-방법-그리고-한계-767cc9973c98</a>
<a href="https://dev.to/kaleidot725/implementaing-jetpack-compose-orbit-mvi-3gea">https://dev.to/kaleidot725/implementaing-jetpack-compose-orbit-mvi-3gea</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] 안드로이드 4대 컴포넌트]]></title>
            <link>https://velog.io/@roel_dev/ANDROID-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-4%EB%8C%80-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</link>
            <guid>https://velog.io/@roel_dev/ANDROID-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-4%EB%8C%80-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</guid>
            <pubDate>Wed, 25 Sep 2024 13:02:47 GMT</pubDate>
            <description><![CDATA[<h1 id="android-앱-구성요소">ANDROID 앱 구성요소</h1>
<h2 id="1-activity-액티비티">1. Activity (액티비티)</h2>
<ul>
<li><p>설명: 하나의 화면을 나타내는 컴포넌트로, 사용자와 상호작용할 수 있는 UI 요소입니다.</p>
</li>
<li><p>역할: 사용자 인터페이스를 구성하고, 사용자의 입력을 처리하며, 화면 간 전환을 관리합니다.</p>
</li>
<li><p>예시: 로그인 화면, 홈 화면, 설정 화면 등 각각의 화면이 액티비티로 구현됩니다.</p>
</li>
<li><p>중요한 메서드</p>
<blockquote>
<p>onCreate(): 액티비티가 처음 생성될 때 호출됩니다.
onStart(): 액티비티가 화면에 표시될 때 호출됩니다.
onResume(): 액티비티가 상호작용 상태로 돌아올 때 호출됩니다
onPause(): 액티비티가 일시 중지되거나 다른 액티비티가 화면을 가리는 등 포그라운드에서 벗어나기 전에 호출됩니다.
onStop(): 액티비티가 더 이상 사용자에게 보여지지 않고 화면에서 완전히 가려질 때 호출됩니다.
onRestart(): 액티비티가 onStop() 상태에서 다시 시작되기 전에 호출됩니다.
onDestroy(): 액티비티가 소멸될 때 호출됩니다.</p>
</blockquote>
</li>
</ul>
<h2 id="2-service-서비스">2. Service (서비스)</h2>
<ul>
<li>설명: UI 없이 백그라운드에서 실행되는 작업을 처리하는 컴포넌트입니다. 사용자가 직접 상호작용하지 않지만, 긴 시간 동안 실행되는 작업을 수행하는 데 적합합니다.</li>
<li>역할: 백그라운드에서 작업을 처리하며, 음악 재생, 데이터 동기화, 파일 다운로드 등의 작업을 할 수 있습니다.</li>
<li>예시: 음악 앱에서 음악 재생을 백그라운드에서 계속 실행하는 것, 다운로드 매니저가 파일을 다운로드하는 과정.</li>
<li>중요한 메서드<blockquote>
<p>onStartCommand(): 서비스가 시작될 때 호출됩니다.
onBind(): 바인딩된 서비스의 클라이언트와 통신할 때 사용됩니다.
onDestroy(): 서비스가 종료될 때 호출됩니다.</p>
</blockquote>
</li>
</ul>
<h2 id="3-broadcast-receiver-브로드캐스트-리시버">3. Broadcast Receiver (브로드캐스트 리시버)</h2>
<ul>
<li>설명: 시스템 또는 다른 애플리케이션에서 발생하는 이벤트를 감지하고, 이에 대해 반응하는 컴포넌트입니다.</li>
<li>역할: 시스템 전체에서 발생하는 이벤트(예: 배터리 부족, 네트워크 상태 변경 등)에 반응하여 적절한 작업을 수행합니다.</li>
<li>예시: 배터리 상태 변경, 네트워크 연결 상태 변경, 알람 시간 도달 시 알림.</li>
<li>중요한 메서드<blockquote>
<p>onReceive(): 브로드캐스트 메시지가 전달될 때 호출됩니다.</p>
</blockquote>
</li>
</ul>
<h2 id="4-content-provider-콘텐트-제공자">4. Content Provider( 콘텐트 제공자)</h2>
<ul>
<li>설명: 애플리케이션 간의 데이터 공유를 관리하는 컴포넌트로, 데이터를 제공하고 저장할 수 있는 인터페이스를 제공합니다.</li>
<li>역할: 애플리케이션 간에 데이터를 저장하고 불러오며, 이를 통해 다른 애플리케이션이 데이터에 접근할 수 있도록 합니다. 주로 데이터베이스나 파일 시스템의 데이터를 관리합니다.</li>
<li>예시: 연락처, 캘린더, 사진 갤러리 등의 데이터를 다른 앱에서 조회하거나 수정할 수 있습니다.</li>
<li>중요한 메서드<blockquote>
<p>query(): 데이터를 조회할 때 사용됩니다.
insert(): 데이터를 삽입할 때 사용됩니다.
update(): 데이터를 수정할 때 사용됩니다.
delete(): 데이터를 삭제할 때 사용됩니다.
getType(): 제공하는 데이터의 형식을 클라이언트에게 알려줄 때 사용됩니다.</p>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자료구조] Classes and Objects: A Deeper Look - 2]]></title>
            <link>https://velog.io/@roel_dev/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-Classes-and-Objects-A-Deeper-Look-2-zxz7famk</link>
            <guid>https://velog.io/@roel_dev/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-Classes-and-Objects-A-Deeper-Look-2-zxz7famk</guid>
            <pubDate>Thu, 19 Sep 2024 00:55:32 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/roel_dev/post/35827523-9c47-426b-84ee-6f814994b3cd/image.jpg" alt=""></p>
<h1 id="referring-to-the-current-objects-members-with-the-this-reference">Referring to the Current Object’s Members with the this Reference</h1>
<ul>
<li><p>모든 객체는 <strong>this</strong> 라는 키워드로 자신에 대한 참조를 엑세스할 수 있습니다.</p>
</li>
<li><p>특정 객체에 대해 인스턴스 메서드가 호출될 때 메서드의 본문은 <strong>this</strong> 키워드를 암시적으로 사용하여 객채의 인스턴스 변수 및 기타 메서드를 참조합니다.</p>
<ul>
<li>클래스의 코드를 사용하여 어떤 개체를 조작해야 하는지 알 수 있습니다.</li>
<li>인스턴스 메서드의 본문에서 <strong>this</strong> 키워드를 명시적으로 사용할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>this</strong> 참조를 암묵적으로 명시적으로 사용할 수 있습니다.</p>
</li>
<li><p>두 개 이상의 클래스가 포함된 .java 파일을 컴파일할 때 그 컴파일러는  모든 클래스에 대해 .class 확장자가 있는 별도의 클래스 파일을 생성합니다.</p>
</li>
<li><p>하나의 소스 코드(.java) 파일에 여러 클래스 선언이 포함된 경우 컴파일러는 해당 클래스의 두 클래스 파일을 같은 디렉토리에 배치합니다.</p>
</li>
<li><p>소스 코드 파일에는 private 클래스를 하나만 포함할 수 있으며, 그렇지 않으면 컴파일 오류가 발생합니다.</p>
</li>
<li><p><strong>non-public</strong> 클래스들은 동일한 패키지에 있는 다른 클래스에서만 사용할 수 있습니다.</p>
</li>
</ul>
<pre><code class="language-java">// Fig. 8.4: ThisTest.java
// this used implicitly and explicitly to refer to members of an object

public class ThisTest
{
    public static void main(String[] args)
    {
        SimpleTime time = new SimpleTime(15, 30, 19);
        System.out.println(time.buildString());
    }
} // end class ThisTest

// class SimpleTime demonstrates the &quot;this&quot; reference
class SimpleTime
{
    private int hour; // 0~23
    private int minute; // 0~59
    private int second; // 0~59

    // if the constructur uses parameter names identical to
    // instance variable names the &quot;this&quot; reference is
    // required to distingish between the names

    public SimpleTime(int hour, int minute, int second)
    {
        this.hour = hour; // set &quot;this&quot; object&#39;s hour
        this.minute = minute; // set &quot;this&quot; object&#39;s minute
        this.second = second; // set &quot;this&quot; object&#39;s second
    }

    // use explicit and implicit &quot;this&quot; to call toUniversalString
    public String buildString()
    {
        return String.format(&quot;%24s: %s%n%24s: %s&quot;,
            &quot;this.toUniversalString()&quot;, this.toUniversalString(),
            &quot;toUniversalString()&quot;, toUniversalString());
    }
    // convert to String in universal-time format (HH:MM:SS)
    public String toUniversalString()
    {
        // &quot;this&quot; is not required here to access instance variables
        // because method does not have local variables with smae
        // name as instance variables
        return String.format(&quot;%02d:%02d:%02d&quot;,
            this.hour, this.minute, this.second);
    }
} // end class SimpleTime</code></pre>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/36c7293a-f168-45bc-b3bb-345568edc6de/image.png" alt=""></p>
<ul>
<li><strong>SimpleTime</strong> 은 시간, 분, 초 <strong>private</strong> 인스턴스 변수를 선언합니다.</li>
<li>클래스의 인스턴스 변수 이름들과 동일한 생성자의 매개 변수 이름인 경우</li>
<li>우리는 이 <strong>this</strong> 를 사용하여 인스턴스 변수를 참조합니다. ****</li>
</ul>
<p>대부분의 IDE들은 <strong>this</strong>.x = x 대신 x = x라고 말하면 경고를 발행하며, 이 문장은 종종 작동 금지라고 불립니다.</p>
<ul>
<li>Java는 클래스의 모든 개체에서 이 메서드가 호출되는 클래스당 각 메서드의 사본을 하나만 유지하여 저장소를 보존합니다.</li>
<li>반면에 각 개체에는 클래스의 인스턴스 변수에 대한 고유한 사본이 있습니다.</li>
<li>클래스의 각 메서드는 이를 암시적으로 사용하여 유지 관리할 클래스의 특정 개체를 결정합니다.</li>
</ul>
<h1 id="time-class-case-study-overloaded-constructors">Time Class Case Study: Overloaded Constructors</h1>
<ul>
<li><p>오버로드된 생성자를 사용하면 클래스의 개체를 다양한 방식으로 초기화할 수 있습니다.</p>
</li>
<li><p>생성자에 과부하를 걸려면 서명이 다른 여러 생성자 선언을 제공하기만 하면 됩니다.</p>
</li>
<li><p>컴파일러는 각 서명의 매개 변수 수, 매개 변수 유형 및 매개 변수 유형 순서에 따라 서명을 차별화한다는 점을 기억하세요.</p>
</li>
<li><p>Class Time2 에는 객체를 초기화하는 편리한 방법을 제공하는 5개의 오버로드된 생성자가 포함되어 있습니다.</p>
</li>
<li><p>컴파일러는 생성자 호출에 지정된 인수 유형의 수, 유형 및 순서를 각 컨스트럭터 선언에 지정된 매개변수 유형의 수, 유형 및 순서와 일치시켜 적절한 생성자를 호출합니다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자료구조] Classes and Objects: A Deeper Look]]></title>
            <link>https://velog.io/@roel_dev/Classes-and-Objects-A-Deeper-Look</link>
            <guid>https://velog.io/@roel_dev/Classes-and-Objects-A-Deeper-Look</guid>
            <pubDate>Wed, 04 Sep 2024 16:08:25 GMT</pubDate>
            <description><![CDATA[<h1 id="introduction">Introduction</h1>
<ul>
<li>클래스 구축, 클래스 멤버에 대한 엑세스 제어, 생성자 생성에 대해 자세히 알아보기</li>
<li>문제가 발생했음을 나타내기 위해 예외를 throw 하는 방법을 보여줍니다.</li>
<li><strong>구성(Composition)</strong> - 클래스가 다른 클래스의 객체에 대한 참조를 멤버로 가질 수 있게 해주는 기능입니다.</li>
<li><strong>열거형(enum)</strong> 유형에 대한 자세한 내용들</li>
<li>정적인 클래스 구성원과 최종 인스턴스 변수에 대해 자세히 논의합니다.</li>
<li>대규모 어플리케이션을 관리하고 재사용을 촉진하기 위해 패키지로 클래스를 구성하는 방법 보여주기</li>
</ul>
<h1 id="time-class-case-study">Time Class Case Study</h1>
<ul>
<li><strong>Class Time1</strong> 은 하루 중 시간을 나타냅니다.</li>
<li><strong>private int</strong> 인스턴스 변수 hour, minute, 그리고 second 는 보편적 시간의 형식을 나타냅니다. (시간은 0 ~ 23, 분 및 초는 각각 0 ~ 59의 범위인 24시간 시계 형식)</li>
<li>public methods 는 시간을 toUniversalString, toString 으로 설정합니다.<ul>
<li>클래스가 클라이언트에게 제공하는 공공 서비스 및 공공 인터페이스를 호출합니다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">// Fig. 8.1: Time1.java
// Time1 class declaration maintains the time in 24-hour format.

public class Time1 
{
    private int hour; // 0 ~ 23
    private int minute; // 0 ~ 59
    private int second; // 0 ~ 59

    // set a new time value using universal time: throw an
    // exception if the hour, minute or second is invalid
    public void setTime(int hour, int minute, int second)
    {
        if (hour &lt; 0 || hour &gt;= 24 || minute &lt; 0 || minute &gt;= 60 || second &lt; 0 || second &gt;= 60)
        {
            throw new IllegalArgumentException(&quot;hour, minute and/or second was out of range&quot;);
        }
        this.hour = hour;
        this.minute = minute;
        this.second = second;
    }

    // convert to String in universal-time format (HH:MM:SS)
    public String toUniversalString()
    {
        return String.format(&quot;%02d:%02d:%02d&quot;, hour, minute, second);
    }

    // convert to String in standard-time format (H:MM:SS AM or PM)
    public String toString()
    {
        return String.format(&quot;%d:%02d:%02d %s&quot;, 
            ((hour == 0 || hour == 12) ? 12 : hour % 12),
            minute, second, (hour &lt; 12 ? &quot;AM&quot; : &quot;PM&quot;));
    }
} // end class Time1</code></pre>
<p>위 코드의 <strong>setTime</strong> 과 같은 방법의 경우, 인스턴스 변수 값을 설정하는 데 사용하기 전에 방법의 모든 인수를 검증하여 모든 인수가 유효한 경우에만 개체의 데이터가 수정되는지 확인합니다.</p>
<p>Chapter 3 에서 액세스 수정자로 선언된 방법은 비공개 방법이 선언된 클래스의 다른 방법으로만 호출할 수 있다는 점을 기억하세요. 이러한 방법은 일반적으로 클래스의 다른 방법의 작동을 지원하는 데 사용되기 때문에  <strong>utility methods</strong> 또는 <strong>helper methods</strong> 이라고도 합니다.</p>
<pre><code class="language-java">// Fig. 8.2: TimeTest.java
// Time1 object used in an app

public class TimeTest
{
    public static void main(String[] args) 
    {
        // create and initialize a Time1 object
        Time1 time = new Time1(); // invokes Time1 constructor

        // output string representations of the time
        displayTime(&quot;After time object is created&quot;, time);
        System.out.println();

        // change time and output updated time
        time.setTime(13, 27, 6);
        displayTime(&quot;After calling setTime&quot;, time);
        System.out.println();
        // attempt to set time with invalid values
        try
        {
            time.setTime(99, 99, 99); // all values out of range
        }
        catch(IllegalArgumentException e)
        {
            System.out.printf(&quot;Exception: %s%n%n&quot;, e.getMessage());
        }

        // display time after attempt to set invalid values
        displayTime(&quot;After calling setTime with invalid values&quot;, time);
    }

    // displays a Time1 object in 24-hour and 12-hour formats
    private static void displayTime(String header, Time1 t)
    {
        System.out.printf(&quot;%s%nUniversal time: %s%nStandard time: %s%n&quot;,
            header, t.toUniversalString(), t.toString());
    }
} // end Class TimeTest</code></pre>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/d2f38d19-c1b3-463d-a45e-e7b67f60d961/image.png" alt=""></p>
<ul>
<li>Class Time1 는 생성자를 선언하지 않았으므로 컴파일러가 기본 생성자(<strong>default</strong>)를 제공합니다.</li>
<li>각 인스턴스 변수는 암묵적으로 기본값을 받습니다. <strong>int</strong> value.</li>
<li>인스턴스 변수는 클래스 본문에 선언될 때 로컬 변수와 동일한 초기화 구문을 사용하여 초기화할 수도 있습니다.</li>
</ul>
<p><strong>Method setTime</strong> 그리고 <strong>Throwing Exceptions</strong></p>
<ul>
<li><strong>Method setTime</strong> 은 시간을 설정할 때 쓰일 정수 파라미터들을 선언합니다.</li>
<li>각 인수를 테스트하여 값이 적절한 범위를 벗어나는지 확인합니다.</li>
</ul>
<p>잘못된 값의 경우, setTime 은 IllegalArgumentException 유형의 예외를 throw 합니다.</p>
<ul>
<li>잘못된 인수가 메서드에 전달되었음을 클라이언트 코드에 알립니다.</li>
<li><strong>try…catch</strong> 문을 사용하여 예외를 잡고 복구를 시도할 수 있습니다.</li>
<li><strong>throw statement</strong> 의 클래스 인스턴스 생성 표현을 IllegalArgumentException 라는 새로운 개체를 생성합니다. 이 경우 사용자가 지정 오류 메세지를 지정할 수 있는 생성자를 호출합니다.</li>
<li>예외 개체가 생성된 후 <strong>throw statement</strong> 은 <strong>setTime</strong> 을 즉시 종료하고 예외는 시간을 설정하려고 시도한 호출 메서드로 돌아갑니다.</li>
</ul>
<p>Class Time1 선언의 소프트웨어 엔지니어링</p>
<ul>
<li>인스턴수 변수 hour, minute, second 는 각각 private 로 선언됩니다.</li>
<li>클래스 내에서 사용되는 실제 데이터 표현은 클래스의 클라이언트에게 아무런 문제가 되지 않습니다.</li>
<li>Time1 이 내부적으로 시간을 자정 이후의 초수 또는 자정 이후의 분 및 초수로 표현하는 것은 합리적입니다.</li>
<li>클라이언트는 이것을 인식하지 못한 채 <strong>public methods</strong> 을 사용하여 동일한 결과를 얻을 수 있습니다.</li>
</ul>
<p>클래스는 프로그래밍을 단순화합니다. </p>
<p>클라이언트는 클래스의 <strong>public methods</strong> 만 사용할 수 있기 때문입니다. </p>
<p>이러한 방법은 일반적으로 구현 지향적이라기보다는 클라이언트 지향적입니다.</p>
<p>클라이언트는 클래스의 구현을 인식하지도, 관여하지도 않습니다. </p>
<p>클라이언트는 일반적으로 클래스가 하는 일에는 관심이 있지만 클래스가 하는 방식에는 관심이 없습니다.</p>
<p>인터페이스는 구현보다 덜 자주 변경됩니다. </p>
<p>구현이 변경되면 구현 종속 코드가 그에 따라 변경되어야 합니다. </p>
<p>구현을 숨기면 다른 프로그램 부분이 클래스 구현 세부 정보에 의존하게 될 가능성이 줄어듭니다.</p>
<p>Java SE 8 - 날짜/시간 API</p>
<ul>
<li>일반적으로 나만의 날짜 및 시간 클래스를 구축하는 대신 Java API에서 제공하는 클래스를 재사용합니다.</li>
<li>Java SE 8은 java.time 패키지의 클래스로 정의되는 새로운 날짜/시간 API를 도입했습니다.</li>
<li>는 이전 클래스의 다양한 문제를 수정하고 날짜, 시간대, 시간대, 캘린더 등을 조작할 수 있는 보다 강력하고 사용하기 쉬운 기능을 제공합니다.<ul>
<li>이전 클래스의 다양한 문제를 수정하고 날짜, 시간대, 시간대, 캘린더 등을 조작할 수 있는 보다 강력하고 사용하기 쉬운 기능을 제공합니다.</li>
</ul>
</li>
</ul>
<h1 id="controlling-access-to-members">Controlling Access to Members</h1>
<ul>
<li>엑세스 수정자를 public 및 private 로 제어하여 클래스의 변수 및 방법에 대한 엑세스 권한을 부여합니다.</li>
<li>클래스의 클라이언트에 제시되는 <strong>public methods</strong> 는 클래스가 제공하는 서비스의 한 뷰입니다.(클래스의 <strong>public</strong> 인터페이스)</li>
<li>클라이언트는 클래스가 작업을 수행하는 방식에 대해서 걱정할 필요가 없습니다.<ul>
<li>이러한 이유로 클래스의 <strong>private</strong> 변수와 <strong>private</strong> <strong>methods</strong> 은 그것의 클라이언트에 액세스 할 수 없습니다.</li>
</ul>
</li>
<li><strong>private</strong> 클래스 구성원은 외부 클래스에 접근할 수 없습니다.</li>
</ul>
<pre><code class="language-java">// Fig. 8.3: MemberAccessTest.java
// Private members of class Time1 are not accessible.
public class MemberAccessTest
{
    public static void main(String[] args)
    {
        Time1 time = new Time1(); // create and initialize Time1 object

        time.hour = 7; // error: hour has private access in Time1
        time.minute = 15; // error: hour has private access in Time1
        time.second = 30; // error: hour has private access in Time1
    }
} // and class MemberAccessTest</code></pre>
<p><img src="https://velog.velcdn.com/images/roel_dev/post/b416c46d-9046-4552-b2a1-9f129f97049f/image.png" alt=""></p>
<blockquote>
<p>클래스의 멤버가 아닌 방법으로 해당 클래스의 <strong>private</strong> 멤버에 액세스하려고 하면 컴파일 오류가 발생합니다.</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>