2013-01-01 31 views
1

我有一組相關的類都從一個基類繼承。我想使用工廠方法爲這些類實例化對象。我想這樣做是因爲在將對象返回給調用者之前,我可以將這些對象存儲在由類名稱鍵入的字典中。然後,如果有一個特定類的對象的請求,我可以檢查是否已經存在於我的字典中。如果沒有,我將實例化它並將其添加到字典中。如果是這樣,那麼我會返回字典中的現有對象。這將基本上將我的模塊中的所有類轉換爲單例。Python - 我如何強制使用工廠方法來實例化對象?

我想這樣做,因爲他們都繼承的基類會自動包裝子類中的函數,而且我不希望函數被多次包裝,這就是目前發生的情況如果創建了兩個相同類的對象。

我可以考慮這樣做的唯一方法是檢查基類的__init__()方法中的堆棧跟蹤,該方法將始終被調用,並在堆棧跟蹤未顯示請求進行調用時引發異常對象來自工廠功能。

這是個好主意嗎?

編輯:這裏是我的基類的源代碼。我被告知我需要找出元類來完成這個更優雅的事情,但這是我現在所擁有的。所有頁面對象使用相同的Selenium Webdriver實例,該實例位於頂部導入的驅動程序模塊中。該驅動程序初始化非常昂貴 - 它在首次創建LoginPage時進行初始化。初始化後,initialize()方法將返回現有驅動程序,而不是創建新驅動程序。這個想法是,用戶必須先創建一個LoginPage。最終會有數十個Page類定義,並且它們將被單元測試代碼用來驗證網站的行爲是否正確。

from driver import get_driver, urlpath, initialize 
from settings import urlpaths 

class DriverPageMismatchException(Exception): 
    pass 

class URLVerifyingPage(object): 
    # we add logic in __init__() to check the expected urlpath for the page 
    # against the urlpath that the driver is showing - we only want the page's 
    # methods to be invokable if the driver is actualy at the appropriate page. 
    # If the driver shows a different urlpath than the page is supposed to 
    # have, the method should throw a DriverPageMismatchException 

    def __init__(self): 
     self.driver = get_driver() 
     self._adjust_methods(self.__class__) 

    def _adjust_methods(self, cls): 
     for attr, val in cls.__dict__.iteritems(): 
      if callable(val) and not attr.startswith("_"): 
       print "adjusting:"+str(attr)+" - "+str(val) 
       setattr(
        cls, 
        attr, 
        self._add_wrapper_to_confirm_page_matches_driver(val) 
       ) 
     for base in cls.__bases__: 
      if base.__name__ == 'URLVerifyingPage': break 
      self._adjust_methods(base) 

    def _add_wrapper_to_confirm_page_matches_driver(self, page_method): 
     def _wrapper(self, *args, **kwargs): 
      if urlpath() != urlpaths[self.__class__.__name__]: 
       raise DriverPageMismatchException(
        "path is '"+urlpath()+ 
        "' but '"+urlpaths[self.__class.__name__]+"' expected "+ 
        "for "+self.__class.__name__ 
       ) 
      return page_method(self, *args, **kwargs) 
     return _wrapper 


class LoginPage(URLVerifyingPage): 
    def __init__(self, username=username, password=password, baseurl="http://example.com/"): 
     self.username = username 
     self.password = password 
     self.driver = initialize(baseurl) 
     super(LoginPage, self).__init__() 

    def login(self): 
     driver.find_element_by_id("username").clear() 
     driver.find_element_by_id("username").send_keys(self.username) 
     driver.find_element_by_id("password").clear() 
     driver.find_element_by_id("password").send_keys(self.password) 
     driver.find_element_by_id("login_button").click() 
     return HomePage() 

class HomePage(URLVerifyingPage): 
    def some_method(self): 
     ... 
     return SomePage() 

    def many_more_methods(self): 
     ... 
     return ManyMorePages() 

這是如果一個頁面被實例化的時間屈指可數沒什麼大不了的 - 方法只會得到包裹的時間屈指可數和不必要的檢查,極少數會發生,但一切仍然可以工作。但是,如果一個頁面被實例化了幾十次,幾百次或幾萬次,那將會很糟糕。我只是把國旗在類定義爲每個頁面,並檢查是否這些方法已經被包裹着,但我喜歡保持類定義純淨搡所有的戲法到的深角落的想法我的系統,沒有人可以看到它,它只是工作。

+0

