上篇文章,我们说到了简单工厂和工厂方法,如果没看过的,请先看上篇,不然的话,可能有些吃力,或者直接点击阅读原文,查看我博客园的对应详细版的文章。
大家学到了这里,我建议自己可以练习练习,可以写一下部分代码,很好的区分下【简单工厂】和【工厂方法】之间的区别和练习,如果说你已经看懂了,或者说自己已经练习好了,那咱们就继续往下说今天的重要内容 —— 抽象工厂。
再说抽象工厂之前呢,咱们先简单总结一下:
1、我们知道,工厂模式属于创建型开发模式的一元,他的作用就是创建我们需要的对象,如果一个一个创建的话,会很麻烦,所以我们诞生出来了一个【简单工厂】,这个简单工厂只是简单的人为的把几个对象的实例给堆起来,通过type 来区分,然后分别 new 实例化,有时候也是一个很好的方案,但是这样有一个弊端,违背了我们开发六大原则中的——OCP开放关闭原则,所以这个时候,我们就又多出来一个新的概念【工厂方法】。
2、【工厂方法】是在【简单工厂】的基础上,做了相应的改良——通过多个子类的形式,来替换之前的 type 分类法,对内修改关闭,对外新增打开,这样无论是代码整洁上,还是扩展封装上,都有了很好的体验,同时也满足了我们的OCP开发原则。
3、但是!这种方案真的就很好了么,我们再来回忆一下,我们无论是简单工厂,还是工厂方法,都是生成的单独的一个类,好处是可以一一的慢慢扩展,但归根结底还是在处理一个类,我们平时开发的时候,处理一个类是很多,比如常见的helper,Log 之类的。但是这不是我们开发的重点,我们平时使用最多的还是 Service 类,或者 Repository 类,里边有很多,各种各样的的类,比如 User 表,Role 表,Permission 表等等,每一个实体又都对应各自的一个服务类或者仓储类。
4、那这个时候,我们使用上边的【工厂方法】还行么?肯定是不行的!因为我们上边是一个二维体系,EFCoreRepository 、SugarRepository、DapperRepository等等,是这样的二维,我们现在要做的就是在这个二维的基础上,再加上一个维度,就是要解决 User 、Role、Permission 实体等等这种一组或者一系列的类创建的问题。
5、那就是今天下边要说到的【抽象工厂】模式。
注意,下边的例子可能不太恰当,只是作为理解抽象工厂模式来使用,具体开发中,可能有出入。
这篇文章内容不是很全,我在博客园重新编排了一下,然后也同时加上了一个小故事,更方便理解,大家可以点击【阅读原文】来查看。
一、抽象工厂模式
上边的问题我们都看到了,我们要解决一系列一组类创建的问题,引申出来了抽象工厂模式,那下边我们就简单写一些代码,看看是否跑的通。
1、创建一个核心层,添加多个仓储操作
我们毕竟要操作数据库嘛,所以肯定需要仓储来持久化,那我们就创建一个 FactoryPattern.Core 层,用来存放我们整个项目核心的底层。首先要创建的就是几个仓储:
/// <summary>
/// 定义抽象的基类仓储
/// </summary>
public abstract class BaseRepository
{
/// <summary>
/// 创建
/// </summary>
public abstract void Add();
/// <summary>
/// 删除
/// </summary>
public abstract void Delete();
/// <summary>
/// 修改
/// </summary>
public abstract void Update();
/// <summary>
/// 查询
/// </summary>/
public abstract void Query();
}
/// <summary>
/// 定义抽象用户仓储,继承抽象基类仓储
/// 抽象的目的,是为了给UserRepositoryEFCore、UserRepositorySugar、
/// 做父类
/// </summary>
public abstract class UserRepository: BaseRepository
{
}
/// <summary>
/// 同 UserRepository
/// </summary>
public abstract class RoleRepository: BaseRepository
{
}
/// <summary>
/// 同 UserRepository
/// </summary>
public abstract class PermissionRepository: BaseRepository
{
}
那基本的仓储都已经定义好了,现在就需要一个工厂来生产这一系列产品了,所以我们定义一个抽象工厂类:
/// <summary>
/// 抽象工厂类,提供创建不同仓储接口
/// </summary>
public abstract class AbstractFactory
{
// 抽象工厂提供创建一系列产品的接口
public abstract UserRepository UserRepository();
public abstract RoleRepository RoleRepository();
public abstract PermissionRepository PermissionRepository();
}
结构如下:
2、创建EFCore仓储工厂层
说人话就是,刚刚我们不是定义了一个抽象的工厂么,用来生产我们数据库中一系列一组的产品,也就是数据库表,那现在我们就需要指定具体的工厂来生产他们了,首先第一个我们就用EFCore这个工厂来生产,创建一个 FactoryPattern.Repository.EFCore 类库,并引用 Core 核心层
首先呢,我们就要在这一层中,对那几个抽象的仓储类做重写,对应每一个EFCore 版本的仓储类,可能你会问为什么,要每一个重写下,还是OCP原则,而且还有一个愿意,Sqlsugar 可能某些表达式查询,在EFCore里不能用,所以必须每一个重写出来。
这里有一个地方就是,可以在EFCore也针对基类仓储做一个基类的,但是后来有类型不一致问题,大家可以自己看看.
/// <summary>
/// EFCore User 仓储,继承User仓储
/// </summary>
public class UserRepositoryEFCore : UserRepository
{
public override void Add()
{
throw new NotImplementedException();
}
public override void Delete()
{
throw new NotImplementedException();
}
public override void Query()
{
throw new NotImplementedException();
}
public override void Update()
{
throw new NotImplementedException();
}
}
// 其他两个表也是这个情况,不粘贴代码了。
那现在有了子仓储产品了,我们就开始加工生产了,创建 EFCoreRepositoryFactory.cs ,并继承抽象工厂,同时实现抽象方法:
/// <summary>
/// EFCore 仓储子工厂
/// 用来生产各个实体仓储
/// </summary>
public class EFCoreRepositoryFactory : AbstractFactory
{
public override PermissionRepository PermissionRepository()
{
return new PermissionRepositoryEFCore();
}
public override RoleRepository RoleRepository()
{
return new RoleRepositoryEFCore();
}
public override UserRepository UserRepository()
{
return new UserRepositoryEFCore();
}
}
结构如下:
3、同理,创建Sugar仓储工厂层
过程和上边的一模一样,我就不多说了,整体结构还是这样的:
4、控制器调用实例
我们在 api 层,引用刚刚创建的两个仓储层项目:
然后开始调用:
[HttpGet]
public void Get()
{
// 实例化工厂,这里用来生产 efcore 这一系列的 产品
AbstractFactory efcoreFactory = new EFCoreRepositoryFactory();
efcoreFactory.UserRepository().Add();
efcoreFactory.RoleRepository().Delete();
efcoreFactory.PermissionRepository().Query();
// 实例化工厂,这里用来生产 sugar 这一系列的 产品
AbstractFactory sugarFactory = new SugarRepositoryFactory();
sugarFactory.UserRepository().Add();
sugarFactory.RoleRepository().Delete();
sugarFactory.PermissionRepository().Query();
}
结果我就不调试,肯定是没有问题的,毕竟仅仅是类的调用嘛,如果说你从上边往下看,看到了这里,还没有问题,并且能大概明白其中的意义,那你的工厂模式已经完全学会了,特别是这个抽象工厂,一直很绕,而且也使用的不是很直观,用途不是很多。
现在我们再简单的说明一下,我们通过【抽象工厂】模式,慢慢的明白了,其实抽象工厂是在【工厂方法】模式的基础上,往外又多做了一套封装,目的就是解决生产一系列产品的时候,工厂方法无法满足的问题。
所以这个时候,如果有人问你二者的区别,核心的区别就是:如果是一个产品用工厂方法,一系列产品,用抽象工厂。
但是虽然解决了问题,还是有很多的问题的,单单从上边我们创建了那么多的子类,以及子类必须重写这一点来看,这么设计肯定有弊端,那有什么改进的呢?咱们继续往下看
二、抽象工厂与依赖注入
这里我就不详细说了,其实就是一个思路的用法,这里举个例子就行了,大家肯定都用过三层架构,其中有一个数据访问层 DALFactory ,我们平时使用的时候,就是直接把类的实例给 return 出来,如果我们同时连接多个数据库呢?那这样的话,我们就像上边说到的,建立多个 DAL 层,比如 DALSqlServer、DALMysql 等等,那我们如何通过接口来获取服务呢,就是通过反射指定的程序集来实现,这个就是简单的使用了抽象工厂。
比如这个网上的图片,就是这个意思,大家看个意思就行:
说到这里大家有没有了解到一些小小的心得,似乎这个和有一个东西很像!对!就是我们平时使用的依赖注入。其实我们可以想一想,我们在服务注册的时候,通过反射将多个服务注册到容器里,然后我们再使用的时候,是容器通过接口别名,给我们找到指定的具体服务,甚至也实现了一个接口,多个服务的操作,这个就是工厂模式和依赖注入的小小的关系。
总结一下:
今天我们通过简单的代码,一步一步,从【简单工厂】开始,了解到了多个类是如何创建的,然后明白了【工厂方法】模式,更好的实现了OCP原则,也实现了封装多态的原理,接下来咱们通过【抽象工厂】的举例,进一步对一系列一组产品生产的时候,所采用的方案,到最后,我们简单的说明了一下反射以及依赖注入和工厂模式的关系,可能读起来还是有点儿凌乱,不过我还是简单大家多多的学学,查查资料,因为我认为,设计模式是结构的基础,而工厂模式又是设计模式的基础,可见其重要性,如果看不懂没关系,等我直播讲课吧。
当然抽象工厂也是有一些弊端的,比如:
3.1】、抽象工厂模式的优点:【抽象工厂】模式将系列产品的创建工作延迟到具体工厂的子类中,我们声明工厂类变量的时候是使用的抽象类型,同理,我们使用产品类型也是抽象类型,这样做就尽可能的可以减少客户端代码与具体产品类之间的依赖,从而降低了系统的耦合度。耦合度降低了,对于后期的维护和扩展就更有利,这也就是【抽象工厂】模式的优点所在。可能有人会说在Main方法里面(这里的代码就是客户端的使用方)还是会使用具体的工厂类,对的。这个其实我们通过Net的配置,把这部分移出去,最后把依赖关系放到配置文件中。如果有新的需求我们只需要修改配置文件,根本就不需要修改代码了,让客户代码更稳定。依赖关系肯定会存在,我们要做的就是降低依赖,想完全去除很难,也不现实。
3.2】、抽象工厂模式的缺点:有优点肯定就有缺点,因为每种模式都有他的使用范围,或者说要解决的问题,不能解决的问题就是缺点了,其实也不能叫缺点了。【抽象工厂】模式很难支持增加新产品的变化,这是因为抽象工厂接口中已经确定了可以被创建的产品集合,如果需要添加新产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的以及所有子类的改变,这样也就违背了“开发——封闭”原则。
3.3】、抽象工厂模式的使用场景: 如果系统需要多套的代码解决方案,并且每套的代码方案中又有很多相互关联的产品类型,并且在系统中我们可以相互替换的使用一套产品的时候可以使用该模式,客户端不需要依赖具体实现。
三、示例代码
https://github.com/anjoy8/DesignPattern/tree/master/FactoryPattern
【参考文献:】
1、Dependency Injection vs Factory Pattern