PHP 디자인 패턴 활용 사례: 효율적인 웹 개발의 핵심 🚀
PHP(Hypertext Preprocessor)는 웹 개발에서 가장 널리 사용되는 프로그래밍 언어 중 하나입니다. 그러나 복잡한 프로젝트를 다룰 때 코드의 유지보수성과 확장성을 높이는 것이 중요합니다. 이를 위해 디자인 패턴이 등장했죠. 디자인 패턴은 소프트웨어 설계에서 자주 발생하는 문제들에 대한 재사용 가능한 해결책입니다.
이 글에서는 PHP에서 자주 사용되는 디자인 패턴들과 그 활용 사례를 상세히 살펴보겠습니다. 초보자부터 전문가까지 모두가 이해하기 쉽게 설명하려 노력했으니, 끝까지 함께 해주세요! 🤓
우리는 재능넷(https://www.jaenung.net)과 같은 복잡한 웹 애플리케이션을 개발할 때 디자인 패턴의 중요성을 실감하게 됩니다. 다양한 재능을 거래하는 플랫폼을 구축하려면 확장 가능하고 유지보수가 쉬운 코드 구조가 필수적이기 때문이죠. 그럼 지금부터 PHP 디자인 패턴의 세계로 빠져볼까요? 💡
1. 싱글톤 패턴 (Singleton Pattern) 🔒
싱글톤 패턴은 클래스의 인스턴스가 오직 하나만 생성되도록 보장하는 패턴입니다. 이 패턴은 데이터베이스 연결, 로깅, 캐싱 등 애플리케이션 전체에서 공유되어야 하는 리소스를 관리할 때 유용합니다.
1.1 싱글톤 패턴의 구조
위 다이어그램은 싱글톤 패턴의 기본 구조를 보여줍니다. private static 변수인 instance와 private 생성자, 그리고 getInstance() 메서드가 핵심입니다.
1.2 PHP에서의 싱글톤 패턴 구현
class Database {
private static $instance = null;
private $conn;
private function __construct() {
$this->conn = new PDO("mysql:host=localhost;dbname=mydb", "username", "password");
}
public static function getInstance() {
if (self::$instance == null) {
self::$instance = new Database();
}
return self::$instance;
}
public function getConnection() {
return $this->conn;
}
}
// 사용 예
$db1 = Database::getInstance();
$db2 = Database::getInstance();
if ($db1 === $db2) {
echo "두 인스턴스는 동일합니다.";
}
이 예제에서 Database 클래스는 싱글톤 패턴을 사용하여 구현되었습니다. getInstance() 메서드를 통해 항상 동일한 데이터베이스 연결 인스턴스를 반환합니다.
1.3 싱글톤 패턴의 장단점
- 장점:
- 전역 접근 지점 제공
- 인스턴스 생성 횟수 제한으로 리소스 절약
- 단점:
- 단일 책임 원칙 위반 가능성
- 테스트의 어려움
1.4 실제 활용 사례
재능넷과 같은 웹 애플리케이션에서 싱글톤 패턴은 다음과 같은 상황에서 유용하게 사용될 수 있습니다:
- 데이터베이스 연결 관리: 사용자 정보, 재능 거래 내역 등을 저장하는 데이터베이스 연결을 관리할 때
- 설정 관리: 애플리케이션의 전역 설정을 관리할 때
- 로깅 시스템: 애플리케이션 전체에서 일관된 로깅을 수행할 때
싱글톤 패턴은 강력하지만, 과도한 사용은 피해야 합니다. 적절한 상황에서만 사용하고, 가능한 의존성 주입 등의 대안을 고려하는 것이 좋습니다. 다음 섹션에서는 팩토리 패턴에 대해 알아보겠습니다. 🏭
2. 팩토리 패턴 (Factory Pattern) 🏭
팩토리 패턴은 객체 생성 로직을 캡슐화하여 코드의 유연성과 재사용성을 높이는 생성 패턴입니다. 이 패턴은 객체 생성을 서브클래스에 위임하여 클라이언트 코드와 구체적인 클래스 사이의 결합도를 낮춥니다.
2.1 팩토리 패턴의 종류
팩토리 패턴은 크게 세 가지로 나눌 수 있습니다:
- 심플 팩토리 (Simple Factory)
- 팩토리 메서드 (Factory Method)
- 추상 팩토리 (Abstract Factory)
각각의 패턴에 대해 자세히 살펴보겠습니다.
2.2 심플 팩토리 (Simple Factory)
심플 팩토리는 가장 기본적인 형태의 팩토리 패턴입니다. 객체 생성을 담당하는 별도의 클래스를 만들어 사용합니다.
PHP에서의 심플 팩토리 구현 예:
interface Product {
public function operation(): string;
}
class ConcreteProductA implements Product {
public function operation(): string {
return "ConcreteProductA";
}
}
class ConcreteProductB implements Product {
public function operation(): string {
return "ConcreteProductB";
}
}
class SimpleFactory {
public function createProduct(string $type): Product {
switch ($type) {
case 'A':
return new ConcreteProductA();
case 'B':
return new ConcreteProductB();
default:
throw new Exception("Invalid product type.");
}
}
}
// 사용 예
$factory = new SimpleFactory();
$productA = $factory->createProduct('A');
echo $productA->operation(); // 출력: ConcreteProductA
이 예제에서 SimpleFactory 클래스는 Product 인터페이스를 구현하는 다양한 ConcreteProduct 객체를 생성합니다. 클라이언트는 구체적인 클래스를 알 필요 없이 팩토리를 통해 원하는 제품을 얻을 수 있습니다.
2.3 팩토리 메서드 (Factory Method)
팩토리 메서드 패턴은 객체 생성을 서브클래스에 위임합니다. 이 패턴은 부모 클래스에서 객체 생성의 인터페이스를 제공하고, 서브클래스에서 어떤 클래스의 인스턴스를 생성할지 결정합니다.
PHP에서의 팩토리 메서드 구현 예:
abstract class Creator {
abstract public function factoryMethod(): Product;
public function someOperation(): string {
$product = $this->factoryMethod();
return "Creator: The same creator's code has just worked with " . $product->operation();
}
}
class ConcreteCreatorA extends Creator {
public function factoryMethod(): Product {
return new ConcreteProductA();
}
}
class ConcreteCreatorB extends Creator {
public function factoryMethod(): Product {
return new ConcreteProductB();
}
}
interface Product {
public function operation(): string;
}
class ConcreteProductA implements Product {
public function operation(): string {
return "{Result of the ConcreteProductA}";
}
}
class ConcreteProductB implements Product {
public function operation(): string {
return "{Result of the ConcreteProductB}";
}
}
// 사용 예
function clientCode(Creator $creator) {
echo "Client: I'm not aware of the creator's class, but it still works.\n"
. $creator->someOperation();
}
clientCode(new ConcreteCreatorA());
// 출력: Client: I'm not aware of the creator's class, but it still works.
// Creator: The same creator's code has just worked with {Result of the ConcreteProductA}
clientCode(new ConcreteCreatorB());
// 출력: Client: I'm not aware of the creator's class, but it still works.
// Creator: The same creator's code has just worked with {Result of the ConcreteProductB}
이 예제에서 Creator 클래스는 추상 팩토리 메서드를 정의하고, ConcreteCreator 클래스들이 이를 구현합니다. 각 ConcreteCreator는 자신만의 ConcreteProduct를 생성합니다.
2.4 추상 팩토리 (Abstract Factory)
추상 팩토리 패턴은 관련된 객체들의 집합을 생성하기 위한 인터페이스를 제공합니다. 이 패턴은 여러 제품군을 다룰 때 유용합니다.
PHP에서의 추상 팩토리 구현 예:
interface AbstractFactory {
public function createProductA(): AbstractProductA;
public function createProductB(): AbstractProductB;
}
class ConcreteFactory1 implements AbstractFactory {
public function createProductA(): AbstractProductA {
return new ConcreteProductA1();
}
public function createProductB(): AbstractProductB {
return new ConcreteProductB1();
}
}
class ConcreteFactory2 implements AbstractFactory {
public function createProductA(): AbstractProductA {
return new ConcreteProductA2();
}
public function createProductB(): AbstractProductB {
return new ConcreteProductB2();
}
}
interface AbstractProductA {
public function usefulFunctionA(): string;
}
class ConcreteProductA1 implements AbstractProductA {
public function usefulFunctionA(): string {
return "The result of the product A1.";
}
}
class ConcreteProductA2 implements AbstractProductA {
public function usefulFunctionA(): string {
return "The result of the product A2.";
}
}
interface AbstractProductB {
public function usefulFunctionB(): string;
public function anotherUsefulFunctionB(AbstractProductA $collaborator): string;
}
class ConcreteProductB1 implements AbstractProductB {
public function usefulFunctionB(): string {
return "The result of the product B1.";
}
public function anotherUsefulFunctionB(AbstractProductA $collaborator): string {
$result = $collaborator->usefulFunctionA();
return "The result of the B1 collaborating with the ({$result})";
}
}
class ConcreteProductB2 implements AbstractProductB {
public function usefulFunctionB(): string {
return "The result of the product B2.";
}
public function anotherUsefulFunctionB(AbstractProductA $collaborator): string {
$result = $collaborator->usefulFunctionA();
return "The result of the B2 collaborating with the ({$result})";
}
}
// 사용 예
function clientCode(AbstractFactory $factory) {
$productA = $factory->createProductA();
$productB = $factory->createProductB();
echo $productB->usefulFunctionB() . "\n";
echo $productB->anotherUsefulFunctionB($productA) . "\n";
}
echo "Client: Testing client code with the first factory type:\n";
clientCode(new ConcreteFactory1());
echo "\nClient: Testing the same client code with the second factory type:\n";
clientCode(new ConcreteFactory2());
이 예제에서 AbstractFactory 인터페이스는 두 가지 제품(ProductA와 ProductB)을 생성하는 메서드를 정의합니다. ConcreteFactory 클래스들은 이 인터페이스를 구현하여 관련된 제품들의 집합을 생성합니다.
2.5 팩토리 패턴의 장단점
- 장점:
- 객체 생성 코드를 한 곳에서 관리할 수 있어 유지보수가 쉬움
- 객체 생성과 사용을 분리하여 결합도를 낮춤
- 새로운 제품 추가가 용이함
- 단점:
- 클래스가 많아져 코드가 복잡해질 수 있음
- 모든 제품 생성을 팩토리에 위임하므로 overhead가 발생할 수 있음
2.6 실제 활용 사례
재능넷과 같은 웹 애플리케이션에서 팩토리 패턴은 다음과 같은 상황에서 유용하게 사용될 수 있습니다:
- 사용자 인터페이스 요소 생성: 다양한 테마나 스타일에 따른 UI 컴포넌트 생성
- 데이터베이스 연결 관리: 다양한 데이터베이스 시스템(MySQL, PostgreSQL 등)에 대한 연결 객체 생성
- 결제 시스템 통합: 여러 결제 게이트웨이(PayPal, Stripe 등)에 대한 결제 처리 객체 생성
- 파일 업로드 처리: 다양한 스토리지 서비스(로컬 스토리지, Amazon S3 등)에 대한 파일 업로드 객체 생성
팩토리 패턴은 객체 생성의 유연성을 높이고 코드의 재사용성을 증가시키는 강력한 도구입니다. 하지만 과도한 사용은 코드를 불필요하게 복잡하게 만들 수 있으므로, 적절한 상황에서 신중하게 사용해야 합니다. 다음 섹션에서는 전략 패턴에 대해 알아보겠습니다. 🎯
3. 전략 패턴 (Strategy Pattern) 🎯
전략 패턴은 알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있게 해주는 행위 패턴입니다. 이 패턴을 사용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있습니다.
3.1 전략 패턴의 구조
위 다이어그램은 전략 패턴의 기본 구조를 보여줍니다. Context는 Strategy 인터페이스를 참조하고, 구체적인 전략(ConcreteStrategy)들이 이 인터페이스를 구현합니다.
3.2 PHP에서의 전략 패턴 구현
interface PaymentStrategy {
public function pay($amount);
}
class CreditCardStrategy implements PaymentStrategy {
private $name;
private $cardNumber;
private $cvv;
private $dateOfExpiry;
public function __construct($name, $cardNumber, $cvv, $dateOfExpiry) {
$this->name = $name;
$this->cardNumber = $cardNumber;
$this->cvv = $cvv;
$this->dateOfExpiry = $dateOfExpiry;
}
public function pay($amount) {
echo "Paid $amount using Credit Card.";
}
}
class PayPalStrategy implements PaymentStrategy {
private $email;
private $password;
public function __construct($email, $password) {
$this->email = $email;
$this->password = $password;
}
public function pay($amount) {
echo "Paid $amount using PayPal.";
}
}
class ShoppingCart {
private $paymentStrategy;
public function setPaymentStrategy(PaymentStrategy $paymentStrategy) {
$this->paymentStrategy = $paymentStrategy;
}
public function checkout($amount) {
$this->paymentStrategy->pay($amount);
}
}
// 사용 예
$cart = new ShoppingCart();
// 신용카드로 결제
$cart->setPaymentStrategy(new CreditCardStrategy("John Doe", "0123456", "786", "12/2025"));
$cart->checkout(100);
// PayPal로 결제
$cart->setPaymentStrategy(new PayPalStrategy("johndoe@example.com", "password123"));
$cart->checkout(200);
이 예제에서 ShoppingCart 클래스는 Context 역할을 하며, PaymentStrategy 인터페이스를 통해 다양한 결제 방식(전략)을 사용할 수 있습니다. CreditCardStrategy와 PayPalStrategy는 구체적인 결제 전략을 구현합니다.
3.3 전략 패턴의 장단점
- 장점:
- 런타임에 알고리즘을 선택할 수 있는 유연성 제공
- 새로운 전략을 추가하기 쉬움 (개방-폐쇄 원칙 준수)
- 조건문을 제거하여 코드를 더 깔끔하게 만듦
- 단점:
- 클라이언트가 적절한 전략을 선택해야 함
- 전략이 많아지면 관리가 복잡해질 수 있음
- 모든 전략을 알고 있어야 하는 경우 캡슐화가 깨질 수 있음
3.4 실제 활용 사례
재능넷과 같은 웹 애플리케이션에서 전략 패턴은 다음과 같은 상황에서 유용하게 사용될 수 있습니다:
- 결제 시스템: 위 예제와 같이 다양한 결제 방식을 지원할 때
- 검색 알고리즘: 다양한 검색 방식(키워드, 카테고리, 태그 등)을 구현할 때
- 할인 정책: 다양한 할인 방식(정액 할인, 퍼센트 할인, 쿠폰 등)을 적용할 때
- 파일 압축: 여러 압축 알고리즘을 지원할 때
- 인증 방식: 소셜 로그인, 이메일 인증, 전화번호 인증 등 다양한 인증 방식을 구현할 때
전략 패턴은 알고리즘의 변경이 자주 일어나거나, 여러 알고리즘을 동적으로 선택해야 하는 상황에서 특히 유용합니다. 이 패턴을 사용하면 코드의 유연성과 확장성을 크게 향상시킬 수 있습니다. 다음 섹션에서는 옵저버 패턴에 대해 알아보겠습니다. 👀
4. 옵저버 패턴 (Observer Pattern) 👀
옵저버 패턴은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴입니다.
4.1 옵저버 패턴의 구조
위 다이어그램은 옵저버 패턴의 기본 구조를 보여줍니다. Subject는 Observer들을 관리하고 상태 변화를 통지하며, Observer는 Subject의 상태 변화에 반응합니다.
4.2 PHP에서의 옵저버 패턴 구현
interface Subject {
public function attach(Observer $observer);
public function detach(Observer $observer);
public function notify();
}
interface Observer {
public function update(Subject $subject);
}
class ConcreteSubject implements Subject {
private $observers = [];
private $state;
public function attach(Observer $observer) {
$this->observers[] = $observer;
}
public function detach(Observer $observer) {
$key = array_search($observer, $this->observers, true);
if ($key !== false) {
unset($this->observers[$key]);
}
}
public function notify() {
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
public function setState($state) {
$this->state = $state;
$this->notify();
}
public function getState() {
return $this->state;
}
}
class ConcreteObserverA implements Observer {
public function update(Subject $subject) {
if ($subject instanceof ConcreteSubject && $subject->getState() < 3) {
echo "ConcreteObserverA: Reacted to the event.\n";
}
}
}
class ConcreteObserverB implements Observer {
public function update(Subject $subject) {
if ($subject instanceof ConcreteSubject && ($subject->getState() == 0 || $subject->getState() >= 2)) {
echo "ConcreteObserverB: Reacted to the event.\n";
}
}
}
// 사용 예
$subject = new ConcreteSubject();
$observerA = new ConcreteObserverA();
$subject->attach($observerA);
$observerB = new ConcreteObserverB();
$subject->attach($observerB);
$subject->setState(1);
$subject->setState(2);
$subject->detach($observerB);
$subject->setState(3);
이 예제에서 ConcreteSubject는 상태를 가지고 있으며, 상태가 변경될 때마다 등록된 옵저버들에게 통지합니다. ConcreteObserverA와 ConcreteObserverB는 각각 다른 조건에서 반응하도록 구현되어 있습니다.
4.3 옵저버 패턴의 장단점
- 장점:
- 느슨한 결합: Subject와 Observer는 서로 독립적으로 변경 가능
- 개방-폐쇄 원칙 준수: 새로운 Observer 클래스를 추가하기 쉬움
- 상태 변경 시 동적으로 수신자 변경 가능
- 단점:
- 복잡도 증가: 다수의 Observer 객체를 관리해야 함
- 성능 이슈: 많은 Observer가 있을 경우 모든 Observer에게 알림을 보내는 데 시간이 걸릴 수 있음
- 예측 불가능한 결과: Observer들의 실행 순서가 보장되지 않음
4.4 실제 활용 사례
재능넷과 같은 웹 애플리케이션에서 옵저버 패턴은 다음과 같은 상황에서 유용하게 사용될 수 있습니다:
- 실시간 알림 시스템: 새로운 메시지, 거래 요청, 프로젝트 업데이트 등이 발생했을 때 관련 사용자에게 알림
- 이벤트 기반 시스템: 특정 이벤트(예: 결제 완료, 프로젝트 마감 등)가 발생했을 때 여러 관련 작업 실행
- 데이터 동기화: 데이터베이스의 변경사항을 여러 클라이언트에게 실시간으로 전파
- 사용자 활동 로깅: 사용자의 특정 행동(로그인, 프로필 수정 등)을 감지하여 로그 기록
- UI 업데이트: 백엔드 데이터 변경 시 관련된 모든 UI 컴포넌트 자동 업데이트
옵저버 패턴은 특히 이벤트 기반 시스템이나 실시간 데이터 처리가 필요한 상황에서 매우 유용합니다. 이 패턴을 사용하면 시스템의 다양한 부분을 느슨하게 결합하면서도 효과적으로 상태 변화를 전파할 수 있습니다. 다음 섹션에서는 데코레이터 패턴에 대해 알아보겠습니다. 🎨
5. 데코레이터 패턴 (Decorator Pattern) 🎨
데코레이터 패턴은 객체에 동적으로 새로운 책임을 추가할 수 있게 해주는 구조적 디자인 패턴입니다. 이 패턴은 기존 코드를 수정하지 않고도 객체의 기능을 확장할 수 있어, 유연성과 재사용성을 높입니다.
5.1 데코레이터 패턴의 구조
위 다이어그램은 데코레이터 패턴의 기본 구조를 보여줍니다. Component는 기본 인터페이스를 정의하고, ConcreteComponent는 이를 구현합니다. Decorator는 Component를 참조하며, ConcreteDecorator는 추가적인 기능을 구현합니다.
5.2 PHP에서의 데코레이터 패턴 구현
interface Coffee {
public function getCost();
public function getDescription();
}
class SimpleCoffee implements Coffee {
public function getCost() {
return 10;
}
public function getDescription() {
return "Simple coffee";
}
}
abstract class CoffeeDecorator implements Coffee {
protected $coffee;
public function __construct(Coffee $coffee) {
$this->coffee = $coffee;
}
public function getCost() {
return $this->coffee->getCost();
}
public function getDescription() {
return $this->coffee->getDescription();
}
}
class Milk extends CoffeeDecorator {
public function getCost() {
return $this->coffee->getCost() + 2;
}
public function getDescription() {
return $this->coffee->getDescription() . ", milk";
}
}
class Sugar extends CoffeeDecorator {
public function getCost() {
return $this->coffee->getCost() + 1;
}
public function getDescription() {
return $this->coffee->getDescription() . ", sugar";
}
}
// 사용 예
$coffee = new SimpleCoffee();
echo $coffee->getCost() . ": " . $coffee->getDescription() . "\n";
$coffee = new Milk($coffee);
echo $coffee->getCost() . ": " . $coffee->getDescription() . "\n";
$coffee = new Sugar($coffee);
echo $coffee->getCost() . ": " . $coffee->getDescription() . "\n";
이 예제에서 SimpleCoffee는 기본 커피를 나타내며, Milk와 Sugar는 데코레이터로 커피에 추가적인 기능(우유와 설탕 추가)을 더합니다. 각 데코레이터는 기존 커피 객체를 감싸고 새로운 기능을 추가합니다.
5.3 데코레이터 패턴의 장단점
- 장점:
- 기존 코드를 수정하지 않고 객체의 기능을 확장할 수 있음
- 단일 책임 원칙을 지킬 수 있음
- 런타임에 동적으로 기능을 추가하거나 제거할 수 있음
- 단점:
- 데코레이터를 너무 많이 사용하면 코드가 복잡해질 수 있음
- 초기 설계 단계에서 데코레이터의 계층 구조를 잘 설계해야 함
- 데코레이터의 순서에 따라 결과가 달라질 수 있음
5.4 실제 활용 사례
재능넷과 같은 웹 애플리케이션에서 데코레이터 패턴은 다음과 같은 상황에서 유용하게 사용될 수 있습니다:
- 사용자 권한 관리: 기본 사용자 객체에 다양한 권한(예: 관리자, 프리미엄 사용자 등)을 동적으로 추가
- 로깅 및 모니터링: 기존 기능에 로깅이나 성능 모니터링 기능을 추가
- 데이터 변환: 데이터 스트림에 암호화, 압축 등의 기능을 동적으로 추가
- UI 컴포넌트 확장: 기본 UI 컴포넌트에 추가적인 스타일이나 기능을 덧붙임
- 결제 시스템: 기본 결제 프로세스에 할인, 포인트 사용 등의 기능을 추가
데코레이터 패턴은 객체 지향 프로그래밍의 핵심 원칙 중 하나인 개방-폐쇄 원칙(OCP)을 잘 따르는 패턴입니다. 이 패턴을 사용하면 기존 코드를 수정하지 않고도 새로운 기능을 추가할 수 있어, 유연하고 확장 가능한 시스템을 구축할 수 있습니다. 다음 섹션에서는 의존성 주입 패턴에 대해 알아보겠습니다. 💉
6. 의존성 주입 패턴 (Dependency Injection Pattern) 💉
의존성 주입 패턴은 객체의 생성과 사용의 관심을 분리하는 디자인 패턴입니다. 이 패턴을 사용하면 객체가 필요로 하는 의존성을 외부에서 주입받아 사용하게 되어, 결합도를 낮추고 유연성과 테스트 용이성을 높일 수 있습니다.
6.1 의존성 주입 패턴의 구조
위 다이어그램은 의존성 주입 패턴의 기본 구조를 보여줍니다. Injector는 Client가 필요로 하는 Service 객체를 생성하고 주입합니다. Client는 직접 Service 객체를 생성하지 않고, 주입받아 사용합니다.
6.2 PHP에서의 의존성 주입 패턴 구현
interface MessageService {
public function sendMessage($message, $recipient);
}
class EmailService implements MessageService {
public function sendMessage($message, $recipient) {
echo "Sending email to {$recipient}: {$message}\n";
}
}
class SMSService implements MessageService {
public function sendMessage($message, $recipient) {
echo "Sending SMS to {$recipient}: {$message}\n";
}
}
class NotificationService {
private $messageService;
public function __construct(MessageService $messageService) {
$this->messageService = $messageService;
}
public function sendNotification($message, $recipient) {
$this->messageService->sendMessage($message, $recipient);
}
}
// 의존성 주입 컨테이너 (간단한 버전)
class DIContainer {
private $services = [];
public function register($name, $service) {
$this->services[$name] = $service;
}
public function get($name) {
if (!isset($this->services[$name])) {
throw new Exception("Service not found: {$name}");
}
return $this->services[$name];
}
}
// 사용 예
$container = new DIContainer();
$container->register('email_service', new EmailService());
$container->register('sms_service', new SMSService());
$emailNotification = new NotificationService($container->get('email_service'));
$emailNotification->sendNotification("Hello", "user@example.com");
$smsNotification = new NotificationService($container->get('sms_service'));
$smsNotification->sendNotification("Hi", "0");
이 예제에서 NotificationService는 MessageService 인터페이스에 의존하며, 구체적인 구현(EmailService 또는 SMSService)은 생성자를 통해 주입됩니다. DIContainer는 간단한 의존성 주입 컨테이너 역할을 합니다.
6.3 의존성 주입 패턴의 장단점
- 장점:
- 코드의 결합도를 낮추고 유연성을 높임
- 단위 테스트가 용이해짐 (의존성을 쉽게 모킹할 수 있음)
- 코드의 재사용성과 유지보수성이 향상됨
- 단점:
- 코드의 복잡성이 증가할 수 있음
- 의존성 주입 프레임워크를 사용할 경우 학습 곡선이 존재
- 런타임 시 약간의 성능 오버헤드가 발생할 수 있음
6.4 실제 활용 사례
재능넷과 같은 웹 애플리케이션에서 의존성 주입 패턴은 다음과 같은 상황에서 유용하게 사용될 수 있습니다:
- 데이터베이스 접근: 다양한 데이터베이스 구현(MySQL, PostgreSQL 등)을 쉽게 교체할 수 있도록 함
- 외부 API 통합: 결제 게이트웨이, 소셜 미디어 API 등 외부 서비스와의 통합을 유연하게 관리
- 캐싱 시스템: 다양한 캐시 구현(메모리, 파일, Redis 등)을 쉽게 전환할 수 있도록 함