2015-07-21 70 views
1

我閱讀了一些關於工廠抽象工廠模式的教程,並看到了一些示例。在其中一篇教程中,我讀到工廠模式可以取代主要的「if」或「switch case」語句,並遵循開放/封閉(固定)原則。基於輸入變量的工廠

在我的一個項目中,我有一個巨大的「開關盒」,我想用一個(抽象)工廠代替。它已經是基於接口的,所以實現一個工廠不應該那麼困難,但是在我閱讀的所有示例中,工廠根據配置生成了一個具體類型。任何人都可以指出我正確的方向如何實現一個工廠,可以產生多種類型的枚舉遵循堅實的原則,取代了大型的「開關盒」....或者我誤導了,是「開關盒」搬到工廠?

代碼在這一刻:

所有的
public interface ISingleMailProcessor : IMailProcessor 
{ 
    MailResult ProcesMail(Mail mail); 
} 


public MailResult GetMailResult(mail) 
{ 
    ISingleMailProcessor mailprocessor; 
    switch (mail.MailType) 
    { 
     case MailConnector.MailType.AcronisBackup: 
     mailprocessor = new AcronisProcessor(); 
     return mailprocessor.ProcesMail(mail); 
     case ......etc. 
    } 
} 
+0

'enum'的長度是固定的 - 你不能以任何方式擴展 - 所以它就像一個'switch'語句。所以在某些方面它有相同的限制。你可以請你發佈你想要替換的'enum'和'if' /'switch'語句的例子嗎? – Enigmativity

+0

我添加了一些已經在項目中的代碼。 –

+0

所以它始終是'MailResult'從'Mail.MailType'返回? – Enigmativity

回答

0

你在那裏實施的是戰略模式,它仍然是一種有效的方法。無論如何,如果你想更換整個交換機並使其更易於維護,你可以使用這個接口。每個郵件處理器都會實現這個接口。然後你會有這個

public class MailProcessorExecutor 
{ 
    public MailProcessorSelector(IEnumerable<IProcessMail> processors) 
    { 
      _processors=processors; 
    } 

    public MailResult GetResult(Mail mail) 
    { 
     var proc=_processor.FirstOrDefault(p=>p.CanProcess(mail.MailType)); 
     if (proc==null) 
     { 
      //throw something 
     } 

     return proc.GetMailResult(mail); 
    } 

    static IProcessMail[] _procCache=new IProcessMail[0]; 

    public static void AutoScanForProcessors(Assembly[] asms) 
    { 
     _procCache= asms.SelectMany(a=>a.GetTypesDerivedFrom<IProcessMail>()).Select(t=>Activator.CreateInstance(t)).Cast<IProcessMail>().ToArray(); 
    } 

    public static MailProcessorExecutor CreateInstance() 
    { 
     return new MailProcessorExecutor(_procCache); 
    } 
    } 

    //in startup/config 
    MailProcessorExecutor.AutoScanForProcessors([assembly containing the concrete types]); 

//usage 
var mailProc=MailProcessorExecutor.CreateInstance(); 
var result=mailProc.GetResult(mail); 

好的,所以,重點是會有一個對象負責選擇和執行處理器。靜態方法是可選的,AutoScan方法在任何給定的程序集中搜索具體的IPocessMail實現,然後實例化它們。這使您可以添加/刪除任何處理器,並自動使用(不需要其他設置)。也沒有切換涉及,永遠不會有。請注意,GetTypesFromDerived是我使用的輔助方法(它是我的CavemanTools庫的一部分),Activator需要無參數的構造函數。取而代之的是,您可以使用DI容器來獲取實例(如果您使用的是一個或處理器具有代價)

如果您使用DI容器,則不需要CreateInstance方法。只需將MailProcessorExecutor註冊爲單例,然後在需要它的位置注入它(將其作爲構造函數參數傳遞)。

+0

你的意思是我的方法使用策略? GetTypesDerivedFrom是否使用反射(您必須考慮效率和類型安全性)? – ntohl

+0

我的意思是OP代碼類似於策略。是的'GetTypesDerivedFrom'使用反射。效率? :)))你不知道在封面下有多少東西正在使用反射。關於類型安全,是的,我忘了一個演員(我已經更新了我的答案,包括它)。 – MikeSW

+0

我試圖考慮針對OP問題的策略解決方案,但會出現一個開關,策略的上下文被分配給實際使用的AcronisBackupStrategy。然後調用一個'GetResult'。我認爲戰略不適用。我不知道在封面下使用了多少反思,但我有一個項目,在那裏只需要消除1個LINQ即可恢復軟件的可用性。 – ntohl

0

首先,我要對推薦CCD Webcast about factories。這對這個話題非常有幫助,同時也顯示了我們可能遇到的一些問題。 您也可以找到關於this objectmentor document

