首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 設計模式:規格模式實作與業務規則封裝

     編輯:PHP 綜合知識

規格模式:將業務規則封裝為可組合、可重用的物件

規格模式 將「某物件是否滿足特定條件」封裝為獨立類別,支援邏輯組合 (AND/OR/NOT),讓業務規則從領域物件中分離,提升可測試性與維護性。

核心介面

<?php
// 規格介面
interface Specification {
    public function isSatisfiedBy(mixed $candidate): bool;
    
    // 組合方法 (預設實作)
    public function and(Specification $other): Specification {
        return new AndSpecification($this, $other);
    }
    
    public function or(Specification $other): Specification {
        return new OrSpecification($this, $other);
    }
    
    public function not(): Specification {
        return new NotSpecification($this);
    }
}

// 組合規格基底
abstract class CompositeSpecification implements Specification {
    protected Specification $spec1;
    protected Specification $spec2;
    
    public function __construct(Specification $spec1, Specification $spec2) {
        $this->spec1 = $spec1;
        $this->spec2 = $spec2;
    }
}

class AndSpecification extends CompositeSpecification {
    public function isSatisfiedBy(mixed $candidate): bool {
        return $this->spec1->isSatisfiedBy($candidate) 
            && $this->spec2->isSatisfiedBy($candidate);
    }
}

class OrSpecification extends CompositeSpecification {
    public function isSatisfiedBy(mixed $candidate): bool {
        return $this->spec1->isSatisfiedBy($candidate) 
            || $this->spec2->isSatisfiedBy($candidate);
    }
}

class NotSpecification implements Specification {
    private Specification $spec;
    public function __construct(Specification $spec) { $this->spec = $spec; }
    public function isSatisfiedBy(mixed $candidate): bool {
        return !$this->spec->isSatisfiedBy($candidate);
    }
}
?>

實戰案例:旅遊推薦系統 - 尋找「溫暖目的地」

<?php
// 1. 領域物件
class Traveler {
    public function __construct(
        public string $name,
        public float $minTemperature // 攝氏度
    ) {}
}

class Destination {
    /** @var float[] 12個月平均溫度 */
    public function __construct(
        public string $name,
        public array $monthlyAvgTemps
    ) {}
    
    public function getAvgTemp(int $month): float {
        return $this->monthlyAvgTemps[$month - 1] ?? 0;
    }
}

class Trip {
    public function __construct(
        public Traveler $traveler,
        public Destination $destination,
        public DateTime $date
    ) {}
}

// 2. 具體規格:溫度達標
class TemperatureSpecification implements Specification {
    public function isSatisfiedBy(mixed $candidate): bool {
        if (!$candidate instanceof Trip) return false;
        
        $month = (int)$candidate->date->format('m');
        $avgTemp = $candidate->destination->getAvgTemp($month);
        return $avgTemp >= $candidate->traveler->minTemperature;
    }
}

// 3. 具體規格:預算範圍內
class BudgetSpecification implements Specification {
    public function __construct(
        private float $maxBudget
    ) {}
    
    public function isSatisfiedBy(mixed $candidate): bool {
        if (!$candidate instanceof Trip) return false;
        // 假設 Destination 有 getEstimatedCost()
        return $candidate->destination->getEstimatedCost() <= $this->maxBudget;
    }
}

// 4. 具體規格:飛行時間限制
class FlightDurationSpecification implements Specification {
    public function __construct(
        private int $maxHours
    ) {}
    
    public function isSatisfiedBy(mixed $candidate): bool {
        if (!$candidate instanceof Trip) return false;
        return $candidate->destination->getFlightHours() <= $this->maxHours;
    }
}

// 5. 使用:組合規格
$traveler = new Traveler("Alice", 20.0); // 最低 20°C

$destinations = [
    new Destination("東京", [5,6,10,15,20,23,26,27,23,18,13,8]),
    new Destination("曼谷", [28,29,30,31,30,29,28,28,28,28,28,27]),
    new Destination("雪梨", [22,22,21,19,16,14,13,14,16,18,19,21]),
    new Destination("新加坡", [27,27,27,27,28,27,27,27,27,27,27,27]),
];

$date = new DateTime('2024-01-15'); // 1月

// 建立組合規格:溫暖 AND 預算5萬內 AND 飛行8小時內
$spec = (new TemperatureSpecification())
    ->and(new BudgetSpecification(50000))
    ->and(new FlightDurationSpecification(8));

