转载

[读书笔记]使用Builder模式创建Selenium WebDriver

在《[读书笔记]《Effective Java》第二章》[ http://www.flyml.net/2017/02/05/effective-java-chapter-1/ ] 的Item2 提到:

使用builder替代多个参数的constructor

正好, 我要基于Selenium WebDriver 写一个爬虫。因为实际情况不一样, 有的时候需要使用不同的userAgent, 比如模拟移动浏览器, 有的时候需要挂不同的代理。 而且, 未来还很有可能通过更多的参数构建不同的WebDriver实例。其中,最重要的是要构建出不同的 DesiredCapabilities

这是一个实际使用Builder模式的好场景,因为 DesiredCapabilities 的参数会越来越多

如果不使用Builder模式, 在当前情况下, 其实也是可以的:

非Builder模式

import org.openqa.selenium.phantomjs.PhantomJSDriverService;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
 
public class DesiredCapabilitiesDemo {
 
 public DesiredCapabilitiesbuild(String strProxy, String strUserAgent) {
 DesiredCapabilitiescaps = new DesiredCapabilities();
 
 if(strProxy != null) {
    org.openqa.selenium.Proxyproxy = new org.openqa.selenium.Proxy();
    proxy.setHttpProxy(strProxy)
         .setFtpProxy(strProxy)
         .setSslProxy(strProxy);
    caps.setCapability(CapabilityType.PROXY, proxy);
    }
 
 if(strUserAgent != null) {
 caps.setCapability(
 PhantomJSDriverService.PHANTOMJS_PAGE_SETTINGS_PREFIX + "userAgent", strUserAgent);
 }
 
 return caps;
 }
}

不过如果当参数更多的时候, 我们有两种选择:

  1. 原方法增加新的参数但是这种方法,会对原来的代码有影响, 会因为参数变多直接编译不通过。
  2. 增加新的同名方法, 使用重载机制但是每增加一个新的参数,就需要再新增一个重载方法, 在管理上面会非常的麻烦。甚至可能不同参数的组合也会被限制

这个时候, 使用Builder模式就比较灵活了。

本文原创,原文链接:http://www.flyml.net/2017/02/09/effective-java-builder-webdriver-demo/

使用Builder模式

import org.openqa.selenium.phantomjs.PhantomJSDriverService;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
 
public class DesiredCapabilitiesBuilder {
 
 private String proxy;
 private String userAgent;
 
 public DesiredCapabilitiesBuilder(){}
 
 public DesiredCapabilitiesBuilderproxy(String proxy) {
 this.proxy = proxy;
 return this;
 }
 
 public DesiredCapabilitiesBuilderuserAgent(String userAgent) {
 this.userAgent = userAgent;
 return this;
 }
 
 public DesiredCapabilitiesbuild() {
 DesiredCapabilitiescaps = new DesiredCapabilities();
 
 if(this.proxy != null) {
    org.openqa.selenium.Proxyproxy = new org.openqa.selenium.Proxy();
    proxy.setHttpProxy(this.proxy)
         .setFtpProxy(this.proxy)
         .setSslProxy(this.proxy);
    caps.setCapability(CapabilityType.PROXY, proxy);
    }
 
 if(this.userAgent != null) {
 caps.setCapability(
 PhantomJSDriverService.PHANTOMJS_PAGE_SETTINGS_PREFIX + "userAgent", userAgent);
 }
 
 return caps;
 }
}

在使用的时候, 如下:

DesiredCapabilitiescaps = 
 new DesiredCapabilitiesBuilder()
 .userAgent("ua")
 .proxy("my proxy")
 .build();
// PhantomJSDriver driver =  ...

这样, 当我们增加了新的参数, 就再也不怕了!

如果我们需要build的对象, 是我们自己的类, 而不是第三方的类, 更推荐的方式是将Builder作为 嵌套类 的方式进行应用。

注意: 第一次看到这里的时候, 我也挺困惑的:

在一个class之中, 居然可以有两个public class !

只不过另外一个是public static class

实际上, 这个builder 只是作为了外部public class的一个静态成员, 属于 嵌套类

有一个写得还不错的帖子: Java方法参数太多怎么办—Part3—Builder模式 里面的使用方法更加复杂,感兴趣的可以去尝试一下。

原文代码太长(参数真的好多),对总览全貌不太方便, 我把参数删除还剩2个,可以看看下面精简之后的代码:

public class Person {
 // 注意, 这里都加了final的限制
 private String lastName;
 private String firstName;
 
 // 注意: 这里我将其改成了私有的构造函数,原文为public
 // 这样外部调用者就只能通过PersonBuilder,不会直接new Person() 的方式构造了
 private Person(final String newLastName, final String newFirstName) {
 // 更复杂的逻辑可以在这里进行处理
 // 比如最简单的,处理各种null值
 // 也可以在各自的build方法之中执行, 比如PersonBuilder.lastName() 处理lastName的特殊情况
 this.lastName = newLastName;
 this.firstName = newFirstName;
 }
 
 @Override
 public String toString() {
 return String.format("firstName: %s, LastName: %s", this.firstName, this.lastName);
 }
 
 public static class PersonBuilder {
 private String requiredID; // required
 private String nestedLastName; // optional
 private String nestedFirstName; // optional
 
 // required的类成员变量, 需要在这里就直接强制要求了
 public PersonBuilder(String requiredID) {
 this.requiredID = requiredID;
 }
 
 public PersonBuilderlastName(String newLastName) {
 this.nestedLastName = newLastName;
 return this;
 }
 
 public PersonBuilderfirstName(String newFirstName) {
 this.nestedFirstName = newFirstName;
 return this;
 }
 
 public PersoncreatePerson() {
 return new Person(nestedLastName, nestedFirstName);
 }
 }
}

本文原创,原文链接:http://www.flyml.net/2017/02/09/effective-java-builder-webdriver-dem

如果我们要实现跟上面的 DesiredCapabilitiesBuilder 类似的复杂逻辑控制, 可以在Person 的构造函数之中进行控制。

一个问题:为什么使用final来限制Person的类成员变量?

其实我看到也有不少Sample 并没有使用final来限制。

如果不加final的限制,实际上是可变的时候, 推荐另外自己在Persion之中加上getters/setters

第二个问题:有没有什么实际使用Builder的例子?

stackoverflow 上面依然有我们想知道的问题的答案:

http://stackoverflow.com/questions/2169190/example-of-builder-pattern-in-java-api

我在guava上面找到一个例子(其他例子看起来就很复杂) :

https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Interners.java

原文  http://www.flyml.net/2017/02/09/effective-java-builder-webdriver-demo/
正文到此结束
Loading...