所以我有一系列对象,分别称为Impl1,Impl2和Impl3。它们每个都实现一个称为IImpl的接口。我有一个Factory类,他的任务是检索适合于给定情况的ImplX,并将其传递给其调用方。因此,工厂中的代码如下所示:

public IImpl GetInstance(params object[] args)
{
    if (args[0]=="Some Value")
         return new IImpl1();
    else if (args[0]=="Other Value")
         return new IImpl2(args[1]);
    else
         return new IImpl3(args[1]);
}


因此,根据传入的参数选择不同的实例。一切顺利,一切正常。现在的问题是,我有一个需要调用此工厂方法的类。它没有引用IImplX,这很好,但是最终必须确切地知道如何构造GetInstance的输入数组,以确保它接收正确类型的实例。代码结束时看起来像这样:

switch (_selectedInputEnum)
{
    case InputType.A:
        myIImplInst = Factory.GetInstance("Some Value");
    case InputType.B:
        myIImplInst = Factory.GetInstance("Other Value",this.CollectionB);
    case InputType.C:
        myIImplInst = Factory.GetInstance("Third Value",this.CollectionC);
}


这感觉很多余,而且不知何故。提取工厂实际参数的最佳方法是什么?我觉得上面的switch语句与IImplx的实现紧密相关,即使我没有直接的引用。

评论

我认为我们要么需要更多信息,要么不能满足您的期望:如果您需要向该工厂传递不同类型的参数以使其正常工作,那么客户端代码总是必须提供这些参数,并且因此,总是必须知道要调用哪个重载或提供哪些参数。

#1 楼

客户代码在致电工厂之前构造的某种中间黑板怎么样,每个Impl都可以轮询来构造自己?

// client code:

Blackboard blackboard = new Blackboard();
blackboard.pushCollectionB(collectionB);
blackboard.pushCollectionC(collectionC);
blackboard.pushFoo(foo); 

IImpl myInstance = Factory.GetInstance(b);

///////////////////////////

// in Factory.GetInstance():

return new Impl3(blackboard);

////////////////////////////

// in Impl3:

Impl3(Blackboard b) { process(b.getCollectionC()); }


我隐藏了开关客户端代码中的语句,但您也可以将其移到黑板上。

工厂和客户端代码现在都隐藏了每个具体Impl需要的数据。但是,如果您需要Impl(x + 1)的Blackboard中有更多数据,则需要更新代码中创建Blackboard的每个位置。

根据应用程序,像这样过度构建Blackboard对您来说可能很昂贵。您可以在启动时构造一个缓存的版本,也可以将Blackboard用作接口并让您的客户端代码从该接口派生。

评论


\ $ \ begingroup \ $
在这种结构中,工厂本身必须知道要调用哪个ImplX,大概是通过轮询黑板本身?
\ $ \ endgroup \ $
–GWLlosa
2011年1月21日14:45

\ $ \ begingroup \ $
您可以选择任一种方式,将客户端代码中的开关保留为可从InputType转换,然后将字符串与Blackboard一起传递到Factory中,或者按照您的建议将其推入Blackboard中。不管哪种方法。
\ $ \ endgroup \ $
–tenpn
2011年1月21日15:17

#2 楼

CollectionB和CollectionC来自哪里?我要研究的方法是让各种工厂都接受相同类型的输入,然后将它们存储在某种类型的集合中。例如,如果有一个要创建的对象的列表,每行一个对象,并且在第一个空格之前的每一行的部分定义了每个项目的类型,则可以有一个采用字符串并产生适当值的工厂。配置的对象。例如,如果输入看起来像:

square 0,5,5,29
line 5,2 19,23 6,8
text 0,29,Hello there!


一个可能有一个SquareFactory,LineFactory和TextFactory,它们都继承自GraphicObjectFactory,后者接受一个字符串并返回GraphicObject 。然后可以有一个Dictionary,将String映射到GraphicsObjectFactory,并在其中放入上述每个工厂的实例。要允许文件阅读器处理更多类型的图形对象,只需在字典中添加更多工厂即可。

评论


\ $ \ begingroup \ $
除非我误读了它,否则它似乎与tenpn的答案类似,与我所做的很接近,因此我喜欢它:)
\ $ \ endgroup \ $
–GWLlosa
2011年1月29日,下午2:03

\ $ \ begingroup \ $
它有点像tenpn的答案,除了他的“黑板”是由该类的各个客户端中的代码配置的,而我希望工厂可以从配置文件或其他单点定义中构造出来。
\ $ \ endgroup \ $
–超级猫
2011年1月30日23:40

#3 楼

在我看来,工厂应该选择基于arg [1]而不是arg [0]的ImplX。

这样的事情将消除对开关的需求,在此示例中,arg [1]现在为arg [0]:

