在軟件開發中,我們經常會遇到需要與複雜子系統交互的場景。外觀模式通過提供一個統一的簡化接口,隱藏系統的複雜性,讓客户端能夠更輕鬆地使用系統功能。

什麼是外觀模式?

外觀模式(Facade Pattern)是一種結構型設計模式,它為複雜的子系統提供一個統一的簡化接口。這個模式遵循"最少知識原則",讓客户端只需要與一個高層接口交互,而不需要了解底層系統的複雜性。

就像現實世界中的酒店前台:客人不需要直接與客房服務、餐廳、保潔等多個部門打交道,只需要通過前台就能獲得所有服務。

核心概念

  • 外觀(Facade):提供簡化的統一接口
  • 子系統(Subsystem):由多個相互關聯的類組成的複雜系統
  • 客户端(Client):通過外觀接口與系統交互

實戰案例:智能家居控制系統

讓我們通過一個完整的智能家居系統來理解外觀模式的實際應用。

1. 定義子系統組件

// 燈光子系統
class LightingSystem {
    private val lights = mutableMapOf(
        "living_room" to false,
        "bedroom" to false,
        "kitchen" to false
    )
    
    fun turnOn(light: String) {
        lights[light] = true
        println("💡 $light 燈光已打開")
    }
    
    fun turnOff(light: String) {
        lights[light] = false
        println("💡 $light 燈光已關閉")
    }
    
    fun dim(light: String, level: Int) {
        println("💡 $light 燈光調至 $level%")
    }
    
    fun getStatus(): Map<String, Boolean> {
        return lights.toMap()
    }
}

// 空調子系統
class AirConditioningSystem {
    private var isOn = false
    private var temperature = 26.0
    private var mode = "cool" // cool, heat, fan
    
    fun turnOn() {
        isOn = true
        println("❄️ 空調已開啓,温度: ${temperature}℃,模式: $mode")
    }
    
    fun turnOff() {
        isOn = false
        println("❄️ 空調已關閉")
    }
    
    fun setTemperature(temp: Double) {
        temperature = temp
        if (isOn) {
            println("❄️ 空調温度設置為 ${temp}℃")
        }
    }
    
    fun setMode(newMode: String) {
        mode = newMode
        if (isOn) {
            println("❄️ 空調模式設置為 $newMode")
        }
    }
    
    fun getStatus(): String {
        return if (isOn) "運行中 - ${temperature}℃ $mode" else "關閉"
    }
}

// 娛樂子系統
class EntertainmentSystem {
    private var tvOn = false
    private var soundSystemOn = false
    private var currentSource = "TV"
    private var volume = 50
    
    fun turnOnTV() {
        tvOn = true
        println("📺 電視已開啓")
    }
    
    fun turnOffTV() {
        tvOn = false
        println("📺 電視已關閉")
    }
    
    fun turnOnSoundSystem() {
        soundSystemOn = true
        println("🔊 音響系統已開啓")
    }
    
    fun turnOffSoundSystem() {
        soundSystemOn = false
        println("🔊 音響系統已關閉")
    }
    
    fun setSource(source: String) {
        currentSource = source
        println("🔊 信號源切換到 $source")
    }
    
    fun setVolume(level: Int) {
        volume = level.coerceIn(0, 100)
        println("🔊 音量設置為 $volume")
    }
    
    fun getStatus(): String {
        return "TV: ${if (tvOn) "開" else "關"}, 音響: ${if (soundSystemOn) "開" else "關"}"
    }
}

// 安防子系統
class SecuritySystem {
    private var alarmArmed = false
    private var doorsLocked = false
    private var camerasOn = false
    
    fun armAlarm() {
        alarmArmed = true
        println("🚨 報警系統已佈防")
    }
    
    fun disarmAlarm() {
        alarmArmed = false
        println("🚨 報警系統已撤防")
    }
    
    fun lockDoors() {
        doorsLocked = true
        println("🔒 所有門已上鎖")
    }
    
