今天的主要目的是学习设计模式中的Builder模式。由于java基础不牢固,在学习过程中要回过头去学习java内容,因此凑成了这样一篇驳杂的文章。
Builder模式
首先是Builder设计模式的作用:将复杂对象的构建和表示分离,使得不同构建过程创建不同的表示对象。(概念啥的我就看看)
实现过程比较简单,只要把对象类,builder抽象接口,ConcreteBuilder(接口的实现),和Director使用者四个类准备好就可以了。
首先是对象类:我们来新建一个person类:
public class Person {
private String name;
private int age;
private String eat;
private String walk;
private String say;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEat() {
return eat;
}
public void setEat(String eat) {
this.eat = eat;
}
public String getWalk() {
return walk;
}
public void setWalk(String walk) {
this.walk = walk;
}
public String getSay() {
return say;
}
public void setSay(String say) {
this.say = say;
}
@Override
public String toString() {
return "Person{" +
" name='" + name + '\'' +
", age='" + age + '\'' +
", eat='" + eat + '\'' +
", walk='" + walk + '\'' +
", say='" + say + '\'' +
'}';
}
}
这个类比较简单,就是普通的类方法,注意加上了toString()方法方便之后打印日志(对象类常备toString是程序猿的规范)。
然后创建Builder接口。
public interface PersonBuilder {
void setName();
void setAge();
void setEat();
void setWalk();
void setSay();
Person getPerson();
}
但是在这个接口里面不会进行具体操作,接下来要实现接口,创建两个ConcreteBuilder类。
public class ABuilder implements PersonBuilder {
private Person person;
public ABuilder(){
this.person = new Person();
}
@Override
public void setName() {
person.setName("张三");
}
@Override
public void setAge() {
person.setAge(18);
}
@Override
public void setEat() {
person.setEat("大口吃饭");
}
@Override
public void setWalk() {
person.setWalk("慢速行走");
}
@Override
public void setSay() {
person.setSay("口若悬河");
}
@Override
public Person getPerson() {
return person;
}
}
public class BBuilder implements PersonBuilder {
private Person person;
public BBuilder(){
this.person = new Person();
}
@Override
public void setName() {
person.setName("李四");
}
@Override
public void setAge() {
person.setAge(40);
}
@Override
public void setEat() {
person.setEat("小口吃饭");
}
@Override
public void setWalk() {
person.setWalk("快步流星");
}
@Override
public void setSay() {
person.setSay("语速特慢");
}
@Override
public Person getPerson() {
return person;
}
}
这里是两个类,分别代表新建的张三和李四两个人,之后进行对象的表示:
public class Director {
private PersonBuilder mBuilder;
public Director(PersonBuilder builder){
this.mBuilder = builder;
}
public void createPerson(){
mBuilder.setName();
mBuilder.setAge();
mBuilder.setEat();
mBuilder.setWalk();
mBuilder.setSay();
}
public Person getPerson(){
return mBuilder.getPerson();
}
}
这样就算完成了Builder模式。之后需要创建对象的时候
Director director = new Director(new ABuilder());//创建对象
director.createPerson(); //开始创建
Person person = director.getPerson(); //成果
最后的person就是所需要的结果。
当然Builder其实没有它的变种常用,变种的Builder可能作用更广。
需求:具有多个属性值,其中name,age是必须的,而say,walk,eat都是非必须的,且所有变量都是final的!
Final关键字
final会是一个特殊要求吗?查看了编程说明:尽量将属性定义为final。why?因此来看一下final的相关内容。final关键字用于修饰方法,类,和变量有不同的作用。一个个看:
final修饰类的时候表明这个类不能被继承。注意final类中的所有成员方法都会被隐式地指定为final方法,final类中的成员变量可以根据需要设为final。也就是说,final类里面的方法都一定是final方法,final类里面的变量不加上final就不是final变量。final类最大的目的就是让一个类不能被继承。但是要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。
final修饰方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了修饰方法的时候就如同把方法变成了private方法,不能被继承。private方法也隐式地被指定为final方法。
final修饰变量:如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。如何理解呢?
当用final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。
接下来这一段代码
public class Test {
public static void main(String[] args) {
String a = "hello2";
final String b = "hello";
String d = "hello";
String c = b + 2;
String e = d + 2;
System.out.println((a == c));//true
System.out.println((a == e));//false
}
}
来解释一下。当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。也就是说 b+2 就变成了 “hello”+2 这样a就和c相等的。等下,为啥==就会有不相等了?这一块知识点又不明确了,==和equals()方法的区别。
String中==和equals
比较直白的先放几个例子
1. String str = "abc";
String str3 = "abc";
System.out.println(str == str3);//true
System.out.println(str.equals(str3));//true
2. String str1 = new String("abcdeeee");
String str2 = new String("abcdeeee");
System.out.println(str1 == str2);//false
System.out.println(str1.equals(str2));//true
3. String s = new String("aaa");
String s1 = "aaa";
System.out.println(s == s1);//false
System.out.println(s.equals(s1));//true
4. String s = "a"+"b"+"c";
String s1 = "abc";
System.out.println(s == s1);//true
System.out.println(s.equals(s1));//true
5. String s = "ab";
String s1 = "abc";
String s2 = s + "c";
System.out.println(s2 == s1);//false
System.out.println(s2.equals(s1));//true
再配上解释:. 首先,String对象并不是通过new来创建的,所以虚拟机并不会为String对象分配内存堆,而是到String缓冲池中寻找。2. 其次,为str寻找String缓冲池中是否存在相同值的String对象存在,如果有,直接将该对象的引用赋值给str,若没有,则虚拟机会在缓冲池内创建此对象,其动作就是new String(“abc”);,然后把此String对象的引用赋值给str。
理解引用这个概念的话就好理解了。第一个?就是str3在缓冲池里面找到了“abc”,因此就用了它的引用,因此两次都是true。第二个?两个都是new的string,因此两次的引用不同,因此 == 的结果就是false。第四个?就是说字符串相加是不影响缓冲池的引用的。至于第五个?,就比较特别,因为s是变量,那么s无论是和常量还是和其他变量相+,在源码里面得到的新串,都是new出来的一个新的String,这个String是放在堆里面的。既然是new 出来的,那自然不是同一个对象。所以==的结果是false。
唯一让我纳闷的是第三个?。我觉得第一个new出来的字符串,那么第二次找的时候,找到的来了这个字符串,那不就应该得到它的引用了吗?因为new出来的都放到了堆里面,而找的时候只会去缓冲池里面找同样的字符串对象。
回到Final
之前说的final的变量,
public class Test {
public static void main(String[] args) {
String a = "hello2";
final String b = "hello";
String d = "hello";
String c = b + 2;
String e = d + 2;
System.out.println((a == c));//true
System.out.println((a == e));//false
}
}
这里final类型的b会直接替换成"hello",因此引用相等了。
public class Test {
public static void main(String[] args) {
String a = "hello2";
final String b = getHello();
String c = b + 2;
System.out.println((a == c));
}
public static String getHello() {
return "hello";
}
}
像这个返回的就是false,因为并非是直接进行引用,还需要进行一步转换才能得到确切的b的值。
public class Test {
public static void main(String[] args) {
final MyClass myClass = new MyClass();
System.out.println(++myClass.i);
}
}
class MyClass {
public int i = 0;
}
这段代码输出的值为1,这说明final修饰的对象的值可变,但是对象本身不可变。
回到变种Builder模式
变种的Builder模式因为有了必须的和非必须的属性,同时还全部都为final,因此将必须属性留存在构造方法中,非必须属性通过set方法导入。如果所有属性都通过set和get方法实现那么就不能满足全都为不可变的final类型的数据。在此就可以使用变种Builder模式。还是新建一个对象类Person
package com.example.fengers;
public class Person {
private final String name;//必须
private final int age;//必须
private final String eat;//非必须
private final String walk;//非必须
private final String say;//非必须
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getEat() {
return eat;
}
public String getWalk() {
return walk;
}
public String getSay() {
return say;
}
@Override
public String toString() {
return "Person{" +
" name='" + name + '\'' +
", age='" + age + '\'' +
", eat='" + eat + '\'' +
", walk='" + walk + '\'' +
", say='" + say + '\'' +
'}';
}
//这个类只有一个构造方法,传入的参数类型为builder
private Person(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.walk = builder.walk;
this.say = builder.say;
this.eat = builder.eat;
}
public static class Builder {
private final String name;
private final int age;
private String walk;
private String say;
private String eat;
public Builder(String name,int age) {
this.name = name;
this.age = age;
}
public Builder walk(String walk) {
this.walk = walk;
return this;
}
public Builder eat(String eat) {
this.eat = eat;
return this;
}
public Builder say(String say) {
this.say = say;
return this;
}
public Person build(){
return new Person(this);
}
}
}
好了,代码结束。
因为内容简单直接。代码中只有一个构造方法,传入的是自定义的Builder类。在Builder类的构造方法中传入两个必须属性,非必须属性通过相应方法传入。传入之后生成的对象中所有属性仍旧都是final的。这样子的话People类就是私有的,不能被直接调用,而且其中对于所有属性值只会提供get方法而没有set方法(因为不可变啊),Builder类只会接收必须属性作为参数。使用方法就很简单:
Person person = new Person.Builder("张三",18)
.say("他有点呆")
.walk("快步流星")
.eat("樱桃小嘴")
.build();
当我看到这里的时候我愣了一下。折合我之前用过的很多对象好像啊,比如retrofit,比如 dialog等等,很显然其底层对象就是变种Builder模式。
原来这么常用啊。。