装配中的脑袋

用程序装配大脑,再用大脑装配程序
随笔 - 118, 评论 - 1214, 引用 - 11

导航

关于

如果想发较大的信件,请用Ninputer @ gmail.com

不要在我的Blog评论中张贴广告,除非同意向我付款。

标签

每月存档

最新留言

广告

 

注意:本文版权所有,不得擅自转载

泛型不仅能用来做容器,还能够提供代码复用的手段。在泛型的参与下,许多设计就可能更精妙,更具扩展性。今天我就来演示一个利用泛型增强的抽象工厂模式。我们知道,抽象工厂(Abstract Factory)模式是将工厂和产品全部抽象化,一个抽象工厂生成一组抽象产品,而一个具体工厂则生成具体产品的一个特定组合。它能够维持这种相关对象组合的一致性,并使得用户不需要了解工厂和产品的具体实现。传统的Abstract Factory主要弱点是类型依赖性强,可复用性弱。一个抽象工厂通常是为一个特定需要而设计,通常不能被其他需要抽象工厂的场合使用。而且尽管抽象工厂可以将实际生产任务委派给特定类型的工厂,但这种委派是需要通过自己纯代码实现的,没能利用语言所提供的抽象特性。我们今天的任务就是编写一个不针对特定产品类型和数目的泛型抽象工厂,当你需要特定的抽象工厂时,可随时复用无需再定义专门的抽象工厂实现。

我们首先从只生成一个产品的工厂方法开始,以这作为抽象工厂的原料。很明显,可以设计这样一个接口作为工厂方法的模板:

public interface IFactory<T>
{
    T Create();
}

这个工厂生产一个T类型的对象。当你实现此工厂时,应该让T为抽象产品的类型——即产品通用的基类。比如我们可以实现一个采用无参数构造函数来创建对象的OpNewFactory实现:

public class OpNewFactory<TAbstractProduct, TProduct> : IFactory<TAbstractProduct>
    where TProduct : TAbstractProduct, 
new()
{
    
public TAbstractProduct Create()
    {
        
return new TProduct();
    }
}

从此例子可以看出,你应该仅实现抽象类型的IFactory接口,并生成具体类型。现在我们做完了单一产品的工厂方法模板,就要开始定义生产多个产品的抽象工厂接口了。.NET泛型支持按类型参数个数进行重载,就是说,我们可以定义生产一个、两个、三个……等多种数目的抽象工厂接口,而使用同一个名字。(汗吧,这就是所谓支持“任意数目”的手法)这里免不了要拷贝代码,不过别担心,纯拷贝而已,使用的时候可是非常舒心的哦。我们以生产两种产品类型的抽象工厂为例介绍。能不能定义成这样呢?

public interface IAbstractFactory<T1, T2>
{
    T1 Create();
    T2 Create(); 
//编译错误!!!
}

哦不!方法不能以返回类型区分重载,只能靠参数类型重载。然而这里我们显然不能用T1和T2作为参数,因为这些方法就是为了生产T1和T2准备的,怎么能接受它们作为参数呢?难道要命名为Create1和Create2吗?这很难接受,我们希望生产方法能够体现产品的类型,怎么能叫1和2呢。为了解决这个问题,我们引入了TypeToken<T>类型,它的定义如下:

public sealed class TypeToken<T>
{
    
static private TypeToken<T> instanceValue = new TypeToken<T>();
    
static public TypeToken<T> Instance
    {
        
get { return instanceValue; }
    }

    
private TypeToken() { }
}

这个类没有成员,并且每个类型实参只能创建一个实例,因此代价极小。但就是这小小的实例上带有其类型实参的类型信息,因此可以作为判断函数重载的依据。我们用TypeToken<T>作为区分生产函数重载的依据,实现如下:

public interface IAbstractFactory<T1, T2>
{
    T1 Create(TypeToken
<T1> token);
    T2 Create(TypeToken
<T2> token);
}

现在我们针对抽象工厂实现具体工厂。具体工厂就是利用生产每种产品的单一工厂来组合实现。因此你只要有每种类型的单一工厂就可以直接组合生成抽象工厂,而无需定义一个类来做这件事。注意,对每种数目的抽象工厂接口都需要对应生成一个具体工厂的实现,这里我仅针对生成两个产品的演示:

