2012-03-16 119 views
0

我有大約20個類從ConvertApi類派生。每個班級從父母班級分享Convert方法,並擁有獨特的屬性SupportedFiles。我使用這些類的文件操作和我的代碼看起來像c#編程代碼優化

if (fileEx=="txt") 
    new Text2Pdf().Convert(file) 
else 
    if (fileEx=="doc") 
    new Word2Pdf().Convert(file) 
    else 
    //and so on.. 

我知道,因爲20倍if運營商重複這看起來糟透了,這些代碼可以優化,但無法找到一個方法來做到這一點。任何人都可以幫我嗎?

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(....); 
} 
+2

首先,改變各種'如果(fileEx = 「TXT」)''來,如果(fileEx == 「TXT」)'。編輯:你已經編輯你的問題。現在沒事了。 – 2012-03-16 11:23:50

+0

在C#中缺少虛擬類方法的情況之一是令人討厭的情況之一。 – CodesInChaos 2012-03-16 11:25:21

+0

這些枚舉真的屬性?不是那些嵌套的每個類的枚舉類型? – Glenn 2012-03-16 11:26:17

回答

3

在你的基類,有這樣的事情:

public abstract class ConvertApi 
{ 
    protected abstract string[] SupportedFilesImpl(); 

    public bool Supports(string ext) 
    { 
     return SupportedFilesImpl.Contains(ext); 
    } 
} 

現在,您的派生類可以實現此方法:

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"}; } 
} 

...等的轉換器的其餘部分。然後把這些在列表...

List<ConvertApi> converters = new List<ConvertApi>(); 
converters.Add(new Text2PDF()); 
converters.Add(new Doc2PDF()); 

(注意,我可能有一個包含這些,而不僅僅是一個列表的一類,但無論如何)。現在,找到一個轉換器:

foreach(ConvertApi api in converters) 
{ 
    if(api.Supports(fileExt)) 
    { 
     // woo! 
     break; 
    } 
} 
+0

此解決方案在我的情況下不起作用,因爲我失去了訪問子類屬性的可能性。 – Tomas 2012-03-16 12:17:11

+0

@Tomas,如果您在此時需要訪問兒童屬性,那麼您的設計就會被擰緊。 :) – 2012-03-16 12:46:02

+0

我剛剛解決了使用動態關鍵字和您提供的代碼的問題。 – Tomas 2012-03-16 13:13:35

0

怎麼樣使用switch

switch(fileEx){ 
    case "txt": new Text2Pdf().Convert(file); break; 
    case "doc": new Word2Pdf().Convert(file); break; 
} 
+3

不推薦 - 這是[反模式](http://stackoverflow.com/questions/505454/large-switch-statements-bad-oop/505555#505555)。 – 2012-03-16 11:30:15

+0

+1提反模式! – 2012-03-16 11:34:02

+0

你應該利用OO polymorphysm。即'ConvertAPI x = new Text2Pdf(); x.Convert(file);'但這只是一個簡短的答案,更長的答案是使用工廠或依賴注入,但我不能在這裏詳細說明,因爲我在我的手機上。 – 2012-03-16 11:34:51

0

使用依賴注入,您可以在類的構造函數中傳遞支持的文件。

+2

這樣的答案應該更詳細闡述... – 2012-03-16 11:28:09

+0

請添加更多信息。 – Tomas 2012-03-16 11:29:28

2

假設每個轉換器是無狀態的,這聽起來像你只是想要一個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); 
} 

(字典初始化最終將建立多個轉換器比你真的需要支持多個分機任何轉換,但是這另當別論)

如果你需要一個新的轉換器每一次,使之成爲Dictionary<string, Func<ConvertApi>>並填充它。

{ "txt",() => new Text2PdfConverter() }, 
{ "doc",() => new Word2PdfConverter() }, 

...然後在需要時調用委託來獲取轉換器。

當然,這將所有的初始化放在一個地方 - 您可能需要一種方法,讓轉換器到的寄存器他們可以通過某種類型的「轉換器提供者」瞭解的擴展名。

+2

如果轉換器支持多個文件擴展名,會發生什麼情況? :) – 2012-03-16 11:29:40

+0

@ Moo-Juice:正在爲此編輯 - 最終會出現多個實例,除非您採取措施避免它。這絕對是可以避免的,但我首先會選擇最簡單的選項。 – 2012-03-16 11:31:07

+0

