在《[读书笔记]《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模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import org.openqa.selenium.phantomjs.PhantomJSDriverService; import org.openqa.selenium.remote.CapabilityType; import org.openqa.selenium.remote.DesiredCapabilities; public class DesiredCapabilitiesDemo { public DesiredCapabilities build(String strProxy, String strUserAgent) { DesiredCapabilities caps = new DesiredCapabilities(); if(strProxy != null) { org.openqa.selenium.Proxy proxy = 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; } } |
不过如果当参数更多的时候, 我们有两种选择:
- 原方法增加新的参数但是这种方法,会对原来的代码有影响, 会因为参数变多直接编译不通过。
- 增加新的同名方法, 使用重载机制但是每增加一个新的参数,就需要再新增一个重载方法, 在管理上面会非常的麻烦。甚至可能不同参数的组合也会被限制
这个时候, 使用Builder模式就比较灵活了。
本文原创,原文链接:http://www.flyml.net/2017/02/09/effective-java-builder-webdriver-demo/
使用Builder模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
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 DesiredCapabilitiesBuilder proxy(String proxy) { this.proxy = proxy; return this; } public DesiredCapabilitiesBuilder userAgent(String userAgent) { this.userAgent = userAgent; return this; } public DesiredCapabilities build() { DesiredCapabilities caps = new DesiredCapabilities(); if(this.proxy != null) { org.openqa.selenium.Proxy proxy = 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; } } |
在使用的时候, 如下:
1 2 3 4 5 6 |
DesiredCapabilities caps = 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个,可以看看下面精简之后的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
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 PersonBuilder lastName(String newLastName) { this.nestedLastName = newLastName; return this; } public PersonBuilder firstName(String newFirstName) { this.nestedFirstName = newFirstName; return this; } public Person createPerson() { 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/

文章评论