你是一個實例工廠或類工廠後?你有沒有嘗試過一種相對簡單的類裝飾器方法? (我們可以看到你試過的東西,例如不起作用嗎?) –

+3

讓我直截了當。你想把所有的類變成單例,因爲它們的基類被破壞了?聽起來就像拍攝你的狗,因爲一個瘋狂的屁股誰不斷闖入你的房子對狗過敏。 IOW是一個完全錯誤的「解決方案」。你確定你不想修復基類,或者至少解決它的缺陷? – delnan

+0

@delnan我認爲線索是在標題:「我怎麼** **力......」 - 重點煤礦 –

回答

2

這聽起來像你想提供一個__new__實現:喜歡的東西:

class MySingledtonBase(object): 
    instance_cache = {} 
    def __new__(cls, arg1, arg2): 
     if cls in MySingletonBase.instance_cache: 
      return MySingletonBase.instance_cache[cls] 
     self = super(MySingletonBase, cls).__new__(arg1, arg2) 
     MySingletonBase.instance_cache[cls] = self 
     return self 
+0

這顯示瞭如何避免顯式工廠方法,而不是如何強制使用顯式工廠方法。如果他重新思考自己的問題,但沒有任何爭論,它可以想象成爲OP實際上想要的東西,它只是回答了一個不同的,只是模糊不清的問題。 – abarnert

+1

Ahh ..嗯..是的......工廠方法可以,如果Jon *真的需要*,用'__new__'驗證的一些特殊的,魔術的祕密參數調用'__new__'。它可能是不合理的,不可能通過意外傳遞(比如,guid),或者只是有用的,但冗長的參數來告訴'__new__'它需要做什麼。 – SingleNegationElimination

+0

如果你想要走得那麼遠,那麼讓事情變得更容易,比如只能在基類和'__new__'方法創建的範圍內可用的'object()'實例。當然,正如你強烈暗示的那樣,你幾乎肯定不想走那麼遠。 – abarnert

3

在Python,這是幾乎從來沒有值得嘗試「強制」任何東西。無論你想出什麼,有人可以通過monkeypatching你的課程,複製和編輯源代碼,與字節碼混搭等。

所以,只需寫下你的工廠,並以正確的方式獲取文檔你的類的實例,並希望任何人使用你的類來了解TOOWTDI,並沒有違反它,除非她真的知道自己在做什麼,並願意找出並處理其後果誰寫的代碼。

如果你只是想防止意外,而不是故意的「濫用」,這是一個不同的故事。事實上,這只是標準的設計合同:檢查不變量。當然,在這一點上,SillyBaseClass已經做錯了,已經太晚了修復它,所有你能做的就是assertraiselog,或任何其他爲宜。但這就是你想要的:這是應用程序中的邏輯錯誤,唯一要做的就是讓程序員修復它,所以assert可能正是你想要的。

所以:

class SillyBaseClass: 
    singletons = {} 

class Foo(SillyBaseClass): 
    def __init__(self): 
     assert self.__class__ not in SillyBaseClass.singletons 

def get_foo(): 
    if Foo not in SillyBaseClass.singletons: 
     SillyBaseClass.singletons[Foo] = Foo() 
    return SillyBaseClass.singletons[Foo] 

如果你確實想從得到這個地步停止的事情,你可以檢查不變較早,在__new__方法,但除非「SillyBaseClass得到搞砸了」相當於爲了「發射核武器」,爲什麼要麻煩?

+0

嗯,我那愚蠢的基礎課並不是很遠 - 我仍然有時間去改變我的設計。看到上面我原來的問題編輯。我試圖避免錯誤(例如類循環中實例化數百次)而不是故意濫用。 – jononomo

2

而不是增加複雜的代碼趕在運行時的錯誤,我會首先嚐試使用約定來指導你的模塊的用戶可以自己做了正確的事情。

給你的類「私人」的名字(用下劃線前綴),給他們暗示,他們不應該被實例化的名稱(如_Internal ...),讓你的工廠函數「公共」。

也就是說,這樣的事情:

class _InternalSubClassOne(_BaseClass): 
    ... 

class _InternalSubClassTwo(_BaseClass): 
    ... 

# An example factory function. 
def new_object(arg): 
    return _InternalSubClassOne() if arg == 'one' else _InternalSubClassTwo() 

我還文檔字符串或註釋添加到每個類,如「請勿用手實例化這個類,使用工廠方法new_object」。

相關問題