一、简介(Brief Introduction)
提供一个创建型一系列相关或相互依赖对象的接口,而无需指定它们具体的类。抽象工厂模式是工厂模式家族中最为抽象和最具一般性的一种形态。
抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象 ,而且使用抽象工厂模式还要满足一下条件:
1)系统中有多个产品族,而系统一次只可能消费其中一族产品。
2)同属于同一个产品族的产品以其使用。
例如一个应用,需要在三个不同平台上运行Windows、Linux、Android(Google发布的 智能终端操作系统)上运行,你会怎么设计?分别设计三套不同的应用?肯定不是,通过抽象工厂模式屏蔽掉操作系统对应用的影响。三个不同操作系统上的软件功 能、应用逻辑、UI都应该是非常类似,唯一不同的是调用不同的工厂方法,由不同的产品类去处理与操作系统交互的信息。
二、模式分析(Analysis)
抽象工厂模式的各个角色(和工厂方法一样):
1)抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
2)具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。
3)抽象产品角色:它是具体产品继承的父类或者是实现的接口。
4)具体产品角色:具体工厂角色所创建的对象就是此角色的实例。
三、案例分析(Example)
namespace 抽象工厂反射
{1、客户端
class Program { static void Main(string[] args) { User user = new User() ; Department dept = new Department(); IUser iu = DataAccess.CreateUser(); iu.Insert(user); iu.GetUser(1); IDepartment id = DataAccess.CreateDepartment(); iu.Insert(user); iu.GetUser(1); Console.Read(); } }
2、SqlserverUser来访问Sqlserver的User
class SqlserverUser:IUser { public void Insert(User user) { Console.WriteLine("在SQL中给User表添加一条记录"); } public User GetUser(int id) { Console.WriteLine("在SQL中根据ID得到User表一条记录"); return null; } }
3、AcessUser来访问Acess的User
class AcessUser:IUser { public void Insert(User user) { Console.WriteLine("在Acess中给User表增加一条记录"); } public User GetUser(int id) { Console.WriteLine("在Acess中根据ID得到User表一条记录"); return null; } }
4、User表两个字段的设置
class User { private int _id; public int ID { get { return _id; } set { _id = value; } } private string _name; public string Name { get { return _name; } set { _name = value; } } }
5、用于操作User表class SqlseverUser { public void Insert(User user) { Console.WriteLine("在SQL Server中给User表增加一条记录"); } public User GetUser(int id) { Console.WriteLine("在SQL Server中根据ID得到User表一条记录"); return null; } } class AccessUser { public void Insert(User user) { Console.WriteLine("在Acess中给User表增加一条记录"); } public User GetUser(int id) { Console.WriteLine("在Access中根据ID得到User表一条记录"); return null; } }
6、IDepartment接口用于客户端的访问,解除与具体数据库的访问
interface IDepartment { void Insert(Department department); Department GetDepartment(int id); } interface IUser { void Insert(User user); User GetUser(int id); }
7、反射的使用,消除swith或if语句(这里是字符串,可通过变量处理,根据需要更换)
class DataAccess { private static readonly string AssemblyName = "抽象工厂模式"; private static readonly string db = "Sqlserver"; public static IUser CreateUser() { string className = AssemblyName + "." + db + "User"; return(IUser)Assembly.Load(AssemblyName).CreateInstance(className); } public static IDepartment CreateDepartment() { string className=AssemblyName+"."+db+"Department"; return(IDepartment)Assembly.Load(AssemblyName).CreateInstance(className); } }
8、 同User
class Department { private int _id; public int ID { get { return _id; } set { _id = value; } } private string _name; public string Name { get { return _name; } set { _name = value; } } } class SqlserverDepartment : IDepartment { public void Insert(Department department) { Console.WriteLine("在SQL中Department表增加一条记录"); } public Department GetDepartment(int id) { Console.WriteLine("在SQL中根据ID得到Deparment表一条记录"); return null; } } class AccessDepartment : IDepartment { public void Insert(Department department) { Console.WriteLine("在SQL中Department表增加一条记录"); } public Department GetDepartment(int id) { Console.WriteLine("在SQL中根据ID得到Deparment表一条记录"); return null; } } }
四、解决的问题(What To Solve)
1.系统不依赖于产品类实例如何被创建,组合和表达的细节。
2.系统的产品有多于一个的产品族,而系统只消费其中某一族的产品(抽象工厂模式的原始用Unix&Windows)
3.同属于同一个产品族是在一起使用的。这一约束必须在系统的设计中体现出来。
4.系统提供一个产品类的库,所有产品以同样的接口出现,从而使客户端不依赖于实现。
五、优缺点(Advantage and Disadvantage)
优点
- 封装性,每个产品的实现类不是高层模块要关系的,要关心的是什么?是接口,是抽象,它不关心对象是如何创建出来,这由谁负责呢?工厂类,只要知道工厂类是谁,我就能创建出一个需要的对象,省时省力,优秀设计就应该如此。
- 产品族内的约束为非公开状态。
缺点
抽象工厂模式的最大缺点就是产品族扩展非常困难,为什么这么说呢?我们以通用代码为例,如果要增加一个产品C,也就是说有产品家族由原来的2个,增加到3 个,看看我们的程序有多大改动吧!抽象类AbstractCreator要增加一个方法createProductC(),然后,两个实现类都要修改,严重违反了开闭原则,而且我们一直说明抽象类和接口是一个契约,改变契约,所有与契约有关系的代码都要修改,这段代 码叫什么?叫“坏代码”,——只要这段代码有关系,就可能产生侵害的危险!
注意实现
在抽象工厂模式的缺点中,我们提到抽象工厂模式的产品族扩展比较困难,但是一定要清楚是产品族扩展困难,而不是产品等级,在该模式下,产品等级是非常容易 扩展的,增加一个产品等级,只要增加一个工厂类负责新增加出来的产品生产任务即可,也就是说横向扩展容易,纵向扩展困难。以人类为例子, 产品等级中只要男、女两个性别,现实世界还有一种性别:双性人,即使男人也是女人(俗语就是阴阳人),那我们要扩展这个产品等级也是非常容易的,增加三个 产品类,分别对应不同的肤色,然后再创建一个工厂类,专门负责不同肤色人的双性人的创建任务,完全通过扩展来实现的需求的变更,从这一点上看,抽象工厂模 式是符合开闭原则的
六、联系(Link)
- 单例模式:具体工厂类可以设计成单例类,一个单例类只有一个实例,它自己向外界提供自己唯一的实例。
- 抽象工厂:工厂角色与抽象产品角色合并,在抽象工厂模式中,抽象工厂类可以有静态方法,这个方法根据参数的值,返回对应的具体工厂类实例,但是其返回值类型是抽象工厂类型,这样可以在多态性的保证之下,允许静态工厂方法自行决定哪一个具体工厂符合要求。
- 每一个模式都是针对一定问题的解决方案,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式针对的是多个产品等级结构。
七、扩展(Extend)
1、反射:用反射技术来去除switch或if,解除分支判断带来的耦合。
2、反射+配置文件:
如果将这些信息写在配置文件里,那么有相关改动是不需要改程序,直接改配置文件并重启服务就可以(这一部分将在实践中去学习)。
八、总结(Summary)
通过引进抽象工厂模式,可以处理具有相同(或者相似)等级结构的多个产品族中的产品对象的创建问题。由于每个具体工厂角色都需要负责两个不同等级结构的产品对象的创建,因此每个工厂角色都需要提供两个工厂方法,分别用于创建两个等级结构的产品。既然每个具体工厂角色都需要实现这两个工厂方法,所以具有一般性,不妨抽象出来,移动到抽象工厂角色中加以声明。