2016-05-31 149 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