字符型常量和字符串常量
2017年3月13日更新 :添加构建器模式部分
大多数开发人员对于应该使用强类型还是弱类型的语言都持有强烈的意见,无论他们对这些术语有什么看法。 有些人还积极地练习字符串类型的编程-甚至根本没有意识到它。 当代码库的大多数属性和参数为String
时,就会发生这种情况。 在本文中,我将以以下简单代码段为例:
publicclassPerson{
privatefinalStringtitle;
privatefinalStringgivenName;
privatefinalStringfamilyName;
privatefinalStringemail;
publicPerson(Stringtitle,StringgivenName,StringfamilyName,Stringemail){
this.title=title;
this.givenName=givenName;
this.familyName=familyName;
this.email=email;
}
...
}
原罪
该代码的问题在于,很难记住哪个参数代表什么以及以什么顺序将它们传递给构造函数。
Personperson=newPerson(" [email protected] ","John","Doe","Sir");
在上一个呼叫中,电子邮件和标题参数值已切换。 哎呀
如果有多个提供可选参数的构造函数可用,则情况更糟:
publicPerson(StringgivenName,StringfamilyName,Stringemail){
this(null,givenName,familyName,email);
}
Personanother=newPerson("Sir","John","Doe");
在这种情况下, title
是可选参数,而不是email
。 我的错。
OOP方式解决问题
出于充分的理由,面向对象编程及其倡导者强烈反对字符串型代码。 由于世界上的所有事物都有特定的类型,因此它必须在系统中。
让我们重写之前的代码àla OOP:
publicclassTitle{
privatefinalStringvalue;
publicTitle(Stringvalue){
this.value=value;
}
}
publicclassGivenName{
privatefinalStringvalue;
publicFirstName(Stringvalue){
this.value=value;
}
}
publicclassFamilyName{
privatefinalStringvalue;
publicLastName(Stringvalue){
this.value=value;
}
}
publicclassEmail{
privatefinalStringvalue;
publicEmail(Stringvalue){
this.value=value;
}
}
publicclassPerson{
privatefinalTitletitle;
privatefinalGivenNamegivenName;
privatefinalFamilyNamefamilyName;
privatefinalEmailemail;
publicPerson(Titletitle,GivenNamegivenName,FamilyNamefamilyName,Emailemail){
this.title=title;
this.givenName=givenName;
this.familyName=familyName;
this.email=email;
}
...
}
Personperson=newPerson(newTitle(null),newFirstName("John"),newLastName("Doe"),newEmail(" [email protected] "));
这样就大大限制了出错的可能性。 缺点是冗长度大大增加-可能导致其他错误。
营救模式
解决Java中此问题的常用方法是使用Builder模式。 让我们介绍一个新的构建器类并重新编写代码:
publicclassPerson{
privateStringtitle;
privateStringgivenName;
privateStringfamilyName;
privateStringemail;
privatePerson(){}
privatevoidsetTitle(Stringtitle){
this.title=title;
}
privatevoidsetGivenName(StringgivenName){
this.givenName=givenName;
}
privatevoidsetFamilyName(StringfamilyName){
this.familyName=familyName;
}
privatevoidsetEmail(Stringemail){
this.email=email;
}
publicstaticclassBuilder{
privatePersonperson;
publicBuilder(){
person=newPerson();
}
publicBuildertitle(Stringtitle){
person.setTitle(title);
returnthis;
}
publicBuildergivenName(StringgivenName){
person.setGivenName(givenName);
returnthis;
}
publicBuilderfamilyName(StringfamilyName){
person.setFamilyName(familyName);
returnthis;
}
publicBuilderemail(Stringemail){
person.setEmail(email);
returnthis;
}
publicPersonbuild(){
returnperson;
}
}
}
请注意,除了新的构建器类之外, Person
类的构造器已设置为private
。 使用Java语言功能,这仅允许Builder创建新的Person
实例。 相同的用于不同的二传手。
使用此模式非常简单:
Personperson=newBuilder()
.title("Sir")
.givenName("John")
.familyName("Doe")
.email(" [email protected] ")
.build();
构建器模式将详细程度从调用部分转移到设计部分。 不错的权衡。
救援语言
不幸的是,详尽是Java的标志。 某些其他语言(如Kotlin,Scala等)将对这种方法更加友好,不仅适用于类声明,而且适用于对象创建。
让我们将类声明移植到Kotlin:
classTitle(valvalue:String?)
classGivenName(valvalue:String)
classFamilyName(valvalue:String)
classEmail(valvalue:String)
classPerson(valtitle:Title,valgivenName:GivenName,valfamilyName:FamilyName,valemail:Email)
这要好得多,多亏Kotlin! 现在创建对象:
valperson=Person(Title(null),GivenName("John"),FamilyName("Doe"),Email(" [email protected] "))
为此,与Java相比,冗长程度仅略有降低。
抢救的命名参数
OOP狂热者可能会在那里停止阅读,因为他们的方式并不是唯一应付弦乐类型的狂热者。
一种替代方法是有关命名参数,并且在Kotlin中也可以找到。 让我们回到原始的字符串型代码,将其移植到Kotlin并使用命名参数:
classPerson(valtitle:String?,valgivenName:String,valfamilyName:String,valemail:String)
valperson=Person(title=null,givenName="John",familyName="Doe",email=" [email protected] ")
valanother=Person(email=" [email protected] ",title="Sir",givenName="John",familyName="Doe")
除了处理字符串类型的代码外,命名参数的好处还在于,调用构造函数时,它们与顺序无关。 此外,它们还可以很好地使用默认值:
classPerson(valtitle:String?=null,valgivenName:String,valfamilyName:String,valemail:String?=null)
valperson=Person(givenName="John",familyName="Doe")
valanother=Person(title="Sir",givenName="John",familyName="Doe")
为救援输入别名
在介绍Kotlin时,让我们描述一下可能会有所帮助的1.1版本发布的功能。
类型别名就是它的名字,它暗示了一个现有类型的名字。 类型可以是简单类型,集合,lambda-类型系统中存在的任何类型。
让我们在字符串类型的世界中创建一些类型别名:
typealiasTitle=String
typeliasGivenName=String
typealiasFamilyName=String
typealiasEmail=String
classPerson(valtitle:Title,valgivenName:GivenName,valfamilyName:FamilyName,valemail:Email)
valperson=Person(null,"John","Doe"," [email protected] ")
该声明似乎更加类型化。 不幸的是,对象创建并没有带来任何改善。
请注意,类型别名的主要问题在于它们只是-别名:不会创建新的类型,因此如果2个别名指向同一类型,则所有3个别名都可以互换。
救援图书馆
在本文的其余部分,让我们回到Java语言。
稍微弄乱逻辑,可以在运行时而不是在特定库的帮助下在编译时验证参数。 特别是,Bean验证库可以完成以下工作:
publicPerson(@TitleStringtitle,@GivenNameStringgivenName,@FamilyNameStringfamilyName,@EmailStringemail){
this.title=title;
this.givenName=givenName;
this.familyName=familyName;
this.email=email;
}
诚然,这不是最好的解决方案...但是它有效。
救援工具
我已经写过关于工具的文章 ,它与语言本身同样重要(如果没有更多的话)。
工具是非侵入性的,填补了语言的空白。 缺点是每个人都必须使用它(或找到具有相同功能的工具)。
例如,当我开始我的职业生涯时,编码准则要求开发人员按照类文件中的字母顺序对方法进行排序。 如今,这已经变得毫无意义,因为每个值得盐分的IDE都可以按顺序显示类的方法。
同样,对于缺少参数的语言,命名参数可以是IDE的功能。 特别是,最新版本的IntelliJ IDEA会为被认为是泛型的类型的Java语言模拟命名参数。 下面显示了IDE中的Person
类:
结论
尽管正确的OOP设计是应付字符串类型代码的历史方法,但它在Java中也相当冗长和笨拙。 这篇文章介绍了替代方案,以及它们的优点和缺点。 每个人都需要根据自己的具体情况进行评估,以确定最适合的人。
字符型常量和字符串常量