    fun unlockDoors() {
        doorsLocked = false
        println("🔒 所有門已解鎖")
    }
    
    fun startSurveillance() {
        camerasOn = true
        println("📹 監控系統已啓動")
    }
    
    fun stopSurveillance() {
        camerasOn = false
        println("📹 監控系統已停止")
    }
    
    fun getStatus(): String {
        return "報警: ${if (alarmArmed) "佈防" else "撤防"}, 門鎖: ${if (doorsLocked) "鎖定" else "未鎖"}, 監控: ${if (camerasOn) "開啓" else "關閉"}"
    }
}

// 窗簾子系統
class CurtainSystem {
    private val curtains = mutableMapOf(
        "living_room" to 0, // 0-100 表示打開百分比
        "bedroom" to 0
    )
    
    fun open(curtain: String) {
        curtains[curtain] = 100
        println("🪟 $curtain 窗簾已完全打開")
    }
    
    fun close(curtain: String) {
        curtains[curtain] = 0
        println("🪟 $curtain 窗簾已完全關閉")
    }
    
    fun setOpenPercentage(curtain: String, percentage: Int) {
        curtains[curtain] = percentage.coerceIn(0, 100)
        println("🪟 $curtain 窗簾打開 ${percentage}%")
    }
    
    fun getStatus(): Map<String, Int> {
        return curtains.toMap()
    }
}

2. 創建智能家居外觀類

// 智能家居外觀類 - 統一入口
class SmartHomeFacade(
    private val lighting: LightingSystem = LightingSystem(),
    private val ac: AirConditioningSystem = AirConditioningSystem(),
    private val entertainment: EntertainmentSystem = EntertainmentSystem(),
    private val security: SecuritySystem = SecuritySystem(),
    private val curtains: CurtainSystem = CurtainSystem()
) {
    
    // 場景模式:離家模式
    fun leaveHome() {
        println("\n🏠 啓動離家模式...")
        
        // 關閉所有燈光
        lighting.getStatus().keys.forEach { light ->
            if (lighting.getStatus()[light] == true) {
                lighting.turnOff(light)
            }
        }
        
        // 關閉空調
        ac.turnOff()
        
        // 關閉娛樂系統
        entertainment.turnOffTV()
        entertainment.turnOffSoundSystem()
        
        // 關閉窗簾
        curtains.getStatus().keys.forEach { curtain ->
            curtains.close(curtain)
        }
        
        // 啓動安防系統
        security.lockDoors()
        security.armAlarm()
        security.startSurveillance()
        
        println("✅ 離家模式設置完成")
    }
    
    // 場景模式:回家模式
    fun arriveHome() {
        println("\n🏠 啓動回家模式...")
        
        // 撤防安防系統
        security.disarmAlarm()
        security.unlockDoors()
        
        // 打開入口燈光
        lighting.turnOn("living_room")
        
        // 打開窗簾
        curtains.open("living_room")
        
        // 設置舒適温度
        ac.setTemperature(24.0)
        ac.turnOn()
        
        // 背景音樂
        entertainment.turnOnSoundSystem()
        entertainment.setSource("Music")
        entertainment.setVolume(30)
        
        println("✅ 回家模式設置完成")
    }
    
    // 場景模式:影院模式
    fun startMovieNight() {
        println("\n🎬 啓動影院模式...")
        
        // 調暗燈光
        lighting.getStatus().keys.forEach { light ->
            lighting.dim(light, 20)
        }
        
        // 關閉窗簾
        curtains.getStatus().keys.forEach { curtain ->
            curtains.close(curtain)
        }
        
        // 開啓娛樂系統
        entertainment.turnOnTV()
        entertainment.turnOnSoundSystem()
        entertainment.setSource("TV")
        entertainment.setVolume(60)
        
        // 調整空調温度
        ac.setTemperature(22.0)
        
        println("✅ 影院模式設置完成")
    }
    
    // 場景模式:睡眠模式
    fun startSleepMode() {
        println("\n😴 啓動睡眠模式...")
        
        // 關閉所有燈光
        lighting.getStatus().keys.forEach { light ->
            lighting.turnOff(light)
        }
        
        // 關閉娛樂系統
        entertainment.turnOffTV()
        entertainment.turnOffSoundSystem()
        
        // 關閉客廳窗簾,打開卧室窗簾50%
        curtains.close("living_room")
        curtains.setOpenPercentage("bedroom", 50)
        
        // 設置睡眠温度
        ac.setTemperature(26.0)
        ac.setMode("fan")
        
        // 啓動夜間安防
        security.armAlarm()
        security.startSurveillance()
        
        println("✅ 睡眠模式設置完成")
    }
    
    // 單獨控制方法
    fun controlLighting(light: String, action: String, level: Int? = null) {
        when (action) {
            "on" -> lighting.turnOn(light)
            "off" -> lighting.turnOff(light)
            "dim" -> level?.let { lighting.dim(light, it) }
        }
    }
    
    fun controlTemperature(temp: Double) {
        ac.setTemperature(temp)
        if (!ac.getStatus().contains("運行中")) {
            ac.turnOn()
        }
    }
    
    // 獲取整體狀態
    fun getHomeStatus(): Map<String, Any> {
        return mapOf(
            "lighting" to lighting.getStatus(),
            "ac" to ac.getStatus(),
            "entertainment" to entertainment.getStatus(),
            "security" to security.getStatus(),
            "curtains" to curtains.getStatus()
        )
    }
    
    // 節能模式
    fun enableEnergySaving() {
        println("\n🌱 啓動節能模式...")
        
        // 關閉不必要的設備
        lighting.getStatus().forEach { (light, isOn) ->
            if (isOn && light != "living_room") {
                lighting.turnOff(light)
            }
        }
        
        // 調整空調設置
        ac.setTemperature(28.0)
        curtains.close("living_room")
        
        println("✅ 節能模式設置完成")
    }
}