public IImpl GetInstance(params object[] args)
{
    if (args.length == 0)
         return new IImpl1();
    else if (args[0] is IEnumerable<string>)
         return new IImpl2(args[0]);
    else
         return new IImpl3(args[0]);
}


然后可以像这样调用它:

var impl1 = GetInstance();
var impl2 = GetInstance(new List<string>{"1","2"});
var impl3 = GetInstance("");


编辑:
如果您不希望调用者必须了解它的内部作品,那么您可以应该公开getInstance的重载,这样:

public IImpl GetInstance()
{ 
    return new Impl1();
}
public IImpl GetInstance(IEnumberable<string> strings)
{
    return new Impl2(strings);
}
public IImpl GetInstance(string string)
{
    return new Impl3(string);
}


评论


\ $ \ begingroup \ $
仍然需要GetInstance(在本例中为第二个代码块)的调用者了解GetInstance的工作原理(在本例中,是将不同的参数以不同的顺序传递给GetInstance)。
\ $ \ endgroup \ $
–GWLlosa
2011年1月21日19:52

\ $ \ begingroup \ $
然后,我认为您需要为支持不同调用方法的GetInstance创建一些替代。
\ $ \ endgroup \ $
– Sean Lynch
2011年1月21日在20:02

\ $ \ begingroup \ $
他们还不需要知道要调用哪个替代吗?
\ $ \ endgroup \ $
–GWLlosa
2011年1月21日在20:31



\ $ \ begingroup \ $
他们应该能够决定要获得什么ImplX吗?如果是这样,那么它将是GetInstanceImpl2(IEnumerable 字符串),而不是GetInstance(IEnumerable 字符串)。
\ $ \ endgroup \ $
– Sean Lynch
2011年1月21日在20:37



#4 楼

好像您在滥用工厂模式一样,工厂的整个目的是通过传递一些通用对象,然后工厂决定最适合退货的对象。传递的对象不需要知道工厂,但是工厂应该知道传递的对象。

如果您需要传递非常明确的参数,那么这是体系结构中的代码味道,我d认真考虑做一些重构,而不仅仅是“解决”这个问题

评论


\ $ \ begingroup \ $
这就是这个问题的确切意图;将其重构为更好的解决方案。
\ $ \ endgroup \ $
–GWLlosa
2011年1月23日14:43

#5 楼

它说这个问题最早是5年前发布的,但是没人回答,所以去了。
首先,让我们对示例进行详细介绍。
我建议我们使用IPersistable接口它支持一种名为Save()的公共方法。它看起来像如下所示:
interface IPersistable {
    bool Save();
}

接下来,我们需要三个实现该方法的类:
class FileSaver : IPersistable{
    public bool Save(){
        Console.WriteLine("I'm saving into a FILE.");
        return true;
    }
}

class DatabaseSaver : IPersistable{
    public bool Save(){
      Console.WriteLine("I'm saving into a DATABASE.");
      return true;
    }
}

class TcpSaver : IPersistable{
    public bool Save(){
        Console.WriteLine("I'm saving into a WEB LOCATION.");
        return true;
    }
}

最后,我们需要一个工厂类来构建适当的类
class SaverFactory{
    private string type;
    public SaverFactory(string type){
        this.type = type;
    }
    
    public IPersistable CreateSaver(){
        switch (type){
            case "FileSaver" :
            {
                return new FileSaver();
            }
            case "DatabaseSaver" :
            {
                return new DatabaseSaver();
            }
            case "TcpSaver" :
            {
                return new TcpSaver();
            }
            default : 
                return null;
        }
    }
}

现在您可以复制该C#代码并将其粘贴到LINQPad(http://linqpad.net)会话中,并添加以下主要方法,它将很好地工作。
void Main()
{
    List<IPersistable> allItems = new List<IPersistable>();
    List<String> fakeData = new List<String>();
    fakeData.Add("FileSaver"); fakeData.Add("DatabaseSaver");
    
    foreach (String s in fakeData){
        IPersistable ip = new SaverFactory(s).CreateSaver();
        if (ip != null)
            allItems.Add(ip);
    }
    
    foreach (IPersistable ip in allItems)
    {
        ip.Save();
    }
}

代码运行结果
如果运行该代码,您将看到以下内容:

我正在保存到文件中。
我正在保存到文件中数据库。

这是一种正常工作的Factory方法。它取决于您传入的类型字符串。这是预期的,因为您告诉您正在构建特定的类型,这是预期的。
如果从属类型需要配置参数怎么办?
但是,您现在想知道的是,如果要由工厂构建的类型需要其他一些参数以便可以自行配置,那么会发生什么情况。
例如,在这种情况下,我想发送数据库DatabaseSaver的连接以及FileSaver的文件路径和名称以及TcpSaver的URI。您认为提供用于构造实现对象的配置信息会以某种方式使您的设计不堪一击,但我不相信这样做。
我的意思是,工厂的全部要点是要告诉工厂构造特定的实现类。以我为例,我创建了一个字符串列表,然后将其传递给工厂。想象一下那些字符串加载了其他信息,这些信息配置了我的IPersistable实现。很好。
让我们通过展示如何将所需的配置项传递到工厂中来使该示例更进一步,以便围绕这些额外的配置信息构造IPersistable实现类。
创建新的接口和类:IConfigurable
interface IConfigurable{
    String type{get;set;}
}

