본문 바로가기
Language/php

디자인 패턴

by 잘먹는 개발자 에단 2025. 2. 4.

1. 싱글턴

- 한번 생성한 인스턴스를 전역적으로 공유한다.

- 데이터베이스 연결에 자주 사용된다.

- DB 연결을 한번만 생성하고 재사용하기 때문에 성능이 최적화된다는 장점이 있다. 

class Database{
	private static $instance = null;
    private $connection;
    
    private function __construct(){
    	$this -> connection = new PDO("mysql:host=localhost;dbname=test", "user", "password");
    }
    
    public static function getInstance(){
    	if(self::$instance === null){
        	self::$instance = new Database();
        }
        return self::$instance;
    }
    
    public function getConnection(){
    	return $this->connection;
    }
}


// 사용은 다음과 같이
$db = Database::getInstance()->getConnection();

 

2. 팩토리

- 객체 생성 로직을 캡슐화하여 유연한 객체 생성이 가능하다.

- SQLCONNECT의 다양한 종류 (DB드라이버)를 지원할 때 유용하다.

- DB 종류가 바뀌어도 코드를 수정할 필요없이 쉽게 관리가능하다는 장점이 있다. 

class DatabaseFactory{
	public static function create($type){
    	switch($type){
        	case 'mysql':
                return new PDO("mysql:host=localhost;dbname=test", "user", "password");
            case 'sqlite':
                return new PDO("sqlite:database.db");
            default:
            	throw new Exception("지원하지 않는 db타입입니다");
        }
    }
}

// 사용은 이렇게
$db = DatabaseFactory::create('mysql');

 

 

3. 데코레이터

- 기존 클래스를 수정없이 기능을 확장하는 패턴

- SQLCONNECT를 확장해서 로그 기능을 추가하는 예시이다.

- 기존 Sqlconnect 코드를 수정하지 않고 기능을 추가할 수 있다.

class SQLCONNECT{
	public function query($sql){
    	// 실제 sql 을 실행하는 코드
        return 'executing: $sql';
    }
}

class LoggingSqlConnect{
	private $db;
    
    public function __construct(SQLCONNECT $db){
    	$this->db = $db;
    }
    
    public function query($sql){
    	echo "[LOG] 실행할 SQL : $sql\n";
        return $this->db->query($sql);
    }
}

// 사용시에는
$baseDB = new SQLCONNECT();
$loggingDB = new LoggingSqlConnect($baseDB);

echo $loggingDB->query("SELECT * FROM users");

 

 

4. 전략

- 실행할 SQL 전략을 변경할 수 있는 구조

- 예) 데이터 가져오는 방식(MySql/Redis캐시) 을 변경할 때 사용

- 전략만 교체하면 캐시를 추가하거나 다른 DB로 변경할 수 있다는 이점이 있다. 

interface QueryStrategy{
	public function execute($sql);
}

// MySql 전략
class MySqlQuery implements QueryStrategy{
	public function execute($sql){
    	return "MySql 실행 : $sql";
    }
}

// Redis 전략
class RedisQuery implements QueryStrategy{
	public function execute($sql){
    	return "Redis 캐시 사용 : $sql";
    }
}

// 컨텍스트 : 전략을 변경할 수 있는 객체
class QueryExecutor{
	private $strategy;
    
    public function __construct(QueryStrategy $strategy){
    	this->strategy = $strategy;
    }
    
    public function execute($sql){
    	return $this->strategy->execute($sql);
    }
}

// 사용 시에는
$mysqlExecutor = new QueryExecutor(new MySqlQuery());
echo $mysqlExecutor->execute("SELECT * FROM users");

$redisExecutor = new QueryExecutor(new RedisQuery());
echo $redisExecutor->execute("SELECT * FROM users");

 

5. 프록시

- 원래 객체를 감싸서 접근을 제어하는 패턴

- SqlConnect에 캐싱, 로깅, 보안 기능을 추가할 때 유용

- 자주 실행되는 SQL을 캐싱해서 성능 최적화가 가능

class SqlConnect{
	public function query($sql){
    	return "Executing : $sql";
    }
}

class SqlProxy{
	private $db;
    private $cache = [];
    
    public function __construct(SqlConnect $db){
    	$this->db = $db;
    }
    
    public function query($sql){
    	if(isset($this->cache[$sql])){
        	return "[CACHE]".$this->cache[$sql];
        }
        $result = $this->db->query($sql);
        $this->cache[$sql] = $result;
        return $result;
    }
}

// 사용 시에는 
$db = new SqlConnect();
$proxyDB = new SqlProxy($db);

echo $proxyDB->query("SELECT * FROM users"); // DB 조회
echo $proxyDB->query("SELECT * FROM users"); // 캐시 사용

6. 옵저버

- 이벤트 발생 시에 여러개의 리스너 ( 구독자 ) 가 자동으로 실행

- DB 변경 시 알림을 보내는 시스템에 유용하다.

- sql 실행 시 여러개의 후속작업 ( 로깅, 알림 )을 자동실행할 수 있다. 

interface Observer {
    public function update($sql);
}

class Logger implements Observer {
    public function update($sql) {
        echo "[LOG] 실행된 SQL: $sql\n";
    }
}

class Notifier implements Observer {
    public function update($sql) {
        echo "[NOTIFY] 관리자에게 알림: $sql 실행됨!\n";
    }
}

class SQLCONNECT {
    private $observers = [];

    public function attach(Observer $observer) {
        $this->observers[] = $observer;
    }

    public function query($sql) {
        echo "Executing: $sql\n";
        foreach ($this->observers as $observer) {
            $observer->update($sql);
        }
    }
}

// 사용 예제
$db = new SQLCONNECT();
$db->attach(new Logger());
$db->attach(new Notifier());

$db->query("DELETE FROM users WHERE id = 1");

 

7. 의존성 주입 DI

- 클래스가 직접 객체를 생성하는 것이 아니라, 외부에서 주입받는다.

- SQLConnect를 주입하여 다양한 DB 연결 객체를 유연하게 사용가능하다.

- 테스트하기 쉽고, db 드라이버 변경이 간편해진다. 

class UserRepository {
    private $db;

    public function __construct(SQLCONNECT $db) {
        $this->db = $db;
    }

    public function getUser($id) {
        return $this->db->query("SELECT * FROM users WHERE id = $id");
    }
}

// 사용 예제
$db = new SQLCONNECT();
$repo = new UserRepository($db);
echo $repo->getUser(1);