3. 客户端使用

// 移動APP界面
class SmartHomeApp(private val smartHome: SmartHomeFacade) {
    
    fun demonstrateScenarios() {
        println("=" * 50)
        println("🏠 智能家居控制系統演示")
        println("=" * 50)
        
        // 演示回家模式
        smartHome.arriveHome()
        displayStatus()
        
        Thread.sleep(2000)
        
        // 演示影院模式
        smartHome.startMovieNight()
        displayStatus()
        
        Thread.sleep(2000)
        
        // 演示睡眠模式
        smartHome.startSleepMode()
        displayStatus()
        
        Thread.sleep(2000)
        
        // 演示離家模式
        smartHome.leaveHome()
        displayStatus()
        
        Thread.sleep(2000)
        
        // 演示節能模式
        smartHome.enableEnergySaving()
        displayStatus()
        
        // 演示單獨控制
        println("\n🎛️ 單獨控制演示")
        smartHome.controlLighting("bedroom", "on")
        smartHome.controlTemperature(22.0)
    }
    
    fun displayStatus() {
        println("\n📊 當前家居狀態:")
        val status = smartHome.getHomeStatus()
        status.forEach { (system, state) ->
            println("   $system: $state")
        }
        println()
    }
    
    // 語音控制接口
    fun voiceCommand(command: String) {
        when (command.toLowerCase()) {
            "我回家了" -> smartHome.arriveHome()
            "我要看電影" -> smartHome.startMovieNight()
            "我要睡覺了" -> smartHome.startSleepMode()
            "我要出門了" -> smartHome.leaveHome()
            "節能模式" -> smartHome.enableEnergySaving()
            else -> println("❌ 無法識別的指令: $command")
        }
        displayStatus()
    }
}

// 網頁控制界面
class WebControlPanel(private val smartHome: SmartHomeFacade) {
    
