蓋一座圓頂,需要的角色其實很多
佛羅倫斯教堂的營建委員會,把這些角色分得很清楚
設計決策交給主建築師,財務與採購交給專責的監理人,各行會的工匠各司其職
沒有人期待同一個人,同時扛下所有這些事
想像一個反事實:如果整座工地,只有一個「主任」,什麼都歸他管
他要懂結構力學,也要會記帳;要盯採購進度,也要處理工人的請假申請
一開始,工地很小,一個人還能應付
但隨著工程越蓋越大,事情越堆越多
他變成了瓶頸,任何決定都要等他點頭,任何一件事出錯,都不知道該找誰
這正是「巨大類別(Large Class)」在程式碼裡的樣子
假設我們要開發一個小型店鋪的管理系統,AI 幫我們生出了一個 ShopManager:
public class ShopManager
{
// --- 商品資料 ---
public List<Product> Products { get; set; } = new();
// --- 庫存 ---
public Dictionary<int, int> StockLevels { get; set; } = new();
// --- 員工排班 ---
public List<Shift> Shifts { get; set; } = new();
// --- 銷售紀錄 ---
public List<Sale> Sales { get; set; } = new();
public void AddProduct(Product product) { /* ... */ }
public void RestockProduct(int productId, int qty)
{
StockLevels[productId] = StockLevels.GetValueOrDefault(productId) + qty;
}
public bool AssignShift(int employeeId, DateTime date)
{
// 檢查排班衝突、法定工時...
Shifts.Add(new Shift(employeeId, date));
return true;
}
public decimal RecordSale(int productId, int qty)
{
StockLevels[productId] -= qty;
var price = Products.First(p => p.Id == productId).Price * qty;
Sales.Add(new Sale(productId, qty, price));
return price;
}
public string GenerateMonthlyReport()
{
var totalRevenue = Sales.Sum(s => s.Amount);
return $"本月營收:{totalRevenue:C},共 {Sales.Count} 筆交易";
}
}
看起來每個方法都不長,單獨看都合理
但整個類別,同時扛了商品管理、庫存、排班、銷售紀錄、報表五種職責
問題不是某個方法寫得不好,是這些職責,本來就不該住在同一個屋簷下。
這就像那個身兼一切的工地主任
不是他能力不夠,是這個角色從一開始就不該由一個人扛
用「這個類別要負責什麼」造句時,如果得用好幾個「和」字才講得完,它大概已經是一個巨大類別了
回到營建委員會的做法:把混在一起的職責,還原成各自獨立的角色
換成程式碼,這是提煉類別 (Extract Class):
public class ProductCatalog
{
private readonly List<Product> _products = new();
public void Add(Product product) => _products.Add(product);
public Product Get(int productId) => _products.First(p => p.Id == productId);
}
public class InventoryTracker
{
private readonly Dictionary<int, int> _stockLevels = new();
public void Restock(int productId, int qty) =>
_stockLevels[productId] = _stockLevels.GetValueOrDefault(productId) + qty;
public void Deduct(int productId, int qty) => _stockLevels[productId] -= qty;
}
public class ShiftScheduler
{
private readonly List<Shift> _shifts = new();
public bool Assign(int employeeId, DateTime date)
{
_shifts.Add(new Shift(employeeId, date));
return true;
}
}
public class SalesReporter
{
private readonly List<Sale> _sales = new();
public void Record(Sale sale) => _sales.Add(sale);
public string GenerateMonthlyReport() =>
$"本月營收:{_sales.Sum(s => s.Amount):C},共 {_sales.Count} 筆交易";
}
原本的 ShopManager,變成一個薄薄的協調者,把工作委派給各自的專責角色:
public class ShopManager
{
private readonly ProductCatalog _catalog;
private readonly InventoryTracker _inventory;
private readonly ShiftScheduler _scheduler;
private readonly SalesReporter _reporter;
public ShopManager(ProductCatalog catalog, InventoryTracker inventory,
ShiftScheduler scheduler, SalesReporter reporter)
{
_catalog = catalog;
_inventory = inventory;
_scheduler = scheduler;
_reporter = reporter;
}
public decimal RecordSale(int productId, int qty)
{
_inventory.Deduct(productId, qty);
var price = _catalog.Get(productId).Price * qty;
_reporter.Record(new Sale(productId, qty, price));
return price;
}
}
四個角色,各自只需要懂自己的那一份工作
改排班規則,不會動到報表;測試銷售邏輯,不需要準備排班資料
不是類別大就一定有問題,判斷的重點在內聚力:
只要有一題答案是肯定的,這個類別大概已經戴了不只一頂帽子
明天我們看一個更隱蔽的問題:
一句 if (customerType == "VIP"),看起來人畜無害,卻可能是整個系統最脆弱的一個環節
模組一第三站:原始型別執念(Primitive Obsession)