故事背景:
小菜到一家单位面试, 面试官出的题是:用任意一种面向对象语言实现一个计算器控制台程序,要求输入两个数字和运算符号,得到结果即可。
小菜写了程序,功能实现了,但是忽略了一个很关键的问题,考官的问题是用任意一种面向对象语言实现。小菜将所有代码一股脑写到了主函数中,明显是面向过程的思想。
公司要求采用面向对象的方式实现程序,是因为面向对象程序代码可复用性强,容易扩展,容易维护,代码质量更高。下面看看三工厂模式是如何实现的?
简单工厂模式
初识
简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。很多人认为:简单工厂模式不能说是一个设计模式,说它是一种编程习惯可能更恰当些。
优点
工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象。使程序更加灵活,复用性高。
计算器例子UML类图
理解:例如,小菜编译的计算器,根据客户端的选择条件动态实例化相关的类,对于客户来说,去除了与具体产品的依赖。将逻辑判断部分放到工厂类,客户端代码给出具体哪个运算即可。如果要更改界面,实现的逻辑都是一样的,所以工厂代码是可以复用的。
缺点
- 一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
- 使用简单工厂模式将会增加系统中类的个数,在一定程序上增加了系统的复杂度和理解难度。
工厂方法模式
初识
工厂方法模式(Factory Method),定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
简单工厂里将加减乘除运算都放到一个工厂类中,较简单。而工厂方法中有加减乘除工厂子类继承工厂父类,实现一个工厂子类对应一个运算子类。
优点
-
工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。
-
在系统中添加产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
缺点
在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
计算器例子UML类图
实例:雷锋工厂
static void Main(string[] args)
{
IFactory factory = new UndergrauateFactory();
LeiFeng student = factory.CreateLeiFeng();
student.BuyRice();
student.Sweep();
student.Wash();
Console.ReadKey();
}
class LeiFeng
{
public void Sweep()
{
Console.WriteLine("扫地");
}
public void Wash()
{
Console.WriteLine("洗衣");
}
public void BuyRice()
{
Console.WriteLine("买米");
}
}
class Undergraduate:LeiFeng
{
}
class Volunteer:LeiFeng
{
}
interface IFactory//工厂接口
{
LeiFeng CreateLeiFeng();
}
class UndergrauateFactory:IFactory//大学生工厂类
{
public LeiFeng CreateLeiFeng()
{
return new Undergraduate();//实例化大学生
}
}
class VolunteerFactory:IFactory//志愿者工厂类
{
public LeiFeng CreateLeiFeng()
{
return new Volunteer();
}
}
抽象工厂模式
初识
抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
优点
- 易于交换产品系列,由于具体工厂类,在一个应用中只需在初始化时出现一次,这就使得改变一个应用的具体工厂变得很容易,它只需要改变具体工厂即可使用不同的产品配置。
- 它让具体的创建实例过程与客户端分离,客户端是通过他们抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。
缺点
增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性。
实例
实例:一个程序要更换数据库,两种数据库分别为:Sqlserver与Access,其数据库中的表又有User表与Department表(在这进行举例)。
例子UML类图
理解:声明了IUser接口与IDepartment接口,用于客户端访问,在客户端实例化时解除了与具体数据库访问的耦合。
反射
引入命名空间:
using System.Reflection;
是否记得在简单工厂模式中,我们用到了switch或者if。有用到switch和if的地方,我们都可以考虑利用反射技术来去除,以解除分支带来的耦合。
这其实将会引申一个概念:依赖注入(DI),或者称为控制反转,将传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理,什么意思?就是直观上代码里看不到new 对象,这个操作交给了外部容器,这样将会大大降低程序的耦合性。比如:Spring框架的IoC容器。
常规的实例化:
IUser result=new SqlserverUser();
反射实例化的写法:
IUser result=(IUser)Assembly.Load("抽象工厂模式").CreateInstance("抽象工厂模式.SqlserverUser");
反射和直接new有什么区别呢?答案就在于:反射使用的字符串,也就是说可以用变量来处理。而new的常规方法是已经编译好了的,不能随意灵活更换其实例化对象。所以,如果我们用配置文件的方式,能灵活替换我们想要的实例化对象。