一些好的信息正如你可以看到網絡直播的總結,更要遵循「創造」類型安全,你可以工作少的問題域的OpenClosed校長。

因此,抽象工廠也可以對字符串等更動態的值進行操作。例如,你可以有一個方法。

string GetAllPossibilities(); // Returns all possible kinds that can be constructed 

和相關

T Create<T>(string kind) 

呼叫現在將只需要通過唯一標識請求實例類型的字符串。您的標記可能是「自制」的,如「Rectangle」或事件TypeNames,但這意味着該組件之間存在更多依賴關係,因爲命名空間更改可能會中斷調用者。

所以,您的通話可能類似於下列之一:

Factory.Create("Acronis") // Your Marker 

Factory.Create("MYNameSpace.AcronisProcessor") //TypeName 

Factory.Create<AcronisProcessor>() //Type 

所以,你會不會有在工廠外switch語句。在工廠內部,您可能會有一些,或者您可以考慮創建某種動態對象。

工廠仍然有swithc報表切換你的自制標識或代碼做一些像

var type = Type.GetType(kind); 
return Activator.CreateInstance(type) as T; 

但是,因爲這是從主域分隔LOGIK它沒有像了這個在─重要性。

有了這個第一眼看,如果你有新的選擇,你不會有api實現突破性的變化。

但仍有某種潛在的語義依賴。

編輯:正如你可以在下面的討論中看到的,我切斷了一些細節,因爲我認爲他們會弄清楚主要觀點(OpenClosed Principales和工廠模式)。但還有一些不應該被遺忘的點。像「什麼是應用程序根目錄以及如何設計它」。爲了獲得所有的細節,網絡廣播(也是這個網站的其他人)是一個更好的方法來學習這個細節,然後這篇文章在這裏。

+1

答案甚至沒有提到示例中的切換案例。在你的例子中,'Create'將包含switch類型,取決於kind。 – ntohl

+0

我剛剛注意到幾分鐘前,我現在正在擴展我的帖子,以清除它 –

+0

恕我直言,這不是一個很好的使用工廠。你已經鏈接了一個Bob叔叔的文章,而U Bob說這些開關應該包含在Main方法中。只是爲了初始化工廠。您不會傳遞此參數來創建,而是使用正確的類型初始化工廠。 – ntohl

1

在你的情況,只是重構它的方法應該是綽綽有餘。

private ISingleMailProcessor CreateMailProcessor(MailType type) 
{ 
    switch (type) 
    { 
     case MailConnector.MailType.AcronisBackup: 
     return new AcronisProcessor(); 
     case ......etc. 
    } 
} 

public MailResult GetMailResult(mail) 
{ 
    ISingleMailProcessor mailprocessor = CreateMailProcessor(mail.MailType); 
    return mailprocessor.ProcesMail(mail); 
} 

我不認爲使用工廠會幫助你在這裏。如果「郵件類型」是在實際創建併發送郵件本身的代碼之外決定的,那麼工廠是有意義的。

然後,將有特定的工廠爲每種類型的郵件發送,用接口來創建發件人對象。但即使如此,如果您每次都需要一個新的發件人實例,那也是有意義的。就你而言,只需傳入你現在的界面應該足夠了。

你可以在這裏閱讀的工廠我意見https://softwareengineering.stackexchange.com/a/253264/56655

+1

[不要因爲「YAGNI」的理由而失敗](http://程序員。 stackexchange.com/questions/253254/why-do-people-nowadays-use-factory-classes-so-often/253264#comment509336_253264),第一條評論提到。 Luuk表示,這個開關已經太大了。 – ntohl

+0

我主張的問題是我看到創建實例只是一個完全獨立的問題,不應該成爲業務領域的一部分。不管它是如何提取的,它應該被提取 –

0

您需要爲訪問者模式。

{ 
    public MailResult GetMailResult(mail) 
    { 
     _objectStructure.Visit(mail) 
    } 
    ObjectStructure _objectStructure= new ObjectStructure(); 
    constructor() { 
     _objectStructure.Attach(new AcronisBackupVisitor()); 
     _objectStructure.Attach(new ...) 
    } 
} 

class AcronisBackupVisitor: Visitor { 
    public override void Visit(HereComesAConcreteTypeDerivedFromMail concreteElement) 
    { 
     // do stuff 
    } 
    public override void Visit(HereComesAConcreteTypeDerivedFromMailOther concreteElement) 
    { 
     //don't do stuff. We are not in the right concrete mail type 
    } 
} 

通過這種方式,我們可以通過動態類型Mail你的具體的區分。只需製作Mail摘要,即可派生Acronis郵件以及其他類型的郵件。我已經從here開始執行這個例子。