1. 引入
在了解工厂模式之前,首先,明确什么是工厂?一个简单的词汇,我们很少会去深究其初始含义。最初的工厂是建立于英国殖民地的手工工厂,将从事手工业的工人聚集在一起,流程化管理和分配任务,提高生产效率。当前的工厂主要指,包括一些机械设备和机器的生产线,产出某一类或某些类别的产品。从概念出发,工厂内主要包含两类角色:
- 工厂
- 抽象工厂
- 具体工厂
- 产品
- 抽象产品
- 具体产品
其中,抽象产品,指具体产品的品类,抽象工厂,或称“超级工厂”,指具体工厂的抽象,即工厂的工厂。举例说明,如太平鸟休闲裤、太平鸟T恤、太平鸟羽绒服均为具体产品,属于【太平鸟服装】,然而,太平鸟除了服装外,可能包含箱包产品、首饰产品等,此时,太平鸟即为抽象产品;假设AAAA为生产太平鸟产品的工厂,则AAAA为具体工厂。此时,如果我们想要购买香奈儿的衣服、箱包和首饰,就无法从太平鸟工厂中获取到,因此,我们可以增加一个抽象工厂,可以生产各类品牌的产品。抽象工厂内选择对应的抽象产品,具体工厂继承抽象工厂,具体工厂中创建对应的产品。
简单工厂的本质是,工厂根据传入的参数,动态的决定应该使用哪一个产品(产品是接口的具体实现)。其中涉及三类角色:
(1)工厂角色。负责创建所有实例。工厂类中创建产品类的方法可以被外界直接调用,创建所需的产品对象。
(2)抽象产品角色。简单工厂模式所创建的所有对象的父类,负责描述所有实例共有的公共接口。
(3)具体产品角色。是简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。
2. 场景实现
场景描述:使用c#、java、c、VB任意一种语言实现一个计算器控制台应用程序,要求输入两个数和运算符号,得到结果。
2.1 代码实现
class Program { static void Main(string[] args) { Console.WriteLine("请输入数字1"); string num1 = Console.ReadLine(); Console.WriteLine("请输入数字2"); string num2 = Console.ReadLine(); Console.WriteLine("请输入运算符号"); string sysbol = Console.ReadLine(); string result = ""; switch (sysbol) { case "+":result = Convert.ToString(Convert.ToDouble(num1)+Convert.ToDouble(num2)); break; case "-":result = Convert.ToString(Convert.ToDouble(num1) - Convert.ToDouble(num2)); break; case "*": result = Convert.ToString(Convert.ToDouble(num1) * Convert.ToDouble(num2)); break; case "/": result = Convert.ToString(Convert.ToDouble(num1) / Convert.ToDouble(num2)); break; } Console.WriteLine("result={0}",result); Console.OutputEncoding = Encoding.UTF8; } }
2.2 代码优化
目前来说,实现加减乘除的功能已经实现,然而程序不易维护扩展,更不易复用。因而,我们可以考虑通过封装、继承、多态把程序的耦合度降低,用设计模式令程序更加灵活易修改。
假设此时想要写一个Windows的计算器方法,则目前的代码就无法复用,那么我们可以考虑将计算器的实现部分和控制台的输出部分进行分离,即让业务逻辑和界面逻辑分开,减低他们的耦合度达到易维护易扩展。此时,计算器的实现部分就可实现复用。
class compute { public static double getResult(double num1,double num2,string operation) { double result = 0.0; switch (operation) { case "+": result = num1 + num2; break; case "-": result = num1 - num2; break; case "*": result = num1 * num2; break; case "/": if (num2 != 0) result = num1 / num2; else Console.Write("除数不能为0"); break; } return result; } }
static void Main(string[] args) { Console.WriteLine("请输入数字1"); string num1 = Console.ReadLine(); Console.WriteLine("请输入数字2"); string num2 = Console.ReadLine(); Console.WriteLine("请输入运算符号"); string sysbol = Console.ReadLine(); string result = Convert.ToString(compute.getResult(Convert.ToInt32(num1), Convert.ToInt32(num2), sysbol)); Console.WriteLine("Hello World!"); }
2.3 代码优化2
上述代码虽然实现了控制器与具体方法实现的解耦(界面分离),但如果需要增加其他运算符号时,依然需要编译已写好的代码,此时可能导致修改时会令原有正常运行的代码在修改时不小心改动发生错误。因而,我们可以考虑创建一个运算类(父类),令其他加减乘除的运算类继承他,从而达到修改或增加较少一个子类不影响其他类的运行。
class Operate { public int _num1; public int _num2; public double num1 { get { return _num1; } set { num1 = _num1; } } public double num2 { get { return _num2; } set { num2 = _num2; } } public virtual double getResult(double n1,double n2) { double result = 0.0; return result; } } class Add:Operate { public override double getResult(double n1, double n2) { return n1 + n2; } } class Sub : Operate { public override double getResult(double n1, double n2) { return n1 - n2; } } class Multiply : Operate { public override double getResult(double n1, double n2) { return n1 * n2; } } class Divide: Operate { public override double getResult(double n1, double n2) {
if (n2 == 0)
throw new Exception("除数不能为0");
return n1 / n2;
}
}
static void Main(string[] args) { Console.WriteLine("请输入数字1"); string num1 = Console.ReadLine(); Console.WriteLine("请输入数字2"); string num2 = Console.ReadLine(); Operate sysbol = new Add(); double result= sysbol.getResult(Convert.ToDouble(num1),Convert.ToDouble(num2)); Console.WriteLine("{0}", result); }
2.3 代码优化——简单工厂模式
上述代码实现了类的分离,并能够根据对象实例化,但是必须知道需要调用哪个类,无法根据输入的符号进行判断。因此,如何实现对象实例化,实例化谁,将来要增加的运算怎么处理,将这些用一个单独的工厂类实现。完整代码如下:
class Operate { public int _num1; public int _num2; public double num1 { get { return _num1; } set { num1 = _num1; } } public double num2 { get { return _num2; } set { num2 = _num2; } } public virtual double getResult(double n1,double n2) { double result = 0.0; return result; } } class Add:Operate { public override double getResult(double n1, double n2) { return n1 + n2; } } class Sub : Operate { public override double getResult(double n1, double n2) { return n1 - n2; } } class Multiply : Operate { public override double getResult(double n1, double n2) { return n1 * n2; } } class Divide: Operate { public override double getResult(double n1, double n2) { if (n2 == 0) throw new Exception("除数不能为0"); return n1 / n2; } } class Factory { /// <summary> /// 工厂通过输入符号实例化对象 /// </summary> /// <param name="operation">运算符号</param> /// <returns></returns> public static Operate createOperate(string operation) { Operate oper = null; switch (operation) { case "+": oper = new Add();break; case "-":oper = new Sub();break; case "*": oper = new Multiply();break; case "/":oper = new Divide();break; } return oper; } } class Program { static void Main(string[] args) { Console.WriteLine("请输入数字1"); string num1 = Console.ReadLine(); Console.WriteLine("请输入数字2"); string num2 = Console.ReadLine(); Operate ope = Factory.createOperate("+"); double result= ope.getResult(Convert.ToDouble(num1), Convert.ToDouble(num2)); Console.WriteLine("{0}",result); } }
这种实现中,只需输入运算符号,工厂就可以实例化出合适的对象,通过多态返回父类的方式实现了计算器的结果。此时,不论是控制台程序、Windows程序、web程序或其他手机程序,都可以用上述的工厂方法和运算类。需要增加运算时,只需再增加一个子类继承Operation,并在工厂方法的switch中添加一个分支即可。封装、继承、多态是面向对象的3大特性,也是理解设计模式的基础。
3 简单工厂的UML图
4. 应用场景
简单工厂模式主要适用于抽象子类的业务逻辑相同,但具体实现不同的情况。不同的操作子类执行同样的方法。如以上给出的实现计算器功能,运算的操作都是针对两个数和一个运算方法,只是运算操作不同,就可以使用简单工厂模式。例如,一个人想 开车,家里有多种类型的车(如跑车、越野车、两厢车),它们都能实现跑(run)的功能,选择哪一辆车即对哪个对象实例化,由于可能还会增加或减少车,我们考虑用一个类创建实例。也是简单工厂的应用。
5. 优劣势
优点:
- 客户端不需要了解如何创建产品,完全和产品逻辑解耦,只需要发送指令去“消费”就好,职责明确;
- 将初始化实例时的工作放到工厂里进行,使代码维护更易。更符合面向对象的原则。
缺点:需要增加一个具体对象时,需要在工厂类中增减代码,违背了面向对象设计的“开放-封闭”原则;
6. 总结
简单工厂创建子类实例并传给外界时只需要知道抽象子类对应的参数即可,而不需要知道抽象子类的创建过程,在外界使用时甚至不用引入抽象子类。简单工厂明确区分了各个子类的职责和权力,有利于整个软件体系的优化。
主要参考:《大话设计模式》