    fun renderDashboard() {
        val status = smartHome.getHomeStatus()
        
        println("""
        <div class="dashboard">
            <h1>智能家居控制面板</h1>
            <div class="status">
                <h2>當前狀態</h2>
                <p>燈光: ${status["lighting"]}</p>
                <p>空調: ${status["ac"]}</p>
                <p>娛樂: ${status["entertainment"]}</p>
                <p>安防: ${status["security"]}</p>
                <p>窗簾: ${status["curtains"]}</p>
            </div>
            <div class="controls">
                <button onclick="arriveHome()">回家模式</button>
                <button onclick="leaveHome()">離家模式</button>
                <button onclick="movieNight()">影院模式</button>
                <button onclick="sleepMode()">睡眠模式</button>
            </div>
        </div>
        """.trimIndent())
    }
}

4. 測試代碼

fun main() {
    // 創建智能家居系統
    val smartHome = SmartHomeFacade()
    
    // 移動APP演示
    val app = SmartHomeApp(smartHome)
    app.demonstrateScenarios()
    
    // 語音控制測試
    println("\n🎤 語音控制測試:")
    app.voiceCommand("我回家了")
    app.voiceCommand("我要看電影")
    app.voiceCommand("節能模式")
    
    // 網頁面板演示
    println("\n💻 網頁控制面板:")
    val webPanel = WebControlPanel(smartHome)
    webPanel.renderDashboard()
    
    // 性能測試:直接操作 vs 使用外觀模式
    println("\n⏱️ 性能對比測試:")
    testDirectAccess()
    testFacadeAccess()
}

// 測試直接操作子系統的複雜性
fun testDirectAccess() {
    val startTime = System.currentTimeMillis()
    
    // 直接操作各個子系統實現回家模式
    val lighting = LightingSystem()
    val ac = AirConditioningSystem()
    val entertainment = EntertainmentSystem()
    val security = SecuritySystem()
    val curtains = CurtainSystem()
    
    // 實現回家模式需要調用多個方法
    security.disarmAlarm()
    security.unlockDoors()
    lighting.turnOn("living_room")
    curtains.open("living_room")
    ac.setTemperature(24.0)
    ac.turnOn()
    entertainment.turnOnSoundSystem()
    entertainment.setSource("Music")
    entertainment.setVolume(30)
    
    val duration = System.currentTimeMillis() - startTime
    println("直接操作耗時: ${duration}ms (需要了解所有子系統細節)")
}

// 測試使用外觀模式的簡便性
fun testFacadeAccess() {
    val startTime = System.currentTimeMillis()
    
    val smartHome = SmartHomeFacade()
    smartHome.arriveHome() // 一行代碼完成複雜操作
    
    val duration = System.currentTimeMillis() - startTime
    println("外觀模式耗時: ${duration}ms (簡單易用)")
}

外觀模式的進階應用

1. 分層外觀模式

// 基礎外觀
open class BasicHomeFacade(
    protected val lighting: LightingSystem,
    protected val ac: AirConditioningSystem
) {
    open fun basicControl() {
        println("基礎家居控制")
    }
}

// 高級外觀繼承基礎外觀
class AdvancedHomeFacade(
    lighting: LightingSystem,
    ac: AirConditioningSystem,
    private val entertainment: EntertainmentSystem
) : BasicHomeFacade(lighting, ac) {
    
    override fun basicControl() {
        super.basicControl()
        println("增強的基礎控制")
    }
    
    fun advancedScenarios() {
        startMovieNight()
        startPartyMode()
    }
    
    private fun startPartyMode() {
        println("🎉 啓動派對模式")
        // 複雜的場景設置
    }
}

2. 動態外觀模式

// 支持動態配置的外觀
class DynamicFacade {
    private val subsystems = mutableMapOf<String, Any>()
    
    fun registerSubsystem(name: String, subsystem: Any) {
        subsystems[name] = subsystem
    }
    
    fun executeScenario(scenario: Map<String, List<String>>) {
        scenario.forEach { (subsystemName, actions) ->
            val subsystem = subsystems[subsystemName]
            actions.forEach { action ->
                executeAction(subsystem, action)
            }
        }
    }
    
