2016-05-31 148 views
2

我試圖設計一個拼圖應用程序,使用bdd,dddoop。這個應用程序的目的是檢查頁面是否啓動,如果它包含某些元素或不包括鏈接,圖像等DDD:建模拼圖應用程序

使用BDD,寫我的場景,我想出了像Page類, Link,Image等,其具有如url,src,alt的性質。

,我有是我看到兩種可能性,檢查抗住網站的問題:1。 使用另一個類,一類crawler,這將使用包含在前面的類中的數據和打網絡來檢查頁面時,如果它們所包含的預期元件等:

$crawler = new Crawler(); 
$page = new Page($url); 

$pageReturned = $crawler->get($page); 

if ($pageReturned->isUp()) { 
    // continue with the checking of element... 
    $image = new Image($src, $alt); 

    if ($pageReturned->contains($image)) { 
    // check other things 
    } else { 
    // image not found on the page 
    } 
} 
  • 具有包括在類本身這種「爬行」行爲(其看起來更像oop我),這意味着我會問頁面是否已啓動,如果它包含給定元素等:

    $page = new Page($url); 
    
    if ($page->isUp()) { 
        $image = new Image($src, $alt); 
    
        if ($page->contains($image)) { 
        // check other things 
        } else { 
        // image not found on the page 
        } 
    } 
    
  • 我會嘗試使用#2,但我想知道我怎麼可以這樣做,而不必依賴於某個庫的爬行類。我希望能夠稍後在不同的庫之間切換,如goutteguzzle甚至直接使用curl

    也許我錯過了oop這一點......也許有更好/更聰明的做法,因此我的問題。 :)

    +0

    這就是SOLID中的D起作用的地方 - 依賴性倒置。更高層次的課程只應該依賴於抽象,而不是具體的課程。如果各種爬行實現實現一個公共接口,並且調用代碼僅對此接口起作用 - 則可以根據需要注入所需的具體實現。 – dbugger

    +0

    爲什麼要打擾一個複雜的領域模型?問題中唯一真正的目標是「爬蟲」,它在執行「爬行」行爲時需要保持狀態。其餘的「對象」只是數據結構。 「頁面」是否有轉換的狀態?它的行爲在其一生中是否有變化?如果不是,它只是一個數據結構,將被你的'Crawler'使用。 – 2016-06-01 20:39:29

    回答

    3

    要實現的一個有用的事情是,你的模型代碼往往是自包含的 - 它知道模型中的數據元素(即數據圖)和數據一致性規則,而不是其他任何東西。

    所以你的頁面模型可能會看起來像

    class Page { 
        URL uri; 
        ImageCollection images; 
    } 
    

    換句話說,該模型知道頁面和圖像之間的關係,但它並不一定知道那些東西在實踐中意味着什麼。

    要真正將您的域模型與真實世界進行比較,您需要向模型傳遞一些知道如何完成工作但不知道狀態的服務。

    class Crawler { 
        void verify(URL page, ImageCollection images) 
    } 
    

    現在你將它們匹配在一起;您構建爬網程序並將其傳遞給頁面。該頁面發現它的狀態,並將該國的履帶

    class Page { 
        void verifyWith(Crawler crawler) { 
         crawler.verify(this.uri, this.items); 
        } 
    } 
    

    當然,你可能不希望夫妻的頁面過於緊密的履帶;畢竟,您可能希望換出抓取程序庫,您可能想要對頁面狀態執行其他操作。

    所以你讓這個方法的簽名更一般;它接受一個接口,而不是具有特定含義的對象。在經典的書設計模式,這將是Visitor Pattern

    class Page { 
        interface Visitor { 
         void visitPage(URL uri, ImageCollection images); 
        } 
    
        void verifyWith(Visitor visitor) { 
         visitor.visitPage(this.uri, this.images); 
        } 
    }  
    
    class Crawler implements Page.Visitor { 
        void visitPage(URL page, ImageCollection images) { 
         .... 
        } 
    } 
    

    注意的一個例子 - 模型(頁)是負責維護數據的完整性。這意味着它傳遞給訪問者的任何數據應該是不可變的,或者失敗了模型狀態的可變副本。

    從長遠來看,您可能不希望像這樣嵌入頁面中的Visitor的定義。頁面是模型API的一部分,但訪問者是模型的一部分SPI。這並在這裏得到掩飾

    interface PageVisitor { 
        void visitPage(URL uri, ImageCollection images); 
    } 
    
    class Page { 
        void verifyWith(PageVisitor visitor) { 
         visitor.visitPage(this.uri, this.images); 
        } 
    }  
    
    class Crawler implements PageVisitor { 
        void visitPage(URL page, ImageCollection images) { 
         .... 
        } 
    } 
    

    一件事是,你似乎有「頁」

    // Here's one? 
    $page = new Page($url); 
    
    // And here is something else? 
    $pageReturned = $crawler->get($page); 
    

    一個教訓的兩種不同的實現是命名的東西;特別是要確保你沒有把兩個真正具有單獨含義的想法結合起來。在這種情況下,您應該清楚抓取工具返回的是哪種類型。

    例如,如果你正生活在一個無處不在的語言從靜止借了域名,那麼你可能看起來像

    $representation = $crawler->get($resource); 
    

    在您的例子說明,語言看起來更加HTML特殊,所以這可能合理

    $htmlDocument = $crawler->get($page) 
    

    之所以露出這樣的:文件/表示具有是值對象的概念非常適合 - 這是不可改變的東西不可變袋;您無法通過任何方式操作html文檔來更改「頁面」。

    值對象是純粹的查詢表面 - 這看起來像一個突變對他們的任何方法真的返回類型的新實例的查詢。

    值對象是由plalx在他的回答中描述的規範模式非常適合:

    HtmlSpecification { 
        boolean isSatisfiedBy(HtmlDocument); 
    } 
    
    +0

    非常感謝您的回答和解釋。事情是,我很難看到,我會在哪裏得到包含特定圖像的頁面的結果。如果我理解的很好,在調用'Page-> verifyWith($ crawler)'時會調用爬蟲'visitPage'方法,但是因爲這兩種方法都不會返回任何我會調用方法的布爾值給出結果的檢查/驗證? –

    +0

    最一般的答案:在頁面驗證通過後查詢搜尋器的狀態。 visitPage是一個可以修改搜尋器狀態的命令,例如將驗證錯誤添加到列表中。然後您可以檢查該列表是否爲空。 – VoiceOfUnreason

    1

    什麼這樣的事情?您可以利用任何現有的HTML解析框架構建可通過CSS選擇器查詢的文檔對象模型,並抽象出域接口背後的實現。

    我還使用規範模式爲頁面創建匹配標準,這將使創建新規則變得非常容易。

    enter image description here

    用法:

    var elementsQuery = new ElementsQuery('image[src="someImage.png"], a[href="http://www.google.com"]'); 
    var spec = new PageAvailable().and(new ContainsElements(elementQuery, 2)); 
    var page = pageLoader.load(url); 
    
    if (spec.isSatisfiedBy(page)) { 
        //Page is available & the page contains exactly one image with the attribute src=someImage.png and one link to google 
    } 
    

    有些事情可以做,以提高設計是創造一個流暢的生成器,可以讓你更容易地生成CSS選擇器(ElementsQuery)。

    E.g.

    如果你想最終能夠創造超越通過 ElementsQuery驗證元素的存在會暴露一個更強大的API來檢查文檔對象模型(DOM)規範
    var elementsQuery = new ElementsQueryBuilder() 
             .match('image').withAttr('src', 'someImage.png') 
             .match('a').withAttr('href', 'http://www.google.com'); 
    

    Anothing重要的事情。

    在上面的設計中,您可以用類似的方法替換DOM,並相應地調整PageSpecification API以提供更多規格的功率。

    public interface Element { 
        public String tag(); 
        public String attrValue(String attr); 
        public boolean containsElements(ElementsQuery query, ExpectedCount count); 
        public Elements queryElements(ElementsQuery query); 
        public Elements children(); 
    } 
    

    能夠訪問整個DOM結構從域訪問,而不是隻要求基礎設施服務,如果規定 - 滿意的優點是它的規格說明書申報和實施既可以住在域中。

    In @ VoiceOfUnreason的答案Crawler實現必須存在於基礎架構層中,並且規則的聲明存在於域(ImageCollection)中時,檢查這些規則的邏輯將存在於基礎架構中。

    最後,我想頁面監控條目可能是持久性的,並且可以通過UI或配置文件進行配置。

    我可能會做的是有兩個不同的有界上下文。一個維護頁面以監視其相關規範(Page是此上下文中的一個實體),另一個負責執行監視的上下文(Page是此上下文中的一個值 - 使用類似於我描述的實現)。

    +0

    @VoiceOfUnreason您怎麼看? – plalx

    +0

    不錯。圖像和元素查詢之間的綁定不太正確;一個流利的建設者可能會清理它(但讓這個例子更難理解)。此外,規範需要能夠查詢主題的狀態。 – VoiceOfUnreason

    +0

    你應該包括一些關於模式的散文鏈接:) – VoiceOfUnreason