𝓓𝓮𝓼𝓲𝓰𝓷 𝓟𝓪𝓽𝓽𝓮𝓻𝓷𝓼

2025. 2. 5. 18:36TIL

✔오늘 배운 중요한 🔑 point

  • 디자인 패턴은 코드를 구조화하고 유지보수성을 높이며, 재사용성을 극대화하는 데 도움을 준다.
  • 디자인 패턴은 단순한 코드 작성법이 아니라 특정한 문제를 해결하기 위한 검증된 방법이다. 따라서 어떤 문제를 해결하기 위해서 해당 패턴이 만들어졌는지를 이해하는것이 중요하다.
  • 디자인 패턴을 제대로 사용하기 위해서는 적절한 상황에서 올바른 패턴을 선택하는 능력이 중요하다.

🎯 오늘 배운 내용

 

적절한 패턴 선택

객체를 하나만 유지해야 함 싱글톤 패턴 로깅, 데이터베이스 연결 등
객체 생성을 유연하게 관리하고 싶음 팩토리 메서드 패턴 객체 생성 로직을 숨기고 싶을 때
동적으로 알고리즘을 변경하고 싶음 전략 패턴 런타임에 알고리즘을 선택 가능하게
복잡한 서브 시스템을 단순하게 제공하고 싶음 퍼사드 패턴 여러 클래스를 하나의 인터페이스로 묶음
객체 간의 의존성을 줄이고 싶음 의존성 주입(DI) Spring의 @Autowired, @Bean

 

Design Pattern이란?

소프트웨어 설계에서 자주 발생하는 문제를 해결하기 위한 재사용 가능한 해결책

 

Design Pattern 종류

 

1. 생성 패턴 (Creational Patterns)

2. 구조 패턴 (Structure Patterns)

3. 행위 패턴 (Behavioral Patterns)

 

1. 생성 패턴 (Creational Patterns)

객체의 생성과 초기화를 유연하고 효율적으로 수행하는 객체 생성 패턴 

대표적인 생성 패턴:

SingleTon 

클래스에 단 하나의 인스터스만 존재하도록 보장

Spring 프레임워크를 사용하면서 @Service, @Controller, @Component와 같은 어노테이션들은 Spring의 Bean 관리 방식에 의해서 싱글톤 패턴이 적용되기 때문에 Spring 컨테이너에 의해서 관리되는 싱글톤 패턴이 적용된다고 할 수 있다.

package hjp.hjchat.domain.chat.controller

@Controller
class ChatController(
    private val chatService: ChatService,
) : GraphQLQueryResolver, GraphQLMutationResolver {
}

 

Factory Method

객체 생성을 캡슐화 하여 특정 클래스를 직접 생성하는것이 아닌 팩토리 클래스를 통해 생성하는 패턴

팩토리 메서드의 특징은 객체 생성을 캡슐화 하고 직접적으로 new 키워드를 사용하지 않는것이 핵심이다.

아래 코드의 redisTemplate() 이라는 함수는 @Bean 어노테이션으로 인해 Spring 컨테이너가 해당 함수를 실행해서 RedisTemplate이라는 객체를 생성하고 관리한다.  즉 RedisTemplate<String, String> 이라는  객체를 내가 직접 생성하는것이 아니라 redisTemplate 함수를 통해 객체를 생성하기 때문에 redisTemplate() 함수가 Factory Mehtod 라고 할수 있다.

@Configuration
class RedisConfig {

    @Bean
    fun redisTemplate(connectionFactory: RedisConnectionFactory): RedisTemplate<String, String> {
        val redisTemplate = RedisTemplate<String, String>()
        redisTemplate.connectionFactory = connectionFactory
        redisTemplate.keySerializer = StringRedisSerializer()
        redisTemplate.valueSerializer = StringRedisSerializer()
        return redisTemplate
    }
}

 

Builder

복잡한 객체 생성을 단계별로 처리하는 패턴

Builder 패턴의 핵심은 복잡한 객체 생성을 단계별로 수행하는 것이다.

generateToken이라는 jwt토큰을 생성하는 함수는 Jwts.Buider() 를 통해 JWT 토큰을 점진적으로 생성하는 방식이기 때문에 Builder 패턴이 적용되었다고 볼 수 있다.

private fun generateToken(subject: String, memberRole: String, tokenType: String, expirationTime: Int): String {
        val claims: Claims =
            Jwts.claims().add(mapOf(MEMBER_ROLE_KEY to memberRole, TOKEN_TYPE_KEY to tokenType)).build()

        val now = Instant.now()
        val key = Keys.hmacShaKeyFor(secret.toByteArray(StandardCharsets.UTF_8))

        return Jwts.builder()
            .subject(subject)
            .issuer(issuer)
            .issuedAt(Date.from(now))
            .expiration(Date(System.currentTimeMillis() + expirationTime))
            .claims(claims)
            .signWith(key)
            .compact()
    }

 

 