    private fun executeAction(subsystem: Any?, action: String) {
        // 使用反射動態調用方法
        try {
            val method = subsystem!!::class.java.getMethod(action)
            method.invoke(subsystem)
        } catch (e: Exception) {
            println("無法執行操作: $action on $subsystem")
        }
    }
}

外觀模式在Android開發中的應用

1. 多媒體播放器外觀

class MediaPlayerFacade(
    private val audioManager: AudioManager,
    private val mediaPlayer: MediaPlayer,
    private val notificationManager: NotificationManager
) {
    
    fun playMedia(mediaUri: Uri, playInBackground: Boolean = false) {
        // 管理音頻焦點
        audioManager.requestAudioFocus(
            audioFocusRequestBuilder.build()
        )
        
        // 配置媒體播放器
        mediaPlayer.setDataSource(mediaUri.toString())
        mediaPlayer.prepareAsync()
        
        // 管理通知
        if (playInBackground) {
            showMediaNotification()
        }
        
        mediaPlayer.setOnPreparedListener {
            it.start()
        }
    }
    
    fun stopMedia() {
        mediaPlayer.stop()
        mediaPlayer.release()
        audioManager.abandonAudioFocus(null)
        hideMediaNotification()
    }
    
    private fun showMediaNotification() {
        // 創建媒體播放通知
    }
    
    private fun hideMediaNotification() {
        // 隱藏通知
    }
}

2. 網絡請求外觀

class NetworkFacade(
    private val okHttpClient: OkHttpClient,
    private val gson: Gson,
    private val cacheManager: CacheManager
) {
    
    suspend fun <T> request(
        url: String,
        method: String = "GET",
        body: Any? = null,
        responseType: Class<T>
    ): Result<T> {
        return try {
            // 檢查緩存
            val cached = cacheManager.get<T>(url)
            if (cached != null) {
                return Result.success(cached)
            }
            
            // 構建請求
            val request = buildRequest(url, method, body)
            
            // 執行網絡請求
            val response = okHttpClient.newCall(request).execute()
            
            // 解析響應
            val result = parseResponse<T>(response, responseType)
            
            // 緩存結果
            result.onSuccess { data ->
                cacheManager.put(url, data)
            }
            
            result
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
    
    private fun <T> parseResponse(response: Response, responseType: Class<T>): Result<T> {
        // 簡化實現
        return try {
            val json = response.body?.string() ?: ""
            val result = gson.fromJson(json, responseType)
            Result.success(result)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

外觀模式的優缺點

✅ 優點

  1. 簡化接口:為複雜系統提供簡單統一的接口
  2. 解耦合:客户端與子系統解耦,提高獨立性
  3. 易於使用:降低學習成本,提高開發效率
  4. 提高可維護性:子系統變化不影響客户端

❌ 缺點

  1. 不夠靈活:可能無法滿足所有特殊需求
  2. 增加層數:多了一個抽象層
  3. 可能成為上帝對象:如果外觀類過於龐大,違背單一職責原則

最佳實踐

  1. 合理劃分:根據業務領域劃分外觀,避免上帝對象
  2. 保持簡潔:外觀接口應該簡單明瞭
  3. 分層設計:複雜系統可以使用分層外觀
  4. 適度使用:不是所有系統都需要外觀模式

總結

外觀模式通過提供統一的簡化接口,有效降低了複雜系統的使用難度。在智能家居案例中,我們看到:

  • 🎯 簡化複雜性:將多個子系統的複雜操作封裝成簡單場景
  • 🔄 提高可用性:用户無需瞭解底層技術細節
  • 🏗️ 良好封裝:子系統變化不影響客户端代碼
  • 📱 多客户端支持:同時支持APP、網頁、語音等多種客户端

適用場景

  • 複雜子系統需要提供簡單接口
  • 構建分層系統結構
  • 需要解耦客户端和子系統
  • 為遺留系統提供現代化接口