设计模式(3) 抽象工厂模式

  • 抽象工厂模式
  • 优化抽象工厂
  • 异步工厂

在学习抽象工厂模式前,先来回顾一下前面的简单工厂和工厂方法模式。简单工厂的职责非常简单:构造某个实体类型,然后把实例作为抽象类型返回;
工厂方法模式则进一步抽象出一个抽象的创建者和一个抽象的产品类型,而实际的执行过程是具体工厂创建具体的产品类型,具体工厂和具体产品类型都可以被抽象为之前定义的抽象创建者和抽象产品类型,这种模式即便面对的是一个很庞大的具有复杂家族关系的类型系统,客户程序在操作的过程中仍然可以基于抽象创建者获得满足某种抽象类型的产品实例。

但在很多场景下,需要创建的不是仅仅继承自单个抽象类型的产品,它们本身就是多个具有一定依赖关系,但非同源的类型。

抽象工厂模式

抽象工厂模式可以应对这种情况,它能够产生一系列具有相关依赖关系的类型。
Provide an interface for creating families of related or dependent objects.
— Design Patterns : Elements of Reusable Object-Oriented Software

抽象工厂可以返回一系列相关或相互依赖对象的接口。另外抽象工厂自身也需要一个接口,这个接口定义中包括返回那些相关对象接口的方法定义。

其UML类图如下:

抽象工厂 UML类图

其中IProductA IProductB就是相关或相互依赖对象的接口,实体工厂会生产实现了这些接口的实体产品。IAbstractFactory是抽象工厂的接口,定义了生产IProductA、IProductB的方法。实体工厂自行决定如何实现抽象工厂接口定义的生产方法。

实现代码:

public interface IProductA { };
public interface IProductB { };

public interface IAbstractFactory
{
    IProductA CreateProductA();
    IProductB CreateProductB();
}

public class ProductA1 : IProductA { }
public class ProductA2 : IProductA { }
public class ProductB1 : IProductB { }
public class ProductB2 : IProductB { }

public class ConcreteFactory1 : IAbstractFactory
{
    public IProductA CreateProductA()
    {
        return new ProductA1();
    }

    public IProductB CreateProductB()
    {
        return new ProductB1();
    }
}

public class ConcreteFactory2 : IAbstractFactory
{
    public IProductA CreateProductA()
    {
        return new ProductA2();
    }

    public IProductB CreateProductB()
    {
        return new ProductB2();
    }
}

调用:

[Test]
public void AbstractFactoryTest()
{
    IAbstractFactory factory = new ConcreteFactory1();
    IProductA productA = factory.CreateProductA();
    IProductB productB = factory.CreateProductB();

    Assert.AreEqual(typeof(ProductA1), productA.GetType());
    Assert.AreEqual(typeof(ProductB1), productB.GetType());
}

从调用端代码可以发现一个问题,同前面工厂方法模式一样,Client与某个具体工厂耦合在一起,所以这里也可以采用依赖注入的方式“解决”这个问题,把这一步的处理推给上一层的调用端。

优化抽象工厂模式

这套基于经典的抽象工厂模式的代码还有可以优化的地方:

  • 首先具体工厂的代码重复性太高,
  • 另外具体工厂与具体的产品绑定,如果需要改变产品组合的方式,就得要么修改具体工厂的代码,要么创建新的工厂,改动都很大。
  • 还有个更严重的问题,如果要添加新的抽象产品,那么从抽象工厂接口到每个具体实现都需要修改,几乎是牵一发动全身。

针对第一点的优化方案,可以提取出一个具体工厂共用的基类AbstractFactoryBase,让它实现抽象接口的创建方法,如果某个工厂的Create方法比较特殊,可以重写基类的Create方法。
但是基类工厂并不知道具体工厂要创建的是怎样的产品组合,这可以在实例化具体工厂的时候传递一个抽象产品与具体产品的映射(可以是字典的形式),让基类根据映射关系来运作,或者可以基于现成的IOC容器来配置这样的映射。这样第二个、第三个问题也就都迎刃而解了。
实现代码如下:

public interface IAbstractFactoryWithMapper
{
    T Create<T>() where T : class;
}

public abstract class AbstractFactoryBase : IAbstractFactoryWithMapper
{
    protected IDictionary<Type, Type> mapper;
    public AbstractFactoryBase(IDictionary<Type, Type> mapper)
    {
        this.mapper = mapper;
    }

    public virtual T Create<T>() where T : class
    {
        if (mapper == null || mapper.Count == 0 || !mapper.ContainsKey(typeof(T)))
        {
            throw new ArgumentNullException();
        }
        Type targetType = mapper[typeof(T)];
        return (T)Activator.CreateInstance(targetType);
    }
}

public class ConcreteFactory : AbstractFactoryBase
{
    public ConcreteFactory(IDictionary<Type, Type> mapper) : base(mapper) { }
}

调用:

[Test]
public void AbstractFactoryWithMapperTest()
{
    IDictionary<Type, Type> dictionary = new Dictionary<Type, Type>();
    dictionary.Add(typeof(IProductA), typeof(ProductA1));
    dictionary.Add(typeof(IProductB), typeof(ProductB1));

    IAbstractFactoryWithMapper factory = new ConcreteFactory(dictionary);
    IProductA productA = factory.Create<IProductA>();
    IProductB productB = factory.Create<IProductB>();

    Assert.AreEqual(typeof(ProductA1), productA.GetType());
    Assert.AreEqual(typeof(ProductB1), productB.GetType());
}

异步工厂

有些时候工厂创建产品实例的过程比较复杂,或者涉及网络、数据库等外部资源的访问,整体耗时较长;这种情况下,如果工厂支持异步调用,客户程序就可以只向工厂发一个请求,然后接着干别的事,等收到工厂创建完成的通知后再回来接着处理。
异步工厂实现:

public interface IProduct { };
public interface IFactory
{
    IProduct Create();
}

public interface IFactoryWithNotifier : IFactory
{
    void Create(Action<IProduct> callBack);
}

//实体结构部分
public class ConcreteProduct : IProduct { }
public class ConcreteFactory : IFactoryWithNotifier
{
    public IProduct Create() //同步构造
    {
        return new ConcreteProduct();
    }

    public void Create(Action<IProduct> callBack)  //异步构造
    {
        IProduct product = Create();
        callBack(product);
    }
}

//为方便单元测试构造的订阅者
public class Subscriber
{
    private IProduct product;
    public void SetProduct(IProduct product)
    {
        this.product = product;
    }

    public IProduct GetProduct()
    {
        return product;
    }
}

调用:

[Test]
public void AsyncFactoryTest()
{
    IFactoryWithNotifier factoryWithNotifier = new ConcreteFactory();
    Subscriber subscribe = new Subscriber();
    Action<IProduct> callback = new Action<IProduct>(subscribe.SetProduct);

    Assert.IsNull(subscribe.GetProduct());
    factoryWithNotifier.Create(callback);
    Assert.IsNotNull(subscribe.GetProduct());
}

参考书籍:
王翔著 《设计模式——基于C#的工程化实现及扩展》

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351