設計模式中的那些工廠 Intro 設計模式中有幾個工廠模式,聊一聊這幾個工廠模式的各自用法和使用示例,工廠模式包含簡單工廠,抽象工廠,工廠方法,這些均屬於創建型模式, 所謂創建型模式,就是說這幾個設計模式是用來創建對象的。 簡單工廠 首先來說一說,最簡單的簡單工廠 簡單工廠模式是由一個工廠對象決定創 ...
設計模式中的那些工廠
Intro
設計模式中有幾個工廠模式,聊一聊這幾個工廠模式的各自用法和使用示例,工廠模式包含簡單工廠,抽象工廠,工廠方法,這些均屬於創建型模式,
所謂創建型模式,就是說這幾個設計模式是用來創建對象的。
簡單工廠
首先來說一說,最簡單的簡單工廠
簡單工廠模式是由一個工廠對象決定創建出哪一種產品類的實例
嚴格的來說,簡單工廠模式是工廠模式家族中最簡單實用的模式,但不屬於23種 GOF 設計模式之一。因為每次要新增類型的時候必須修改工廠內部代碼,不符合開閉原則。
來看一個例子:
public class OperationFactory
{
public static Operation CreateOperation(string operate)
{
Operation operation = null;
switch (operate)
{
case "+":
operation = new OperationAdd();
break;
case "-":
operation = new OpertaionSub();
break;
case "*":
operation = new OperationMul();
break;
case "/":
operation = new OperationDiv();
break;
}
return operation;
}
}
這是一個簡單的計算器的示例,支持簡單的加減乘除操作,如果要增加一個操作的話就必須要有增加一個 switch
... case
分支,需要修改 CreateOperation
方法不能滿足對擴展開放對修改關閉的開閉原則,所以普遍地認為簡單工廠不屬於設計模式之一,但是我覺得有時候簡單的業務處理用簡單工廠還是比較方便的。
抽象工廠
抽象工廠模式,提供一系列相關或相互依賴對象的介面,而無需指定他們具體的類。
實現抽象工作模式所需要的組件,主要部分:
- 抽象工廠/抽象產品
- 具體工廠1/具體產品1
- 具體工廠2/具體產品2
- ...
在客戶端根據不同的配置選擇不同的工廠,例如根據配置的資料庫類型的不同選擇使用 Access 資料庫倉儲的工廠還是使用 SqlServer 資料庫的倉儲工廠
示例:
IDbFactory factory = new AccessFactory();
var userRepo = factory.CreateUserRepo();
userRepo.Insert(null);
var departmentRepo = factory.CreateDepartmentRepo();
factory = new SqlServerFactory();
userRepo = factory.CreateUserRepo();
userRepo.Insert(null);
工廠方法
工廠方法模式(Factory Method)定義一個用於創建對象的介面,讓子類決定實例化哪一個類。工廠方法使一個類的實例化延遲到子類。
工廠方法模式實現時,客戶端需要決定實例化哪一個工廠來實現客戶端的操作,也會存在著選擇判斷的問題,不過和簡單工廠相比,簡單工廠的選擇判斷是在工廠內部,而工廠方法則將選擇判斷轉移到了客戶端。
示例:
ILeifengFactory factory = new UndergraduteFactory();
var studentLeifeng = factory.CreateLeifeng();
studentLeifeng.BuyRice();
factory = new VolunteerFactory();
var leifeng1 = factory.CreateLeifeng();
leifeng1.Sweep();
More
工廠模式的作用無外乎下麵這四個。這也是判斷要不要使用工廠模式的最本質的參考標準。
- 封裝變化:創建邏輯有可能變化,封裝成工廠類之後,創建邏輯的變更對調用者透明。
- 代碼復用:創建代碼抽離到獨立的工廠類之後可以復用。
- 隔離複雜性:封裝複雜的創建邏輯,調用者無需瞭解如何創建對象。
- 控制複雜度:將創建代碼抽離出來,讓原本的函數或類職責更單一,代碼更簡潔。
工廠方法和抽象工廠的區別
工廠方法模式:定義一個用於創建對象的介面,讓子類決定實例化哪一個類
抽象工廠模式:為創建一組相關或相互依賴的對象提供一個介面,而且無需指定他們的具體類
區別在於產品,如果產品單一,最合適用工廠模式,但是如果有多個業務品種、業務分類時,通過抽象工廠模式產生需要的對象是一種非常好的解決方式。再通俗深化理解下:工廠模式針對的是一個產品等級結構 ,抽象工廠模式針對的是面向多個產品等級結構的。
抽象工廠關鍵在於產品之間的抽象關係,所以一般至少要兩個產品;工廠方法在於生成產品,不關註產品間的關係,所以可以只生成一個產品。
抽象工廠更像一個複雜版本的策略模式,策略模式通過更換策略來改變處理方式或者結果;而抽象工廠的客戶端,通過更換工廠而改變結果。
工廠方法目的是生產產品,所以能看到產品,而且還要使用產品。當然,如果產品在創建者內部使用,那麼工廠方法就是為了完善創建者,從而可以使用創建者。另外創建者本身是不能更換所生產產品的。
抽象工廠的工廠是類;工廠方法的工廠是方法。抽象工廠的工廠類就做一件事情生產產品。生產的產品給客戶端使用,絕不給自己用。工廠方法生產產品,可以給系統用,可以給客戶端用,也可以自己這個類使用。自己這個類除了這個工廠方法外,還可以有其他功能性的方法。
選擇的優化
簡單工廠因為選擇是在工廠內部的,不符合開閉原則,抽象工廠和工廠方法是將選擇權交給客戶端,由客戶端根據需要自己決定要實例化的工廠。
在實際應用的時候大部分情況是只會使用一種工廠,這種情況我們一般可以藉助反射+配置來優化選擇,如果使用依賴註入,可以直接註入需要的服務即可。
使用反射+配置優化
private static readonly string AssemblyName = "AbstractFactoryPattern";
private static readonly string DbName = ConfigurationHelper.AppSetting("DbName");
public static IUserRepo CreateUserRepo()
{
return (IUserRepo)typeof(DataAccess).Assembly.CreateInstance($"{AssemblyName}.{DbName}UserRepo");
}
public static IDepartmentRepo CreateDepartmentRepo()
{
return (IDepartmentRepo)typeof(DataAccess).Assembly.CreateInstance($"{AssemblyName}.{DbName}DepartmentRepo");
}
使用依賴註入
依賴註入可以使得我們的代碼變得更加良好,擴展性更強。
// 依賴註入
var builder = new ContainerBuilder();
builder.RegisterType<VolunteerFactory>().As<ILeifengFactory>();
builder.RegisterType<SqlServerFactory>().As<IDbFactory>();
var container = builder.Build();
var leifengFactory = container.Resolve<ILeifengFactory>();
var volunteer = leifengFactory.CreateLeifeng();
volunteer.Wash();
var dbFactory = container.Resolve<IDbFactory>();
dbFactory.CreateDepartmentRepo().CreateDepartment(null);
Reference
- https://github.com/WeihanLi/DesignPatterns/blob/master/CreatePattern/SimpleFactoryPattern
- https://github.com/WeihanLi/DesignPatterns/tree/master/CreatePattern/AbstractFactoryPattern
- https://github.com/WeihanLi/DesignPatterns/blob/master/CreatePattern/FactoryMethodPattern