-
SOLID - OCP (개방 폐쇄 원칙)Clean Architecture 2025. 11. 30. 12:41

안녕하세요~ 오랜만이네요. 오늘은 SOLID 원칙 중 두 번째인, OCP 원칙에 대해 알아보겠습니다!
1. OCP 원칙이란?
OCP 원칙은 Open-Closed Principle 의 약어로, 개방 폐쇄 원칙을 의미합니다. 여기서 개방과 폐쇄는 무엇을 가리킬까요? 기능 확장에는 개방되어 있어야(열려 있어야) 하고, 기능 수정에는 폐쇄되어 있어야(닫혀 있어야) 한다는걸 말합니다. 다시 말해, 새로운 기능을 추가할 때는 기존 코드를 변경하지 않고, 코드를 확장하는 방식으로 처리해야 한다는 것입니다.
2. OCP 원칙 위반/준수 예제 (코틀린)
// OCP 위반 class Notifier { fun send(type: String, message: String) { when(type) { "Email" -> sendEmail(message) "SMS" -> sendSms(message) "Slack" -> sendSlack(message) // 새로운 알림 수단 추가 } } }위의 예제에서는, 새로운 알림 수단 추가 시 send 메소드 내에 변경이 일어납니다. 이는, 새로운 기능 추가 시 기존 코드를 변경하기에 OCP 원칙을 위반합니다.
// OCP 준수 interface Notification { fun send() } class EmailNotifier: Notification { override fun send() { // 이메일 알림 로직 } } class SnsNotifier: Notification { override fun send() { // SNS 알림 로직 } } class Notifier { fun send(notification: Notification) { notification.send() } }OCP 원칙을 준수하기 위해 알림 로직을 인터페이스로 추상화했습니다. 또한, 각각의 알림 기능을 클래스로 분리하여 인터페이스를 상속받고 추상체를 구현하여 구체적인 알림 로직을 작성합니다. 이후 Notifier 클래스에서 send 메소드를 작성할 때 인자로 인터페이스를 받고, 메소드를 호출하는 식으로 코드의 변경을 최소화할 수 있습니다. 이렇게 작성하게 되면, 슬랙과 같은 새로운 알림 기능이 생길때 Notifier 클래스의 send 메소드를 수정하지 않고, SlackNotifier 라는 새로운 클래스를 만듦으로써 기능을 확장할 수 있습니다.
3. OCP 원칙 위반/준수 예제 (안드로이드)
코틀린 예제를 보았으니 안드로이드에서는 어떻게 적용하는지 이해해봅시다.
// OCP 위반 class ItemAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when(holder) { is UserViewHolder -> holder.bind(userData[posiiton]) is ChatBotViewHolder -> holder.bind(chatbotData[position]) // 새로운 뷰홀더 추가 } } }RecyclerView 의 어댑터를 예제로 가져왔습니다. 여기서 새로운 뷰홀더가 추가가 되면 when(holder) 내에서 새로운 코드를 추가하면서 기존 onBindViewHolder 메소드에 코드 수정이 일어납니다. 이는 OCP 원칙을 위반한다고 볼 수 있습니다.
// OCP 준수 abstract class BaseViewHolder<T>(itemView: View): RecyclerView.ViewHolder(itemView) { abstract fun bind(data: T) } class UserViewHolder(itemView: View): BaseViewHolder<User>(itemView) { override fun bind(data: String) { // 유저 데이터 바인딩 } } class ChatbotViewHolder(itemView: View): BaseViewHolder<ChatBot>(itemView) { override fun bind(data: String) { // 챗봇 데이터 바인딩 } } class ItemAdapter<T>(private val items: List<T>): RecyclerView.Adapter<BaseViewHolder<T>>() { override fun onBindViewHolder(holder: BaseViewHolder<T>, position: Int) { holder.bind(items[position]) } }OCP 원칙을 준수하기 위해 인터페이스와 유사한 역할을 하는 BaseViewHolder 라는 추상 메소드를 선언했습니다. 데이터 타입을 제네릭 T로 두어, 어떤 데이터 타입이 들어와도 처리가 가능하게 했습니다. 다른 뷰홀더는 해당 추상 메소드를 상속받아 구현하게 됩니다. 이렇게 설계하면 onBindViewHolder 내부에서는 매개변수로 holder 타입을 abstract class 로 받을 수 있으며, 내부 코드 수정 없이 bind 메소드만 호출하면 됩니다.
4. 클린 아키텍처, 도메인 레이어의 유즈 케이스
앞의 예제를 보면 OCP 원칙을 준수하기 위해 인터페이스(interface)와 추상 클래스(abstract class)가 쓰인 것을 볼 수 있습니다. 인터페이스와 추상 클래스를 클린 아키텍처와 연결지어 생각해보면 변경되지 않는 코어한 핵심 레이어, 도메인 레이어의 유즈케이스를 떠올릴 수 있을 것입니다. 새로운 기능 추가 시 기존 코드의 수정을 "최소화"하는 의미에서, 도메인 레이어의 유즈케이스(Use Case)는 OCP 원칙과 아주 잘 맞아떨어집니다. 유즈케이스를 OCP 원칙을 잘 준수하여 설계하면 기존 코드의 변경 없이도 새로운 요구사항에 대응할 수 있습니다. 반대로, 도메인 레이어의 유즈 케이스를 설계하지 않거나 OCP 원칙을 지키지 않는다면, 새로운 기능을 추가할 때 매번 기존의 코드를 연쇄적으로 수정해야하는 불필요한 비용이 발생하게 됩니다.
5. 마무리 정리
실제 서비스 수준의 프로젝트 규모를 생각한다면, 새로운 기능을 추가할 때 기존의 코드의 수정을 최소화하고, 유연하게 확장하는 방식으로 설계하는 것은 유지보수 비용 측면에서 정말 중요합니다. 이를 위해서는 인터페이스와 추상 메소드, 그리고 클린 아키텍처에서 도메인 레이어의 유즈 케이스가 핵심 역할을 한다는 것을 이번 시간에 알 수 있었습니다.
다음 시간에는 SOLID 의 세 번째 원칙, 리스코프 치환 원칙에 대해 알아보겠습니다! 12월도 화이팅하시길 바래요!
'Clean Architecture' 카테고리의 다른 글
SOLID - ISP (인터페이스 분리 원칙) (1) 2026.01.11 SOLID - LSP (리스코프 치환 원칙) (0) 2025.12.14 SOLID - SRP (단일 책임 원칙) (0) 2025.10.05 UI 레이어란? (3) 2025.08.24 Presentation 레이어란? (8) 2025.08.17