echo "適合 {$traveler->name} 的 1 月目的地:\n";
foreach ($destinations as $dest) {
    $trip = new Trip($traveler, $dest, $date);
    if ($spec->isSatisfiedBy($trip)) {
        echo "- {$dest->name} (1月均溫: {$dest->getAvgTemp(1)}°C)\n";
    }
}
// 輸出: 曼谷、新加坡 (東京1月5°C太冷、雪梨雖溫但可能超預算/飛行時間)
?>

進階:規格工廠與參數化規格

<?php
// 參數化規格:動態建立
class ParameterizedSpecFactory {
    public static function minTemperature(float $minTemp): Specification {
        return new class($minTemp) implements Specification {
            private float $min;
            public function __construct(float $min) { $this->min = $min; }
            public function isSatisfiedBy(mixed $candidate): bool {
                if (!$candidate instanceof Trip) return false;
                $month = (int)$candidate->date->format('m');
                return $candidate->destination->getAvgTemp($month) >= $this->min;
            }
        };
    }
    
    public static function maxBudget(float $max): Specification {
        return new BudgetSpecification($max);
    }
    
    public static function maxFlightHours(int $hours): Specification {
        return new FlightDurationSpecification($hours);
    }
}

// 策略工廠:根據用戶偏好動態組合
class TripSpecFactory {
    public static function createForTraveler(Traveler $traveler): Specification {
        $spec = ParameterizedSpecFactory::minTemperature($traveler->minTemperature);
        
        if ($traveler->maxBudget) {
            $spec = $spec->and(ParameterizedSpecFactory::maxBudget($traveler->maxBudget));
        }
        if ($traveler->maxFlightHours) {
            $spec = $spec->and(ParameterizedSpecFactory::maxFlightHours($traveler->maxFlightHours));
        }
        // 可擴展:偏好海島、文化、美食等標籤規格
        
        return $spec;
    }
}

// 使用
$spec = TripSpecFactory::createForTraveler($traveler);
// 等同於手動組合,但集中管理、易於擴展
?>

規格模式在資料庫查詢中的應用

<?php
// 橋接規格到 SQL WHERE 子句
interface SqlSpecification extends Specification {
    public function toSql(): string;
    public function getParameters(): array;
}

class TemperatureSqlSpec implements SqlSpecification {
    private float $minTemp;
    private string $monthColumn;
    
    public function __construct(float $minTemp, string $monthColumn = 'month') {
        $this->minTemp = $minTemp;
        $this->monthColumn = $monthColumn;
    }
    
    public function isSatisfiedBy(mixed $candidate): bool {
        // 記憶體中判斷邏輯
        if (!$candidate instanceof Trip) return false;
        $month = (int)$candidate->date->format('m');
        return $candidate->destination->getAvgTemp($month) >= $this->minTemp;
    }
    
    public function toSql(): string {
        // 簡化:假設資料表有 monthly_temp_1 ... monthly_temp_12 欄位
        return "monthly_temp_{$this->monthColumn} >= ?";
    }
    
    public function getParameters(): array {
        return [$this->minTemp];
    }
}

// 查詢建構器整合
function buildQuery(SqlSpecification $spec): array {
    $sql = "SELECT * FROM destinations WHERE " . $spec->toSql();
    $params = $spec->getParameters();
    return [$sql, $params];
}

// 組合規格需實作複雜的 SQL 組合邏輯 (AndSpecification::toSql() 等)
?>

規格模式優缺點總結

優點 缺點
業務規則獨立封裝,易於單元測試 簡單規則引入過多類別 (Over-engineering)
支援邏輯組合 (AND/OR/NOT),靈活複用 複雜組合可能導致效能問題 (大量物件建立)
符合開放封閉原則:新增規格不修改既有代碼 資料庫查詢轉換複雜 (需 SQL 產生器)
領域語言化:程式碼可讀性高 (領域專家可理解) 學習曲線較陡峭
易於動態組合 (執行時決定規則)

適用場景判斷

  • ✅ 業務規則複雜、頻繁變更、需組合
  • ✅ 規則需在記憶體中驗證物件 (非資料庫)
  • ✅ 規則需序列化儲存、跨系統傳遞
  • ❌ 簡單 CRUD、固定查詢條件
  • ❌ 效能極度敏感、規則極簡單
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved