Reputation: 18117
I have about 20 classes which are derived from ConvertApi
classes. Every class share Convert
method from parent class and has unique property SupportedFiles
. I use these classes for file manipulation and my code looks like
if (fileEx=="txt")
new Text2Pdf().Convert(file)
else
if (fileEx=="doc")
new Word2Pdf().Convert(file)
else
//and so on..
I know that these code can be optimized because 20 times if
operator is repeated and that looks bad, but can't find a way to do that. Could anyone help me?
class Text2Pdf : ConvertApi
{
enum SupportedFiles { txt, log };
}
class Word2Pdf : ConvertApi
{
enum SupportedFiles { doc, docx };
}
class Excel2Pdf : ConvertApi
{
enum SupportedFiles { xls, xlsx };
}
class ConvertApi
{
public void Convert(....);
}
Upvotes: 0
Views: 680
Reputation: 16628
You can use some reflection:
ConvertApi FindMatchingConverter(string _FileExt)
{
//Get all the converters available.
List<ConvertApi> converters = this.GetType()
.Assembly
.GetExportedTypes()
.Where(type => type.IsAssignableFrom(typeof(ConvertApi)))
.ToList();
//Loop and find which converter to use
foreach (var converter in converters)
{
if (converter.SupportedFiles.Contains(_FileExt))
return Activator.CreateInstance(converter);
}
throw new Exception("No converter found");
}
Then you just have to call Convert()
on the ConvertApi returned.
Of course, this requires you to add a virtual List<String>
named SupportedFiles in your base class.
This makes it look like
public abstract class ConvertApi
{
public abstract void Convert();
public virtual List<String> SupportedFiles {get;set;}
}
Upvotes: 1
Reputation: 43499
You need to use an abstract factory pattern, here. All your text converters should derive from a common interface ITextConverter implementing your Convert method. The file extension would be a parameter of your factory. Here is a sample below (typed "on the fly", sometimes copy-pasting code from my sources, so there might be typos. The goal here is to give you a general idea for a flexible implementation).
public interface IFileConverter
{
bool Convert(string filePath);
}
public static class FileConverterFactory
{
public static IFileConverter Create(string extension)
{
extension = type.ToUpper();
Dictionary<string, ConverterConfig> availableConverters = GetConvertersConfig();
if (!availableConverters.ContainsKey(extension))
throw new ArgumentException(string.Format("Unknown extenstion type '{0}'. Check application configuration file.", extension));
ConverterConfig cc = availableConverters[extension];
Assembly runnerAssembly = Assembly.LoadFrom(cc.Assembly);
Type converterType = runnerAssembly.GetType(cc.Class);
IFileConverter converter = (IFileConverter) Activator.CreateInstance(converterType);
return converter;
}
private static Dictionary<string, ConverterConfig> GetConvertersConfig()
{
var configs = (Dictionary<string, ConverterConfig>) ConfigurationManager.GetSection("ConvertersConfig");
return configs;
}
}
public class ConvertersConfigHandler : IConfigurationSectionHandler
{
public object Create(object parent, object configContext, XmlNode section)
{
Dictionary<string, ConverterConfig> converters = new KeyedList<string, ConverterConfig>();
XmlNodeList converterList = section.SelectNodes("Converter");
foreach (XmlNode converterNode in converterList)
{
XmlNode currentConverterNode = converterNode;
ConverterConfig cc = new ConverterConfig();
cc.Type = XML.GetAttribute(ref currentConverterNode, "Type").ToUpper();
cc.Assembly = XML.GetAttribute(ref currentConverterNode, "Assembly");
cc.Class = XML.GetAttribute(ref currentConverterNode, "Class");
converters[cc.Type] = cc;
}
return converters;
}
}
public class ConverterConfig
{
public string Type = "";
public string Assembly = "";
public string Class = "";
}
public class TextConverter : IFileConverter
{
bool Convert(string filePath) { ... }
}
public class PdfConverter : IFileConverter
{
bool Convert(string filePath) { ... }
}
In your app.config file, you add this to the configSections:
<section name = "ConvertersConfig" type = "ConvertersConfigConfigHandler, MyAssembly" />
and this below your configSections:
<ConvertersConfig>
<Converter Type="txt" Assembly="MyAssembly" Class="MyAssembly.TextConverter" />
<Converter Type="pdf" Assembly="MyAssembly" Class="MyAssembly.PdfConverter" />
</ConvertersConfig>
The call would then be like this:
IFileConverter converter = FileConverterFactory.Create("txt");
converter.Convert("c:\temp\myfile");
EDITED the code for giving a solution that is more "generic".
Upvotes: 1
Reputation: 23113
Slower, but more dynamic... use an Object Factory. Here's a good article that seems to fit your needs.
http://www.codeproject.com/Articles/12986/Generic-Object-Factory
The important stuff from the article:
using System.Collections.Generic;
public struct Factory < KeyType, GeneralProduct >
{
//Register a object with the factory
public void> Register< SpecificProduct >(KeyType key)
where SpecificProduct : GeneralProduct, new()
{
if( m_mapProducts == null )
{
m_mapProducts = new SortedList< KeyType, CreateFunctor >();
}
CreateFunctor createFunctor = Creator<SpecificProduct>;
m_mapProducts.Add(key, createFunctor);
}
//Create a registered object
public GeneralProduct Create( KeyType key )
{
CreateFunctor createFunctor = m_mapProducts[ key ];
return createFunctor();
}
private GeneralProduct Creator < SpecificProduct >()
where SpecificProduct : GeneralProduct, new()
{
return new SpecificProduct();
}
private delegate GeneralProduct CreateFunctor();
private SortedList<KeyType, CreateFunctor> m_mapProducts;
}
Usage:
class Fruit
{
}
class Apple : Fruit
{
}
class Orange : Fruit
{
}
class TestClass
{
static void Main(string[] args)
{
General.Factory< string, Fruit > factory;
//Register
factory.Register< Apple >( "Apple" );
factory.Register< Orange>( "Orange" );
//Create
Fruit fruit1 = factory.Create("Apple");
Fruit fruit2 = factory.Create("Orange");
}
}
Upvotes: 0
Reputation: 10512
C# supports switch-case
operator for strings, i.e. your code coud be rewritten as
switch (fileEx)
{
case "txt" : new Text2Pdf().Convert(file); break;
case "doc": new Word2Pdf().Convert(file); break;
...
}
If you change your classes' names to correspond to supported extensions then you will be able to construct them using reflection, like this (error checking omitted for brevity):
var t = Type.GetType(fileEx + "2Pdf");
var tConstructor = t.GetConstructor(Type.EmptyTypes);
var tInstance = tConstructor.Invoke(new object[0]);
((ConvertApi)tInstance).Convert(...);
This might require some additional work (i.e. create a separate class for each extension, deriving them from some base class - for example Doc2Pdf and Docx2Pdf both deriving from Word2Pdf).
The advantage is that you will not have to touch this part of code anymore. If you're planning to write some interface for plugins then it may come in handy.
The code above also has an assumption that your ConvertApi classes all have default parameterless constuctor.
Upvotes: 1
Reputation: 38825
In your base class, have something like this:
public abstract class ConvertApi
{
protected abstract string[] SupportedFilesImpl();
public bool Supports(string ext)
{
return SupportedFilesImpl.Contains(ext);
}
}
Now, your derived classes can implement this method:
public class Text2PDF : ConvertApi
{
protected override string[] SupportedFilesImpl { return new string[] { "txt", "log"}; }
}
public class Doc2PDF : ConvertApi
{
protected override string[] SupportedFilesImpl { return new string[] { "doc", "docx"}; }
}
... and so on for the rest of the converters. Then put these in a list...
List<ConvertApi> converters = new List<ConvertApi>();
converters.Add(new Text2PDF());
converters.Add(new Doc2PDF());
(Note, I'd probably have a class containing these rather than just a list, but anyway). Now, to find a converter:
foreach(ConvertApi api in converters)
{
if(api.Supports(fileExt))
{
// woo!
break;
}
}
Upvotes: 3
Reputation: 1500893
Assuming each converter is stateless, it sounds like you just want a Dictionary<string, ConvertApi>
:
private static readonly Dictionary<string, ConvertApi> ConverterByType =
new Dictionary<string, ConvertApi>
{
{ "txt", new Text2PdfConverter() },
{ "doc", new Word2PdfConverter() },
...
};
...
ConvertApi converter;
if (!ConverterByType.TryGetValue(fileEx, out converter))
{
// No converter available... do whatever
}
else
{
converter.Convert(file);
}
(The dictionary initialization will end up creating more converters than you really need for any converter that supports multiple extensions, but that's a different matter.)
If you need a new converter each time, make it a Dictionary<string, Func<ConvertApi>>
and populate it as:
{ "txt", () => new Text2PdfConverter() },
{ "doc", () => new Word2PdfConverter() },
... then call the delegate to get the converter when you need it.
Of course, this puts all the initialization in one place - you may want a way of getting the converters to register the extensions they understand with a "converter provider" of some type.
Upvotes: 2
Reputation: 1703
Use dependency injection where you pass in the supported files in the constructor of the class.
Upvotes: 0
Reputation: 6047
What about using a switch
?
switch(fileEx){
case "txt": new Text2Pdf().Convert(file); break;
case "doc": new Word2Pdf().Convert(file); break;
}
Upvotes: 0