此解決方案在我的情況下不起作用,因爲我失去了訪問子類屬性的可能性。您的代碼將返回ConvertApi父類,但我需要下注Child類,因爲子類將包含其他屬性和方法。 – Tomas 2012-03-16 12:20:36

1

您需要在這裏使用abstract factory pattern。所有的文本轉換器應該來自實現你的Convert方法的通用接口ITextConverter。文件擴展名將是您工廠的參數。下面是一個示例(鍵入「即時」,有時從源代碼複製粘貼代碼,因此可能會出現拼寫錯誤。此處的目標是爲您提供靈活實現的一般想法)。

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) { ... } 
} 

在您的應用中。配置文件,您添加到了configSections:

<section name = "ConvertersConfig" type = "ConvertersConfigConfigHandler, MyAssembly" /> 

,這下面你configSections:

<ConvertersConfig> 
    <Converter Type="txt" Assembly="MyAssembly" Class="MyAssembly.TextConverter" /> 
    <Converter Type="pdf" Assembly="MyAssembly" Class="MyAssembly.PdfConverter" /> 
</ConvertersConfig> 

然後調用會是這樣的:

IFileConverter converter = FileConverterFactory.Create("txt"); 
converter.Convert("c:\temp\myfile"); 

編輯的代碼給了一個更「通用」的解決方案。

+0

如果這裏的「ITextConverter」定義了一個屬性來獲得它能夠轉換的文件,那麼最好是imho,否則「可用」轉換就會固有地與工廠綁定轉換器本身知道這些信息。 – 2012-03-16 11:41:12

+0

@ Moo-Juice:當然你是對的。只需輸入代碼即可顯示一個簡單的示例。事實上,如果我必須自己實現這個東西,我會使用依賴注入+抽象工廠。 – 2012-03-16 11:43:08

0

更慢,但更動態...使用對象工廠。這是一篇很好的文章,似乎符合您的需求。

http://www.codeproject.com/Articles/12986/Generic-Object-Factory

從文章的重要的東西:

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; 
} 

用法:

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"); 
    } 
} 
+1

您能解釋鏈接的內容嗎,所以在鏈接死了的3年內,有人可以從您的帖子中獲得一些幫助嗎? – 2012-03-16 11:52:26

+0

你是對的......當我點擊死鏈接時,我討厭它,而在這裏我正要創建一個。 LOL – 2012-03-16 11:54:38

1
  1. C#支持switch-case操作字符串,也就是說,如果你改變你的類的名稱對應於支持的擴展,那麼你將能夠建立他們使用反射,這樣的(錯誤代碼coud被改寫爲

    switch (fileEx) 
    { 
        case "txt" : new Text2Pdf().Convert(file); break; 
        case "doc": new Word2Pdf().Convert(file); break; 
        ... 
    } 
    
  2. 檢查略去了):

    var t = Type.GetType(fileEx + "2Pdf"); 
        var tConstructor = t.GetConstructor(Type.EmptyTypes); 
        var tInstance = tConstructor.Invoke(new object[0]); 
        ((ConvertApi)tInstance).Convert(...); 
    

這可能需要一些額外的工作(即爲每個擴展創建一個單獨的類,從某些基類中派生它們 - 例如Doc2Pdf和Docx2Pdf都來自Word2Pdf)。

好處是您不必再觸摸這部分代碼。如果您打算爲插件編寫一些界面,那麼它可能會派上用場。

上面的代碼還假定您的ConvertApi類都具有默認無參數構造函數。

1

你可以使用一些反思:

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"); 
     } 

然後你只需要調用Convert()在ConvertApi返回。
當然,這需要您在您的基類中添加一個名爲SupportedFiles的virtual List<String>

這使它看起來像

public abstract class ConvertApi 
{ 
    public abstract void Convert(); 

    public virtual List<String> SupportedFiles {get;set;} 
} 
+0

'Assembly.GetExportedTypes()'返回'Type'數組,'Type'不是'ConvertApi'。這個想法很好,但正確的實施會更復雜一些。 – 2012-03-16 11:50:03

+0

@SergeyKudriavtsev對,我更新了我的答案。 – 2012-03-16 12:37:42

+0

不過,您正在將'List '分配給'List 轉換器'。這會引發編譯器錯誤「Can not assign ... incompatible types」。要正確地做到這一點,您可以返回一個'Type'或第一個實例化返回對象,類似於我的答案代碼。 – 2012-03-16 12:43:00