2. 구조 패턴 (Structure Patterns)

클래스나 객체를 조합하여 더 큰 구조를 형성하는 패턴

대표적인 구조 패턴:

 

Decorator

기존 클래스의 기능을 확장할 때 상속 대신 활용하는 패턴 

 

아래의 코드에서 decorate() 함수는 WebSocketHandlerDecorator(handler)를 상속받아서 원래의 기능을 감싸고 새로운 기능을 추가로 제공한다. 즉 원래의 기능을 변경하지 않고 확장하는 데코레이터 패턴이라고 볼 수 있다.

@Component
class CustomWebSocketHandlerDecoratorFactory : WebSocketHandlerDecoratorFactory {

    override fun decorate(handler: WebSocketHandler): WebSocketHandler {
        return object : WebSocketHandlerDecorator(handler) {
            override fun afterConnectionEstablished(session: WebSocketSession) {
                val tokenValid = session.attributes["tokenValid"] as? Boolean ?: false
                session.attributes[session.id] = session  
                
                if (!tokenValid) {
                    session.close(CloseStatus(4001, "Invalid JWT Token"))
                    return
                }

                super.afterConnectionEstablished(session)
            }
            override fun afterConnectionClosed(session: WebSocketSession, closeStatus: CloseStatus) {
                session.attributes.remove(session.id) 
                super.afterConnectionClosed(session, closeStatus)
            }
        }
    }
}

 

Facade 

복잡한 서브시스템을 감추고 단순한 인터페이스를 제공하는 패턴 

아래코드에서 Kafka 메세지 전송의 복잡성을 감추고 단순한 sendMessage() 인터페이스만 제공한다. 즉 kafkaProducerService.sendMessage()는 전송 로직을 감춘 퍼사드 패턴이 적용되었다고 볼 수 있다.

kafkaProducerService.sendMessage(
    topic = "chat-messages",
    key = message.chatRoomId.toString(),
    message = mapOf(
        "chatRoomId" to message.chatRoomId.toString(),
        "senderName" to member.userName,
        "senderId" to member.id,
        "content" to message.content,
        "profileImageUrl" to member.profileImageUrl
    )
)

 

Proxy

실제 객체 대신 대리 객체인 Proxy가 원본 객체를 감싸서 추가적인 로직을 수행하는 패턴

@Transactional 은 AOP 기반으로 동작하며 내부적으로 프록시 패턴이 적용된다.

아래 코드에서 @Transactional 을 사용하면 트랜잭션을 관리하는 프록시 객체가 자동으로 생성된다.  따라서 @Transactional 이 적용된 signUp 함수는 프록시 패턴이 적용되었다고 볼 수 있다.

    @Transactional
    fun signUp(request: SignUpRequest): String {
    }

 

 

3. 행위 패턴 (Behavioral Patterns)

객체의 상호작용과 책임을 분배하는 패턴

대표적인 행위 패턴:

 

Observer

 한 객체 (Subjet)의 상태 변화가 발생하면 이를 구독하고 있는 여러 객체들(Observer)에게 자동으로 알리는 패턴

아래 코드에서 KafkaProducer가 Subject 역할을 하며 @KafkaListener가 Observer 역할을 수행한다.  즉 Producer가 메세지를 보내면 Listener가 이를 감지하고 실행되기 때문에 옵저버 패턴이 적용되었다고 볼 수 있다. 

    @KafkaListener(topics = ["friend-events"], groupId = "hjchat-consumer-group")
    fun consumeFriendEvent(record: ConsumerRecord<String, String>) {
        try {
            println("Received Kafka message: ${record.value()}")
            val event = ObjectMapper().readValue(record.value(), FriendEvent::class.java)

            println("Parsed Kafka event: $event")

            messagingTemplate.convertAndSend("/topic/friend/${event.receiverId}", event)
            println("WebSocket message sent to /topic/friend/${event.receiverId}")
        } catch (e: Exception) {
            println("Error processing Kafka message: ${record.value()} - ${e.message}")
            throw e
        }
    }

 

 

🤔 어떻게 활용할까?

프로젝트를 진행할 때 프로젝트의 요구사항을 분석하고 반복적으로 등장하는 문제를 식별한 뒤 그에 맞는 디자인 패턴으르 적용하는것이 매우 중요한것 같다. 패턴을 암기하는 것이 아니라 각 패턴이 어떤 문제를 해결하는지를 이해하고 내가 프로젝트에 적용을 시켰다면 다른 패턴으로도 적용을 시켜보고 문제를 해결하는 최선의 패턴이 무엇인지를 고민하는 과정이 필요할것 같다. 

📓 오늘의 한줄

"In order to succeed, your desire for success should be greater than your fear of failure."

- Bill Cosby -