0
\$\begingroup\$

I made an implementation of the Bridge Pattern to handle ever-changing in crawler APIs that I'm using in my APP.

public interface CrawlerApi {
    // common constants to serveral implementations.
    String CHARSET = "ISO8859_1";
    int TIMEOUT = 20000;

    /**
     * Visit an url and return contents in String format.
     *
     * @param url
     * @return
     */
    String visit (String url);

    /**
     * Parse an Html code into an Navigable object that is provided by the API.
     *
     * @param htmlCode
     * @param documentType
     * @param <T>
     * @return
     */
    <T> T getDocument (String htmlCode, Class <T> documentType);

}

public class HtmlUnitCrawlerApi implements CrawlerApi {
    private WebClient webClient;

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


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

    @Override
    public <T> T getDocument(String htmlCode, Class<T> documentType) {
        throw new IllegalArgumentException("not implemented");
    }


    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(TIMEOUT);
        return webClient;
    }

}

public class JsoupCrawlerApi implements CrawlerApi {
    public static final 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";


    @Override
    public String visit(String url) {
        return connect.apply(url).html();
    }

    //I made this static because of the peculiarity of api that is static and because I have to
    //set some config parameters.
    private static Function<String, Document> connect = url -> {
        try {
            return Jsoup.connect(url).timeout(TIMEOUT).userAgent(USER_AGENT).postDataCharset(CHARSET).get();
        } catch (IOException e) {
            return new Document("");
        }
    };

    private static Function<String, Document> parse = html -> {
        Document doc = Jsoup.parse(html);
        doc.charset(Charset.forName(CHARSET));
        return doc;
    };


    @Override
    public <T> T getDocument(String htmlCode, Class<T> type) {
        return (T) parse.apply(htmlCode);
    }

}

Usage:

public class ApiClientExample {
    private CrawlerApi crawlerApi;

    public ApiClientExample(CrawlerApi crawlerApi) {
        this.crawlerApi = crawlerApi;
    }

    public void visit (String url){
        crawlerApi.visit(url);
    }
}

Concerns:

Is this code clean enough?

Is there anything that can be improved?

\$\endgroup\$
2
  • \$\begingroup\$ A great way to be sure you're dealing with a pattern is to map all the elements. The GoF design for Bridge is here. I don't see the RefinedAbstraction classes. Why not just Strategy pattern? \$\endgroup\$ Feb 1, 2016 at 16:42
  • \$\begingroup\$ @Fuhrmanator As I was studying, strategy is more for handle changes at runtime. because I had to change the Api at implementation time, the bridge is more appropriated. You are right about the lack of Refined abstraction, I had not been able to correctly interpret the pattern. I made changes in this implementation and open another question with the updated code \$\endgroup\$
    – alexpfx
    Feb 2, 2016 at 0:42

1 Answer 1

1
\$\begingroup\$

I looked at it twice and I do not see any kind of Bridge Pattern. I see a simple abstraction and some concrete implementations of it. "ApiClientExample" is maybe a wrapper. Technically it seems to me ok.

Nevertheless I'd like to comment on one semantic thing:

As the method "visit" is totally fine, the method "getDocument()" seems strange to me. Without totally rejecting it I'd make some research on the liskov substitution principle. You also should consider a wrong abstraction level or a wrong semantical assigment to the interface "CrawlerAPI" as the method "getDocument" is totally generic. Will the return value of "visit" be put into "getDocument()"? If so, the method is at the wrong place. You introduced a mental mapping and it would be more a cascading call than a method delared in the same interface.

\$\endgroup\$
4
  • \$\begingroup\$ I tried to follow this implementation: en.wikipedia.org/wiki/Bridge_pattern. But perhaps it is incomplete yet. Perhaps with the way I will use it end up be more like a Bridge. But What I need for now: an easy way to exchange crawling API if i not suit me. That's why the visit and getDocument and another methods I would create are in this class. Maybe I should separate this two methods. You are right too about the substitution principle. It will not be too easy to substitute the Api because of getDocument, which is different for each Api. I will rethink this part. \$\endgroup\$
    – alexpfx
    Jan 30, 2016 at 21:38
  • \$\begingroup\$ if I do it: you'll be more like the Bridge Pattern: public interface CrawlerApi { VisitorApi getVisitorApi (); DocumentParseApi getDocumentParseApi (); } \$\endgroup\$
    – alexpfx
    Jan 30, 2016 at 21:56
  • \$\begingroup\$ public interface VisitorApi { String visit (String url); } \$\endgroup\$
    – alexpfx
    Jan 30, 2016 at 21:57
  • \$\begingroup\$ public interface DocumentParseApi <T> { T parseDocument (String htmlCode); } \$\endgroup\$
    – alexpfx
    Jan 30, 2016 at 21:58

Your Answer

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

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