作者:Hitendra Solanki


【译】设计模式教程 -  OOP的力量_建造者模式

导读--本博客系列要求具有面向对象编程的中级专业知识。您应该对类、对象、构造函数、继承、值和引用类型有基本的了解。通过仔细地从头到尾阅读本系列文章,不管是中级还是高级开发,您都将有所收获。

设计模式用于表示经验丰富的面向对象软件开发人员社区采用的最佳实践。

建造者模式帮助我们更简单更易读地创建一个类,它遵守着以下两条规则:

1、分割原始类和它的构造方法 2、在最后一个返回类的实例

建造者模式最佳的例子就是SwiftUI,是的你没有看错。SwiftUI中大部分类像是Text,Image都是使用的建造者模式。

问题:

想一下,一个 ​Person​类拥有不少于十个属性,当你要使用它时,你需要为它创建一个构造方法。它的构造者将拥有不少于十个参数,去管理这么一个带有很多参数的单一函数或构造方式将是非常困难的,最终你也会让这端代码失去可读性。看下面的例子:

  1. ​class Person {​

  2. ​ //personal details​

  3. ​ var name: String = ""​

  4. ​ var gender: String = ""​

  5. ​ var birthDate: String = ""​

  6. ​ var birthPlace: String = ""​

  7. ​ var height: String = ""​

  8. ​ var weight: String = ""​

  9. ​ //contact details​

  10. ​ var phone: String = ""​

  11. ​ var email: String = ""​

  12. ​ //address details​

  13. ​ var streeAddress: String = ""​

  14. ​ var zipCode: String = ""​

  15. ​ var city: String = ""​

  16. ​ //work details​

  17. ​ var companyName: String = ""​

  18. ​ var designation: String = ""​

  19. ​ var annualIncome: String = ""​

  20. ​ //constructor​

  21. ​ init(name: String,​

  22. ​ gender: String,​

  23. ​ birthDate: String,​

  24. ​ birthPlace: String,​

  25. ​ height: String,​

  26. ​ weight: String,​

  27. ​ phone: String,​

  28. ​ email: String,​

  29. ​ streeAddress: String,​

  30. ​ zipCode: String,​

  31. ​ city: String,​

  32. ​ companyName: String,​

  33. ​ designation: String,​

  34. ​ annualIncome: String) {​

  35. ​ = name​

  36. ​ self.gender = gender​

  37. ​ self.birthDate = birthDate​

  38. ​ self.birthPlace = birthPlace​

  39. ​ self.height = height​

  40. ​ self.weight = weight​

  41. ​ self.phone = phone​

  42. ​ self.email = email​

  43. ​ self.streeAddress = streeAddress​

  44. ​ self.zipCode = zipCode​

  45. ​ self.height = height​

  46. ​ self.city = city​

  47. ​ self.companyName = companyName​

  48. ​ self.designation = designation​

  49. ​ self.annualIncome = annualIncome​

  50. ​ }​

  51. ​}​

  52. ​//This is function in Xcode-Playground which executes our test code​

  53. ​func main() {​

  54. ​ let hitendra = Person(name: "Hitendra Solanki",​

  55. ​ gender: "Male",​

  56. ​ birthDate: "2nd Oct 1991",​

  57. ​ birthPlace: "Gujarat, India",​

  58. ​ height: "5.9 ft",​

  59. ​ weight: "85kg",​

  60. ​ phone: "+91 90333-71772",​

  61. ​ email: "hitendra.developer@",​

  62. ​ streeAddress: "52nd Godrej Street",​

  63. ​ zipCode: "380015",​

  64. ​ city: "Ahmedabad",​

  65. ​ companyName: "Fortune 500",​

  66. ​ designation: "Software architect",​

  67. ​ annualIncome: "45,000 USD")​

  68. ​ //use of Person object​

  69. ​ print("\() works in \(hitendra.companyName) compay as a \(hitendra.designation).")​

  70. ​}​

  71. ​//call main to execute our test code in Xcode-Playground​

  72. ​main()​

  73. ​/* Console output:​

  74. ​Hitendra Solanki works in Fortune 500 compay as a Software architect.​

  75. ​*/​

将上面的例子在playground中运行一下,你会得到预期结果。逻辑上这也是对的。

