本文透過一個「媒體播放器」需求演進,展示如何應用 OOP 原則、重構技法與設計模式,將僵化的程式碼逐步轉化為靈活、可擴充、符合開放封閉原則 (OCP) 的架構。
public class MediaPlayer
{
public void Play(string audioType)
{
switch (audioType.ToLower())
{
case "mp3": PlayMp3(); break;
case "wav": PlayWav(); break;
}
}
private void PlayMp3() => MessageBox.Show("Play mp3");
private void PlayWav() => MessageBox.Show("Play wav");
}
缺點:新增格式需修改核心類別、違反 OCP、測試困難、Switch 膨脹。
public abstract class AudioMedia { public abstract void Play(); }
public class Mp3 : AudioMedia { public override void Play() => ... }
public class Wav : AudioMedia { public override void Play() => ... }
public class MediaPlayer
{
public void Play(AudioMedia media) => media.Play(); // 多態呼叫
}
進步:新增格式只需新增類別,MediaPlayer 不動。但僅支援音訊。
public interface IMedia { void Play(); }
public abstract class AudioMedia : IMedia { public abstract void Play(); }
public abstract class VideoMedia : IMedia { public abstract void Play(); }
public class Mp3 : AudioMedia { ... }
public class Rm : VideoMedia { ... }
public class MediaPlayer
{
public void Play(IMedia media) => media.Play(); // 依賴抽象
}
public interface IMediaFactory { IMedia CreateMedia(); }
public class Mp3Factory : IMediaFactory { public IMedia CreateMedia() => new Mp3(); }
public class RmFactory : IMediaFactory { public IMedia CreateMedia() => new Rm(); }
// 呼叫端仍有 switch
IMediaFactory factory = type switch { "mp3" => new Mp3Factory(), "rm" => new RmFactory() };
IMedia media = factory.CreateMedia();
media.Play();
App.config
<appSettings>
<add key="mp3" value="MyApp.Media.Mp3Factory" />
<add key="wav" value="MyApp.Media.WavFactory" />
<add key="rm" value="MyApp.Media.RmFactory" />
</appSettings>
表單載入時動態填充下拉選單
private void Form_Load(object sender, EventArgs e)
{
foreach (string key in ConfigurationManager.AppSettings.AllKeys)
cboMediaType.Items.Add(key);
cboMediaType.SelectedIndex = 0;
}
播放按鈕:純反射建立工廠
private void btnPlay_Click(object sender, EventArgs e)
{
string type = cboMediaType.SelectedItem.ToString().ToLower();
string factoryTypeName = ConfigurationManager.AppSettings[type];
// 從 MediaLibrary 組件建立工廠實例
IMediaFactory factory = (IMediaFactory)Activator.CreateInstance(
"MediaLibrary", factoryTypeName).Unwrap();
IMedia media = factory.CreateMedia();
media.Play(); // MediaPlayer 類別已可移除
}
MediaLibrary 專案新增 Avi : VideoMedia 並實作 Play()。AviFactory : IMediaFactory 回傳 new Avi()。App.config 新增 <add key="avi" value="MyApp.Media.AviFactory" />。
+----------------+ +----------------+ +----------------+
| UI (ComboBox) | --> | App.config | --> | Activator |
+----------------+ +----------------+ +----------------+
|
v
+----------------+ +----------------+ +----------------+
| IMedia | <-- | Factory | <-- | Concrete |
| (Interface) | | (Interface) | | Factory |
+----------------+ +----------------+ +----------------+
^ ^ ^
| | |
+----------------+ +----------------+ +----------------+
| Mp3/Wav/Rm/.. | | Mp3Factory/.. | | new Mp3() |
| (Concrete) | | (Concrete) | | |
+----------------+ +----------------+ +----------------+
| 原則 | 本例體現 |
|---|---|
| 單一職責 (SRP) | 播放邏輯、建立邏輯、組態讀取分離。 |
| 開放封閉 (OCP) | 新增格式擴充類別與組態,不修改既有程式碼。 |
| 里氏替換 (LSP) | 所有具體媒體可替換 IMedia 介面。 |
| 介面隔離 (ISP) | IMedia 僅定義 Play(),精簡契約。 |
| 依賴反轉 (DIP) | 高層 UI 依賴抽象 IMediaFactory,不依賴具體類別。 |
從 switch 到 App.config + 反射,我們見證了「給程式碼裝上翅膀」的過程。掌握 抽象介面、工廠模式、組態驅動、反射建立 四大關鍵,即能應對多變需求,享受「神清氣閒」的架構之美。