public class ConcreteFactory<T1, T2> : IAbstractFactory<T1, T2>
{
    
private IFactory<T1> factory1;
    
private IFactory<T2> factory2;

    
public ConcreteFactory(IFactory<T1> f1, IFactory<T2> f2)
    {
        factory1 
= f1;
        factory2 
= f2;
    }

    
public T1 Create(TypeToken<T1> token)
    {
        
return factory1.Create();
    }

    
public T2 Create(TypeToken<T2> token)
    {
        
return factory2.Create();
    }
}

public static class ConcretFactory
{
    
public static ConcreteFactory<T1, T2> NewFactory<T1, T2>(IFactory<T1> f1, IFactory<T2> f2)
    {
        
return new ConcreteFactory<T1, T2>(f1, f2);
    }
}

注意,我又声明了一个没有类型参数的ConcretFactory类,用一个静态方法来生成泛型ConcretFactory的实例,这是因为使用泛型方法可以推测类型参数,使得我们可以不必输入尖括号或Of语句,而泛型类则没有这个功能。现在大功告成!我们用一个例子来演示这个泛型抽象工厂的工作情况。现在假设我们需要一个生产PC的抽象工厂,需要生产两种抽象产品:处理器和内存。处理器和内存的抽象和具体实现如下:

Processor 和 Ram

下面的代码演示了如何随心所欲生成想要的抽象工厂接口以及快速从现有单一产品工厂组合成特定的具体工厂实现。

class Program
{
    
static IAbstractFactory<Processor, Ram> ComputerFactory(string type)
    {
        
if (type == "Intel")
        {
            
return ConcretFactory.NewFactory(
                
new OpNewFactory<Processor, PentiumProcessor>(), 
                
new OpNewFactory<Ram, DDR2Ram>());
        }
        
else if (type == "AMD")
        {
            
return ConcretFactory.NewFactory(
                
new OpNewFactory<Processor, AthlonProcessor>(),
                
new OpNewFactory<Ram, DDRRam>());
        }

        
//unknown type
        return null;
    }

    
static void Main(string[] args)
    {
        
//Yield a computer of Intel
        IAbstractFactory<Processor, Ram> factory1 = ComputerFactory("Intel");

        Ram ram1 
= factory1.Create(TypeToken<Ram>.Instance);
        Processor cup1 
= factory1.Create(TypeToken<Processor>.Instance);

        Console.WriteLine(
"An Intel Computer");
        Console.WriteLine(
"CPU Model: {0}", cup1.Model);
        Console.WriteLine(
"Memory Frequency: {0} MHz", ram1.Frequency);

        Console.WriteLine();

        
//Yield a computer of AMD
        IAbstractFactory<Processor, Ram> factory2 = ComputerFactory("AMD");

        Ram ram2 
= factory2.Create(TypeToken<Ram>.Instance);
        Processor cup2 
= factory2.Create(TypeToken<Processor>.Instance);

        Console.WriteLine(
"An AMD Computer");
        Console.WriteLine(
"CPU Model: {0}", cup2.Model);
        Console.WriteLine(
"Memory Frequency: {0} MHz", ram2.Frequency);
    }
}

总结:我们用泛型技术成功地增强了原本重用性较低的抽象工厂,演示了泛型在提高抽象性和代码重用方面卓越的价值。

打印 | 张贴于 2006-01-12 15:59:00 | Tag:技术随笔

留言反馈

#re: 泛型技巧系列:用泛型打造可复用的抽象工厂 编辑
@wayfarer

其实我的主意就是生成一系列的这种同名接口

IAbstractFactory<T1>
IAbstractFactory<T1,T2>
IAbstractFactory<T1,T2,T3>
IAbstractFactory<T1,T2,T3,T4>
IAbstractFactory<T1,T2,T3,T4,T5>
IAbstractFactory<T1,T2,T3,T4,T5,T6>
....
和相应的具体工厂放在类库类,然后使用者随需而取,就好似IAbstractFactory支持人一个数的参数一样。我提到这种重载是合法的了哦,呵呵。
至于可读性较差的问题,我们还有using和Imorts语句嘛
using IComputerFactory = AbstractFactory.IAbstractoryFactory<Processor, Ram, GraphicCard>
2006-01-17 16:47:00 | [匿名:Ninputer]
#re: 泛型技巧系列:用泛型打造可复用的抽象工厂 编辑
虽然这种方法解决了传统的抽象工厂方法强类型依赖的问题。但毕竟还是受到了产品组个数的问题。以本例来说,一旦需求发生变化,工厂需要的产品不仅是Processor和RAM,还包括HardDisk,那么IAbstractFactory<T1,T2>仍然要发生变化。
利用设计模式达成的解耦的功能毕竟是有限的,我认为单纯地去追求这种可复用性和可扩展性,意义不大。所以,利用范型实现抽象工厂,主意不错,但总觉得有过度设计的嫌疑,似乎意义不是太大。当然,我们也可以利用这种方法再继续定义IAbstractFactory1<T1,T2,T3>。但对于客户端的调用者来说,可读性比较差,难以被接受。
2006-01-17 11:43:00 | [匿名:wayfarer]
#re: 泛型技巧系列:用泛型打造可复用的抽象工厂 编辑
@Bramble Whisper