我们可以尝试优化上面的代码,从解决这两个问题入手。1、我们必须按照既定的顺序传参数,而不能通过重新排列参数提高可读性。2、即使创建对象时我们不知道一些属性值,我们也不得不传入所有参数。

例如你需要创建一个 ​Person​类,但是这个人还在找工作。只有当他进入某一公司我们才能得到他的工作信息。

解决方案:

1、创建相关属性的逻辑分组。

2、为不同分组的属性创建不同的建造者类。

3、在建造者类中最后一步返回实例。

让我们从上面的例子开始,我们已经拥有一个 ​Person​类,它含有14个属性。我们仔细观察这14个属性,可以将它分为四组。

1、个人信息 2、联系方式 3、地址信息 4、公司信息

通过强大的设计模式我们可以解决上面两个问题,具体代码如下:

  1. ​//This is function in playground which executes our test code​

  2. ​func main() {​

  3. ​ var hitendra = Person() //person with empty details​

  4. ​ let personBuilder = PersonBuilder(person: hitendra)​

  5. ​ hitendra = personBuilder​

  6. ​ .personalInfo​

  7. ​ .nameIs("Hitendra Solanki")​

  8. ​ .genderIs("Male")​

  9. ​ .bornOn("2nd Oct 1991")​

  10. ​ .bornAt("Gujarat, India")​

  11. ​ .havingHeight("5.9 ft")​

  12. ​ .havingWeight("85 kg")​

  13. ​ .contacts​

  14. ​ .hasPhone("+91 90333-71772")​

  15. ​ .hasEmail("hitendra.developer@")​

  16. ​ .lives​

  17. ​ .at("52nd Godrej Street")​

  18. ​ .inCity("Ahmedabad")​

  19. ​ .withZipCode("380015")​

  20. ​ .build()​

  21. ​ //use of Person object​

  22. ​ print("\() has contact number \(hitendra.phone) and email \(hitendra.email)")​

  23. ​ //later on when we have company details ready for the person​

  24. ​ hitendra = personBuilder​

  25. ​ .works​

  26. ​ .asA("Software architect")​

  27. ​ .inCompany("Fortune 500")​

  28. ​ .hasAnnualEarning("45,000 USD")​

  29. ​ .build()​

  30. ​ //use of Person object with update info​

  31. ​ print("\() works in \(hitendra.companyName) compay as a \(hitendra.designation).")​

  32. ​}​

  33. ​//call main to execute our test code​

  34. ​main()​

  35. ​//Person class which only contains the details​

  36. ​class Person {​

  37. ​ //personal details​

  38. ​ var name: String = ""​

  39. ​ var gender: String = ""​

  40. ​ var birthDate: String = ""​

  41. ​ var birthPlace: String = ""​

  42. ​ var height: String = ""​

  43. ​ var weight: String = ""​

  44. ​ //contact details​

  45. ​ var phone: String = ""​

  46. ​ var email: String = ""​

  47. ​ //address details​

  48. ​ var streeAddress: String = ""​

  49. ​ var zipCode: String = ""​

  50. ​ var city: String = ""​

  51. ​ //work details​

  52. ​ var companyName: String = ""​

  53. ​ var designation: String = ""​

  54. ​ var annualIncome: String = ""​

  55. ​ //empty constructor​

  56. ​ init() { }​

  57. ​}​

  58. ​//PersonBuilder class helps to construct the person class instance​

  59. ​class PersonBuilder {​

  60. ​ var person: Person​

  61. ​ init(person: Person){​

  62. ​ self.person = person​

  63. ​ }​

  64. ​ //personal details builder switching​

  65. ​ var personalInfo: PersonPersonalDetailsBuilder {​

  66. ​ return PersonPersonalDetailsBuilder(person: self.person)​

  67. ​ }​

  68. ​ //contact details builder switching​

  69. ​ var contacts: PersonContactDetailsBuilder {​

  70. ​ return PersonContactDetailsBuilder(person: self.person)​

  71. ​ }​

  72. ​ //address details builder switching​

  73. ​ var lives: PersonAddressDetailsBuilder {​

  74. ​ return PersonAddressDetailsBuilder(person: self.person)​

  75. ​ }​

  76. ​ //work details builder switching​

  77. ​ var works: PersonCompanyDetailsBuilder {​

  78. ​ return PersonCompanyDetailsBuilder(person: self.person)​

  79. ​ }​

  80. ​ func build() -> Person {​

  81. ​ return self.person​

  82. ​ }​

  83. ​}​

  84. ​//PersonPersonalDetailsBuilder: update personal details​

  85. ​class PersonPersonalDetailsBuilder: PersonBuilder {​

  86. ​ func nameIs(_ name: String) -> Self {​

  87. ​ = name​

  88. ​ return self​

  89. ​ }​

  90. ​ func genderIs(_ gender: String) -> Self {​

  91. ​ self.person.gender = gender​

  92. ​ return self​

  93. ​ }​

  94. ​ func bornOn(_ birthDate: String) -> Self {​

  95. ​ self.person.birthDate = birthDate​

  96. ​ return self​

  97. ​ }​

  98. ​ func bornAt(_ birthPlace: String) -> Self {​

  99. ​ self.person.birthPlace = birthPlace​

  100. ​ return self​

  101. ​ }​

  102. ​ func havingHeight(_ height: String) -> Self {​

  103. ​ self.person.height = height​

  104. ​ return self​

  105. ​ }​

  106. ​ func havingWeight(_ weight: String) -> Self {​

  107. ​ self.person.weight = weight​

  108. ​ return self​

  109. ​ }​

  110. ​}​

  111. ​//PersonContactDetailsBuilder: update contact details​

  112. ​class PersonContactDetailsBuilder: PersonBuilder {​

  113. ​ func hasPhone(_ phone: String) -> Self {​

  114. ​ self.person.phone = phone​

  115. ​ return self​

  116. ​ }​

  117. ​ func hasEmail(_ email: String) -> Self {​

  118. ​ self.person.email = email​

  119. ​ return self​

  120. ​ }​

  121. ​}​

  122. ​//PersonAddressDetailsBuilder: update address details​

  123. ​class PersonAddressDetailsBuilder: PersonBuilder {​

  124. ​ func at(_ streeAddress: String) -> Self {​

  125. ​ self.person.streeAddress = streeAddress​

  126. ​ return self​

  127. ​ }​

  128. ​ func withZipCode(_ zipCode: String) -> Self {​

  129. ​ self.person.zipCode = zipCode​

  130. ​ return self​

  131. ​ }​

  132. ​ func inCity(_ city: String) -> Self {​

  133. ​ self.person.city = city​

  134. ​ return self​

  135. ​ }​

  136. ​}​

  137. ​//PersonCompanyDetailsBuilder: update company details​

  138. ​class PersonCompanyDetailsBuilder: PersonBuilder {​

  139. ​ func inCompany(_ companyName: String) -> Self {​

  140. ​ self.person.companyName = companyName​

  141. ​ return self​

  142. ​ }​

  143. ​ func asA(_ designation: String) -> Self {​

  144. ​ self.person.designation = designation​

  145. ​ return self​

  146. ​ }​

  147. ​ func hasAnnualEarning(_ annualIncome: String) -> Self {​

  148. ​ self.person.annualIncome = annualIncome​

  149. ​ return self​

  150. ​ }​

  151. ​}​

  152. ​/* Console output:​

  153. ​ Hitendra Solanki has contact number +91 90333-71772 and email hitendra.developer@

  154. ​ Hitendra Solanki works in Fortune 500 compay as a Software architect.​

  155. ​ */​

在上面的例子中,我们把 ​Person​​类根据职责分割成了几个不同的类。我们创建了多个建造者,他们分别管理相关分组内的属性,而Person只持有这些建造者。我们拥有一个建造者基类 ​PersonBuilder​​和四个衍生的建造者类, ​PersonPersonalDetailsBuilder​​, ​PersonContactDetailsBuilder​​, ​PersonAddressDetailsBuilder​​ 和 ​PersonCompanyDetailsBuilder​​。当其他四个从 ​Personbuilder​​衍生出来的建造者需要更新相关属性时, ​Personbuilder​这个基类可以帮助我们在它们之间进行换。

在上面的例子中我们可以看到新的构造方法变得更加易读了,我们可以用一种更加优雅的方式更新一组或者某一个属性。

需要注意一下,上面的例子中我们再每个建造者更新方法之后返回了它自己。这让我们能够在相同的建造者中写出链式方法,而不是分开的多行。这个概念称为流程模式。

优点

1、用一种优雅的方式很容易地初始化一个含很多参数的类。

2、遵从单一职责原则。

3、根据你的情况,以任意的顺序初始化对象和更新属性。