class FileConfig : IConfigurable{
    public String type{get;set;}
    public string FileName{get;set;}
    public FileConfig(String type, String fileName=null){
        this.type = type;
        FileName = fileName;
    }
    
}
class DatabaseConfig : IConfigurable{
    public String type{get;set;}
    public string ConnectionString{get;set;}
    public DatabaseConfig(String type, String ConnectionString=null){
        this.type = type;
        this.ConnectionString = ConnectionString;
    }
}

class TcpConfig : IConfigurable{
    public String type{get;set;}
    public string Uri {get;set;}
}

我对最后一个感到有些懒惰,并没有全部实现,因为我知道没人会读完所有这些。我将为您提供完整的代码清单,您可以使用LINQPad进行运行和检查。
void Main()
{
    List<IPersistable> allItems = new List<IPersistable>();
    List<IConfigurable> fakeData = new List<IConfigurable>();
    fakeData.Add(new FileConfig("FileSaver",@"c:\superpath")); 
    fakeData.Add(new DatabaseConfig("DatabaseSaver",@"connection=superdb;integrated security=true"));
    
    foreach (IConfigurable ic in fakeData){
        IPersistable ip = new SaverFactory(ic).CreateSaver();
        if (ip != null)
            allItems.Add(ip);
    }
    
    foreach (IPersistable ip in allItems)
    {
        ip.Save();
    }
}

interface IPersistable {
    bool Save();
}

interface IConfigurable{
    String type{get;set;}
}

class FileConfig : IConfigurable{
    public String type{get;set;}
    public string FileName{get;set;}
    public FileConfig(String type, String fileName=null){
        this.type = type;
        FileName = fileName;
    }
    
}
class DatabaseConfig : IConfigurable{
    public String type{get;set;}
    public string ConnectionString{get;set;}
    public DatabaseConfig(String type, String ConnectionString=null){
        this.type = type;
        this.ConnectionString = ConnectionString;
    }
}

class TcpConfig : IConfigurable{
    public String type{get;set;}
    public string Uri {get;set;}
}

class FileSaver : IPersistable{
    IConfigurable config;
    public FileSaver(IConfigurable config){
        this.config = config;
    }
    public bool Save(){
        Console.WriteLine("I'm saving into a FILE.");
        var configItem = config as FileConfig;
        Console.WriteLine(configItem.FileName);
        return true;
    }
}

class DatabaseSaver : IPersistable{
    IConfigurable config;
    public DatabaseSaver(IConfigurable config){
        this.config = config;
    }
    public bool Save(){
        Console.WriteLine("I'm saving into a DATABASE.");
        var configItem = config as DatabaseConfig;
        Console.WriteLine(configItem.ConnectionString);
        return true;
    }
}

class TcpSaver : IPersistable{
    IConfigurable config;
    public TcpSaver(IConfigurable config){
        this.config = config;
    }
    public bool Save(){
        Console.WriteLine("I'm saving into a WEB LOCATION.");
        return true;
    }
}

class SaverFactory{
    IConfigurable config;
    public SaverFactory(IConfigurable config){
        this.config = config;
    }
    
    public IPersistable CreateSaver(){
        switch (config.type){
            case "FileSaver" :
            {
                return new FileSaver(config);
            }
            case "DatabaseSaver" :
            {
                return new DatabaseSaver(config);
            }
            case "TcpSaver" :
            {
                return new TcpSaver(config);
            }
            default : 
                return null;
        }
    }
}


评论


\ $ \ begingroup \ $
Errr,您发布了正确的问题吗?您正在查看的代码与问题中的代码无关。另外,已经有4个答案,因此被回答了。
\ $ \ endgroup \ $
–Tunaki
16年4月21日在20:45

\ $ \ begingroup \ $
我知道。我知道它已经五岁了。我以为OP需要解释Factory模式以及如何进行配置。 OP的原始代码使其他人很难回答,并且需要背后的一些细节。我的示例运行并显示可以将参数发送给Factory以创建实现类,并且参数发送可以成为使用Factory模式的有效部分。
\ $ \ endgroup \ $
– raddevus
16-4-21在20:49



\ $ \ begingroup \ $
这个旧问题在当时是不合时宜的(并且有很好的答案),不再是不合时宜的,因此已被历史锁定。如果您想与OP进行更多讨论(如果他/她看到此信息),则可以将其用作聊天。
\ $ \ endgroup \ $
– Jamal♦
16-4-22的3:08