It says this question was first posted 5 years ago, but no one ever answered it so here goes.
First of all, let's get a bit more detail into the example.
I suggest we use an IPersistable interface which supports one public method named Save(). It'll look like the following:
interface IPersistable {
bool Save();
}
Next, we want three classes which implement this method:
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;
}
}
Finally, we need a factory class which will build our appropriate class on demand.
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;
}
}
}
You can now copy that C# code and paste it into a LINQPad (http://linqpad.net) session and add the following main method and it will work beautifully.
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();
}
}
Code Run Result
If you run that code you will see the following:
I'm saving into a FILE.
I'm saving into a DATABASE.
That is a proper working Factory method. It is dependent upon the type string that you pass in. That is expected, because you are telling it that you are building a specific type and that is expected.
What If Dependent Type Needs Parameters For Configuration?
However, what you are wondering now is what happens if the type you are requesting to be built by the factory needs some other parameters so that it can configure itself.
For example in this case I would want to send in a database connection for the DatabaseSaver and a file path and name for the FileSaver and a URI for the TcpSaver. You believed that providing that configuration information for building the implementation object somehow sullied your design, but I don't believe it does.
I mean the whole point of a factory is that something tells it to build a specific implementation class. In my case I create a list of strings which are then passed in to the factory. Imagine those strings were loaded with other information which configures my IPersistable implementation. That would be fine.
Let's move the example ahead a bit by showing how I might pass the needed configuration item into the factory so the IPersistable implementation class can be constructed around that extra configuration information.
Create New Interface and Classes : 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;}
}
I became a little lazy on that last one and didn't implement it all because I know no one will read all of this. I will leave you with the entire code listing which you can run and examine using 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;
}
}
}