Android常考问题(8)-设计模式:Builder模式(顺带学习了一下String的比较和final)

12 篇文章 0 订阅
10 篇文章 0 订阅

今天的主要目的是学习设计模式中的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模式。

原来这么常用啊。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值