0
\$\begingroup\$

Earlier today I tried to implement an example of the bridge design pattern, but I ended up misinterpreting it.

I made a lot of changes:

public interface VisitorAPI {
    String visit (String url);
}

public class JsoupVisitorAPI implements VisitorAPI {

    @Override
    public String visit(String url) {
        try {
            return Jsoup.connect(url).timeout(TIMEOUT).userAgent(USER_AGENT).postDataCharset(CHARSET).get().html();
        } catch (IOException e) {
            return "";
        }

    }
}

public class JauntVisitorAPI implements VisitorAPI {

    private UserAgent userAgent;

    public JauntVisitorAPI(UserAgent userAgent) {
        this.userAgent = userAgent;
    }


    @Override
    public String visit(String url) {
        try {
            return userAgent.visit(url).innerHTML();
        } catch (ResponseException e) {
            return "";
        }
    }
}

public class HtmlUnitVisitorAPI implements VisitorAPI {
    WebClient webClient;

    public HtmlUnitVisitorAPI() {
        this.webClient = createWebClient();
    }

    @Override
    public String visit(String url) {
        Page p = null;
        try {
            p = webClient.getPage(url);
            return p.getWebResponse().getContentAsString(Constants.CHARSET);
        } catch (IOException e) {
            return "";
        }
    }

    private WebClient createWebClient() {
        WebClient webClient = new WebClient(BrowserVersion.FIREFOX_38);
        webClient.getOptions().setJavaScriptEnabled(true);
        webClient.getOptions().setCssEnabled(false);
        webClient.getOptions().setThrowExceptionOnScriptError(false);
        webClient.setAjaxController(new NicelyResynchronizingAjaxController());
        webClient.waitForBackgroundJavaScript(Constants.TIMEOUT);
        return webClient;
    }
}

public interface ParserAPI<T> {
    T parseDocument (String htmlCode);
}

public class JSoupParseAPI implements ParserAPI<Document> {

    @Override
    public Document parseDocument(String htmlCode) {
        Document doc = Jsoup.parse(htmlCode);
        doc.charset(Charset.forName(CHARSET));
        return doc;
    }
}

public class JauntParserAPI implements ParserAPI<Document> {
    private UserAgent userAgent;

    public JauntParserAPI(UserAgent userAgent) {
        this.userAgent = userAgent;
    }

    @Override
    public Document parseDocument(String htmlCode) {
        try {
            return userAgent.openContent(htmlCode);
        } catch (ResponseException e) {
            //I don't want to return Null, but its difficult to implement an Empty Jaunt Document
            return null;
        }
    }
}

public interface CrawlerAPI {    
    String visit(String url);    
    <T> T parse(String htmlCode);    
}

public class CrawlerAPIImpl implements CrawlerAPI {

    private VisitorAPI visitorAPI;
    private ParserAPI parserAPI;

    public CrawlerAPIImpl(VisitorAPI visitorAPI, ParserAPI parserAPI) {
        this.visitorAPI = visitorAPI;
        this.parserAPI = parserAPI;
    }

    @Override
    public String visit(String url) {
        return visitorAPI.visit(url);
    }

    @Override
    public <T> T parse(String htmlCode) {
        return (T) parserAPI.parseDocument(htmlCode);
    }
}


public interface Constants {
    String CHARSET = "ISO8859_1";
    int TIMEOUT = 20000;
    String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36";
}

So I use it this way:

public class CrawlerAPIImplTest {

    CrawlerAPI crawlerAPI;

    @Before
    public void setUp() {
        crawlerAPI = new CrawlerAPIImpl(new HtmlUnitVisitorAPI(), new JSoupParseAPI());
    }

    @Test
    public void testVisit() throws Exception {
        String visit = crawlerAPI.visit("http://www.angeloni.com.br/super/index?grupo=20");
        assertNotNull(visit);
        assertNotEquals("", visit);

    }

    @Test
    public void testParse() throws Exception {
        Document document = crawlerAPI.<Document>parse("<html> <body> </body> </html>");
        assertNotNull(document);
    }
}

Is it a valid implementation of the bridge pattern?

\$\endgroup\$
2
  • \$\begingroup\$ I'm confused. Do you want to apply the Bridge pattern as an exercise or do you want to use some design pattern that would solve your ever-changing implementation problem (which is what your previous questions lead me to believe)? \$\endgroup\$
    – Tunaki
    Jan 31, 2016 at 0:33
  • \$\begingroup\$ @Tunaki Both: The APP I'm made is a Weekend project I'm doing for fun. I'm seeking a way to reach what I want: make the use these crawler's API more interchangeable. So I made a post in Programmers forum and someone indicated me the Bridge Pattern to solve that problem. Now I'm trying to implement this pattern correctly, for exercise and to solve my problem. \$\endgroup\$
    – alexpfx
    Jan 31, 2016 at 1:03

1 Answer 1

2
\$\begingroup\$

Yes, what you implemented looks a lot like the bridge pattern. I have to admit that I never wittingly applied the bridge pattern. I rather think in terms of the strategy pattern which is quite similar.

Since I never understood the purpose of the bridge pattern 100%, I just read a bit about it. The main difference seems to be that the strategy pattern is used to be able to switch different strategies at runtime while the bridge pattern aims to make a class future-proof by avoiding dependencies on certain implementations of some functionality (for instance vendor specific code that might have to change).

The latter is basically what you are doing. Your Crawler relies on a Visitor and a Parser (I suggest you drop the ...API suffix), not on concrete implementations but only the interfaces. You can then have different classes or hierarchies of classes without ever having to change the Crawler code.

This allows you to subclass the Crawler to create variations in another dimension (maybe a crawling strategy that cannot be easily extracted into another class). Although you only have one implementation, you indicated that there might be different implementations by providing an interface (in this case it is probably more appropriate to have an abstract class because you can then reuse the two fields you always have).

I think it is a good example of the bridge pattern.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ One other purpose for the Bridge Pattern is to implement multiple inheritance in a language that supports only single inheritance. In this case the CrawlerAPI is inheriting VisitorAPI and ParserAPI. This is following the best practise to favor composition over inheritance. \$\endgroup\$
    – oopexpert
    Jan 31, 2016 at 21:16
  • \$\begingroup\$ @oopexpert, good point, hadn't thought of that! \$\endgroup\$
    – lex82
    Jan 31, 2016 at 22:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.