那个我看过了,不能说是“很好的实现”,很富于技巧性罢了。甚至我觉得我的比他的更实用。
那本书里的TypeList技巧是很明显的FP思想,使用常数定义和匹配式选择来完成逻辑。这种思想不一定要靠C++ template来完成,用其他语言也可以做到,我就一直在尝试,如果它确实能够带来便利的话。
2006-01-14 21:59:00 | [匿名:Ninputer]
#re: 泛型技巧系列:用泛型打造可复用的抽象工厂 编辑
在Modern C++ Design中有一个关于工厂的很好的实现
感觉C#的template和C++的GP不在一个水平线上
2006-01-14 19:48:00 | [匿名:Bramble Whisper]
#re: 泛型技巧系列:用泛型打造可复用的抽象工厂 编辑
ok,我已经清除了你所说的全部runtime overhead,现在我的TypeToken<T>已经完全没有runtime了

public abstract class TypeToken<T>
{
  public const TypeToken<T> Value = null;
  private TypeToken() { }
}

然而功能不变!还有疑问吗:)
2006-01-13 11:52:00 | [匿名:Ninputer]
#re: 泛型技巧系列:用泛型打造可复用的抽象工厂 编辑
我保证你用到抽象工厂的任何程序的瓶颈都不是TokenType<T>带来的那点消耗,哪怕是HelloWorld级别的,在这个问题上面提runtime overhead简直就是令人难以置信。
而out,它在我看来是更加邪恶的东西,它让Create方法有了副作用,让“表达式状”生成变得不可能,许多场合下他根本就不优美,已经超越命名规范这一层了。
2006-01-13 11:08:00 | [匿名:Ninputer]
#re: 泛型技巧系列:用泛型打造可复用的抽象工厂 编辑
@Ninputer

我只是觉得,这样单纯地只是为了看上去漂亮,而绕一个大圈子是不是值得。再怎么说,当你调用TokenType<T>.Instance的时候,一个建栈销毁栈的动作是省略不了的。

其实如果注意变量命名的规范,使用out参数,既没有runtime overhead,代码也还是蛮漂亮的。

IAbstractFactory<Processor, Ram> intelFactory = ComputerFactory("Intel");

Ram intelRam;
intelFactory.Create(out intelRam);

只是个人审美观的不同而已。

在你的方法有明显runtime overhead的情况下,我还是比较倾向用out参数。

这和C++里的Traits有本质的区别,像C++这样,在编译期的Traits,我们有可能对它做更多的优化,完全优化掉一个对象创建的开销都是有可能的。
2006-01-13 10:51:00 | [匿名:Justin Shen]
#re: 泛型技巧系列:用泛型打造可复用的抽象工厂 编辑
@tiaoci

不是这样的,ComputerFactory是另一个简单工厂,与抽象工厂无关,只是用来演示抽象工厂用的。现在你无需定义自己的抽象工厂与具体工厂类了
2006-01-13 09:37:00 | [匿名:Ninputer]
#re: 泛型技巧系列:用泛型打造可复用的抽象工厂 编辑
绕了半天编程的工作量并没有减少啊
ComputerFactory还是要实现的么,
如果按照原始方法,我也只需要写这个
ComputerFactory方法吧
2006-01-13 08:33:00 | [匿名:tiaoci]
#re: 泛型技巧系列:用泛型打造可复用的抽象工厂 编辑
@Justin Shen

不成的,我需要在Create的时候有个办法让T1/T2的真实名称出现。用out不但语法不优美,还会破坏参数的常数语义,使用了GP/FP的思想之后就更不会喜欢它。
2006-01-13 08:11:00 | [匿名:Ninputer]
#re: 泛型技巧系列:用泛型打造可复用的抽象工厂 编辑
为什么不:

public interface IAbstractFactory<T1, T2>
{
void Create(out T1 instance);
void Create(out T2 instance);
}

?
2006-01-12 23:26:00 | [匿名:Justin Shen]
对不起,目前本随笔不允许发表新评论.

Powered by: Joycode.MVC引擎 0.5.2.0