【译】设计模式引导--OOP的能力
- 20 Aug, 2019
原文链接 作者:Hitendra Solanki
导读—本博客系列要求具有面向对象编程的中级专业知识。您应该对类、对象、构造函数、继承、值和引用类型有基本的了解。通过仔细地从头到尾阅读本系列文章,不管是中级还是高级开发,您都将有所收获。
设计模式用于表示经验丰富的面向对象软件开发人员社区采用的最佳实践。
建造者模式帮助我们更简单更易读地创建一个类,它遵守着以下两条规则: 1、分割原始类和它的构造方法 2、在最后一个返回类的实例
建造者模式最佳的例子就是SwiftUI,是的你没有看错。SwiftUI中大部分类像是Text,Image都是使用的建造者模式。
问题:
想一下,一个Person
类拥有不少于十个属性,当你要使用它时,你需要为它创建一个构造方法。它的构造者将拥有不少于十个参数,去管理这么一个带有很多参数的单一函数或构造方式将是非常困难的,最终你也会让这端代码失去可读性。看下面的例子:
class Person {
//personal details
var name: String = ""
var gender: String = ""
var birthDate: String = ""
var birthPlace: String = ""
var height: String = ""
var weight: String = ""
//contact details
var phone: String = ""
var email: String = ""
//address details
var streeAddress: String = ""
var zipCode: String = ""
var city: String = ""
//work details
var companyName: String = ""
var designation: String = ""
var annualIncome: String = ""
//constructor
init(name: String,
gender: String,
birthDate: String,
birthPlace: String,
height: String,
weight: String,
phone: String,
email: String,
streeAddress: String,
zipCode: String,
city: String,
companyName: String,
designation: String,
annualIncome: String) {
self.name = name
self.gender = gender
self.birthDate = birthDate
self.birthPlace = birthPlace
self.height = height
self.weight = weight
self.phone = phone
self.email = email
self.streeAddress = streeAddress
self.zipCode = zipCode
self.height = height
self.city = city
self.companyName = companyName
self.designation = designation
self.annualIncome = annualIncome
}
}
//This is function in Xcode-Playground which executes our test code
func main() {
let hitendra = Person(name: "Hitendra Solanki",
gender: "Male",
birthDate: "2nd Oct 1991",
birthPlace: "Gujarat, India",
height: "5.9 ft",
weight: "85kg",
phone: "+91 90333-71772",
email: "hitendra.developer@gmail.com",
streeAddress: "52nd Godrej Street",
zipCode: "380015",
city: "Ahmedabad",
companyName: "Fortune 500",
designation: "Software architect",
annualIncome: "45,000 USD")
//use of Person object
print("\(hitendra.name) works in \(hitendra.companyName) compay as a \(hitendra.designation).")
}
//call main to execute our test code in Xcode-Playground
main()
/* Console output:
Hitendra Solanki works in Fortune 500 compay as a Software architect.
*/
将上面的例子在playground中运行一下,你会得到预期结果。逻辑上这也是对的。
我们可以尝试优化上面的代码,从解决这两个问题入手。 1、我们必须按照既定的顺序传参数,而不能通过重新排列参数提高可读性。 2、即使创建对象时我们不知道一些属性值,我们也不得不传入所有参数。
例如你需要创建一个Person
类,但是这个人还在找工作。只有当他进入某一公司我们才能得到他的工作信息。
解决方案:
1、创建相关属性的逻辑分组。 2、为不同分组的属性创建不同的建造者类。 3、在建造者类中最后一步返回实例。
让我们从上面的例子开始,我们已经拥有一个Person
类,它含有14个属性。我们仔细观察这14个属性,可以将它分为四组。
1、个人信息
2、联系方式
3、地址信息
4、公司信息
通过强大的设计模式我们可以解决上面两个问题,具体代码如下:
//This is function in playground which executes our test code
func main() {
var hitendra = Person() //person with empty details
let personBuilder = PersonBuilder(person: hitendra)
hitendra = personBuilder
.personalInfo
.nameIs("Hitendra Solanki")
.genderIs("Male")
.bornOn("2nd Oct 1991")
.bornAt("Gujarat, India")
.havingHeight("5.9 ft")
.havingWeight("85 kg")
.contacts
.hasPhone("+91 90333-71772")
.hasEmail("hitendra.developer@gmail.com")
.lives
.at("52nd Godrej Street")
.inCity("Ahmedabad")
.withZipCode("380015")
.build()
//use of Person object
print("\(hitendra.name) has contact number \(hitendra.phone) and email \(hitendra.email)")
//later on when we have company details ready for the person
hitendra = personBuilder
.works
.asA("Software architect")
.inCompany("Fortune 500")
.hasAnnualEarning("45,000 USD")
.build()
//use of Person object with update info
print("\(hitendra.name) works in \(hitendra.companyName) compay as a \(hitendra.designation).")
}
//call main to execute our test code
main()
//Person class which only contains the details
class Person {
//personal details
var name: String = ""
var gender: String = ""
var birthDate: String = ""
var birthPlace: String = ""
var height: String = ""
var weight: String = ""
//contact details
var phone: String = ""
var email: String = ""
//address details
var streeAddress: String = ""
var zipCode: String = ""
var city: String = ""
//work details
var companyName: String = ""
var designation: String = ""
var annualIncome: String = ""
//empty constructor
init() { }
}
//PersonBuilder class helps to construct the person class instance
class PersonBuilder {
var person: Person
init(person: Person){
self.person = person
}
//personal details builder switching
var personalInfo: PersonPersonalDetailsBuilder {
return PersonPersonalDetailsBuilder(person: self.person)
}
//contact details builder switching
var contacts: PersonContactDetailsBuilder {
return PersonContactDetailsBuilder(person: self.person)
}
//address details builder switching
var lives: PersonAddressDetailsBuilder {
return PersonAddressDetailsBuilder(person: self.person)
}
//work details builder switching
var works: PersonCompanyDetailsBuilder {
return PersonCompanyDetailsBuilder(person: self.person)
}
func build() -> Person {
return self.person
}
}
//PersonPersonalDetailsBuilder: update personal details
class PersonPersonalDetailsBuilder: PersonBuilder {
func nameIs(_ name: String) -> Self {
self.person.name = name
return self
}
func genderIs(_ gender: String) -> Self {
self.person.gender = gender
return self
}
func bornOn(_ birthDate: String) -> Self {
self.person.birthDate = birthDate
return self
}
func bornAt(_ birthPlace: String) -> Self {
self.person.birthPlace = birthPlace
return self
}
func havingHeight(_ height: String) -> Self {
self.person.height = height
return self
}
func havingWeight(_ weight: String) -> Self {
self.person.weight = weight
return self
}
}
//PersonContactDetailsBuilder: update contact details
class PersonContactDetailsBuilder: PersonBuilder {
func hasPhone(_ phone: String) -> Self {
self.person.phone = phone
return self
}
func hasEmail(_ email: String) -> Self {
self.person.email = email
return self
}
}
//PersonAddressDetailsBuilder: update address details
class PersonAddressDetailsBuilder: PersonBuilder {
func at(_ streeAddress: String) -> Self {
self.person.streeAddress = streeAddress
return self
}
func withZipCode(_ zipCode: String) -> Self {
self.person.zipCode = zipCode
return self
}
func inCity(_ city: String) -> Self {
self.person.city = city
return self
}
}
//PersonCompanyDetailsBuilder: update company details
class PersonCompanyDetailsBuilder: PersonBuilder {
func inCompany(_ companyName: String) -> Self {
self.person.companyName = companyName
return self
}
func asA(_ designation: String) -> Self {
self.person.designation = designation
return self
}
func hasAnnualEarning(_ annualIncome: String) -> Self {
self.person.annualIncome = annualIncome
return self
}
}
/* Console output:
Hitendra Solanki has contact number +91 90333-71772 and email hitendra.developer@gmail.com
Hitendra Solanki works in Fortune 500 compay as a Software architect.
*/
在上面的例子中,我们把Person
类根据职责分割成了几个不同的类。我们创建了多个建造者,他们分别管理相关分组内的属性,而Person只持有这些建造者。
我们拥有一个建造者基类PersonBuilder
和四个衍生的建造者类,PersonPersonalDetailsBuilder
, PersonContactDetailsBuilder
, PersonAddressDetailsBuilder
和 PersonCompanyDetailsBuilder
。
当其他四个从Personbuilder
衍生出来的建造者需要更新相关属性时,Personbuilder
这个基类可以帮助我们在它们之间进行转换。
在上面的例子中我们可以看到新的构造方法变得更加易读了,我们可以用一种更加优雅的方式更新一组或者某一个属性。
需要注意一下,上面的例子中我们再每个建造者更新方法之后返回了它自己。这让我们能够在相同的建造者中写出链式方法,而不是分开的多行。这个概念称为流程模式。
优点
1、用一种优雅的方式很容易地初始化一个含很多参数的类。 2、遵从单一职责原则。 3、根据你的情况,以任意的顺序初始化对象和更新属性。