IMCAT.DEV
로그인 가입
Scala 고급 디자인패턴

Scala 디자인 패턴 (Design Patterns)

Scala 디자인 패턴 (Design Patterns) — 고급

디자인 패턴 (Design Patterns)

1. 도입 (왜 배워야 하는가)

디자인 패턴은 소프트웨어 공학에서 자주 발생하는 문제를 해결하기 위한 일반적인 해결책을 제공합니다. 실무에서는 복잡한 애플리케이션을 설계하고 구축할 때, 코드의 재사용성과 유지보수성을 높이는 데 큰 도움이 됩니다. 디자인 패턴을 모른다면, 중복 코드가 많아지고, 수정이 어렵고, 팀원 간의 협업이 힘들어질 수 있습니다. 또한, 패턴을 이해하지 못하면 코드의 의도를 파악하기 어려워지고, 결과적으로 품질이 떨어진 소프트웨어를 만들 수 있습니다.

2. 핵심 개념 설명

디자인 패턴은 크게 생성(Creational), 구조(Structural), 행위(Behavioral) 패턴으로 나뉩니다. 각 패턴은 특정한 문제를 해결하기 위한 방법을 제시합니다.

패턴 종류설명
생성 패턴객체의 생성 방식에 대한 패턴 (예: Singleton, Factory)
구조 패턴객체 간의 관계를 정의하는 패턴 (예: Adapter, Composite)
행위 패턴객체 간의 상호작용을 정의하는 패턴 (예: Observer, Strategy)

비유하자면, 디자인 패턴은 요리 레시피와 같습니다. 요리사가 재료를 조합하여 요리를 만들듯, 개발자는 디자인 패턴을 이용해 코드 구조를 설계합니다.

3. 기본 코드 예제

싱글턴 패턴 (Singleton Pattern)

싱글턴 패턴은 클래스의 인스턴스가 오직 하나만 존재하도록 보장합니다.

object Singleton {
  def sayHello(): Unit = {
    println("Hello, Singleton!")
  }
}

// 사용
Singleton.sayHello()

실행 결과:

Hello, Singleton!
  • object Singleton은 Scala에서 싱글턴 객체를 생성합니다.
  • sayHello 메소드는 "Hello, Singleton!"을 출력합니다.
  • 마지막 줄에서 Singleton.sayHello()를 호출하여 메시지를 출력합니다.
  • 4. 실전 활용 예제

    1. 팩토리 패턴 (Factory Pattern)

    쇼핑몰에서 다양한 종류의 상품을 생성할 때 사용할 수 있습니다.

    trait Product {
      def price: Double
    }
    
    class Book extends Product {
      override def price: Double = 15.99
    }
    
    class Toy extends Product {
      override def price: Double = 9.99
    }
    
    object ProductFactory {
      def createProduct(productType: String): Product = {
        productType match {
          case "Book" => new Book
          case "Toy" => new Toy
          case _ => throw new IllegalArgumentException("Unknown product type")
        }
      }
    }
    
    // 사용
    val book = ProductFactory.createProduct("Book")
    println(book.price) // 15.99

    실행 결과:

    15.99
  • Product 트레이트는 상품의 기본 형태를 정의합니다.
  • Book과 Toy 클래스는 각각 상품의 종류를 구현합니다.
  • ProductFactory는 createProduct 메소드를 통해 상품을 생성합니다.
  • 2. 옵저버 패턴 (Observer Pattern)

    주식 시장에서 주식 가격 변동을 알리기 위해 사용할 수 있습니다.

    trait Observer {
      def update(price: Double): Unit
    }
    
    class StockObserver(name: String) extends Observer {
      def update(price: Double): Unit = {
        println(s"$name received price update: $$price")
      }
    }
    
    class Stock {
      private var observers: List[Observer] = List()
      private var price: Double = _
    
      def addObserver(observer: Observer): Unit = {
        observers = observer :: observers
      }
    
      def setPrice(newPrice: Double): Unit = {
        price = newPrice
        notifyObservers()
      }
    
      private def notifyObservers(): Unit = {
        observers.foreach(_.update(price))
      }
    }
    
    // 사용
    val stock = new Stock
    val observer1 = new StockObserver("Investor A")
    val observer2 = new StockObserver("Investor B")
    
    stock.addObserver(observer1)
    stock.addObserver(observer2)
    
    stock.setPrice(100.0)

    실행 결과:

    Investor B received price update: $100.0
    Investor A received price update: $100.0
  • Observer 트레이트는 옵저버의 기본 형태를 정의합니다.
  • StockObserver는 주식 가격 변동을 수신합니다.
  • Stock 클래스는 가격 변동을 관리하고, 옵저버에게 알립니다.
  • 5. 흔한 실수와 주의사항

    1. 생성자에서 직접 객체 생성: 팩토리 패턴을 사용할 때, 생성자를 직접 호출하면 패턴의 장점을 잃게 됩니다.
    2. - 잘못된 코드:

           val book = new Book() // 직접 생성

      - 올바른 코드:

           val book = ProductFactory.createProduct("Book") // 팩토리 사용
    3. 옵저버 등록 누락: 옵저버 패턴에서 옵저버를 등록하지 않으면 업데이트를 받을 수 없습니다.
    4. - 잘못된 코드:

           val stock = new Stock
           stock.setPrice(100.0) // 옵저버 등록 안 함

      - 올바른 코드:

           stock.addObserver(observer1)
           stock.setPrice(100.0) // 옵저버 등록 후 가격 설정

      6. 정리

    5. 디자인 패턴은 코드의 재사용성과 유지보수성을 높이는 데 필수적입니다.
    6. 주요 패턴은 생성, 구조, 행위로 나뉘며 각각 특정 문제 해결에 유용합니다.
    7. 실무에서 자주 사용하는 패턴으로는 팩토리 패턴과 옵저버 패턴이 있습니다.
    8. 다음에 학습하면 좋은 주제로는 "리팩토링"이나 "SOLID 원칙"이 있습니다.