设计模式之创建模式(二)

版权声明:欢迎转载评论~哈哈哈哈请标明出处呀 https://blog.csdn.net/legendaryhaha/article/details/88122178

设计模式进阶篇

上一篇


抽象工厂

在上一篇中,我们讲到了工厂方法,其中,父类(或者接口)定义创建对象的公共的接口,而具体创建什么样的对象是在它的子类(若是接口,则应该是实施者)中实现的。这大大方便了我们进行业务拓展,生产哪种产品,就加入哪种产品的工厂。但是,当我们加入的业务过多时,工厂的数量也变的多起来了,这时,我们可以引入抽象工厂解决这一问题。

即在工厂方法模式中,客户端每次都只能创建一个同类型的对象,当有多个抽角色时,客户端只需要维护包含多个抽象角色的接口就好了(即抽象工厂),因为根据里氏代换原则,父类出现的地方,子类应该可以。换句话来说,客户不需要关注太多具体的产品。

在这里插入图片描述

抽象工厂(AbstractFactory):客户端直接引用,由未实现的工厂方法组成,子类必须实现其工厂方法创建产品家族。

具体工厂(ConcreteFactory):实现抽象工厂接口,负责实现工厂方法,一个具体工厂可以创建一组产品。

抽象产品(AbstractProduct):产品家族的父类,由此可以衍生很多子产品。

具体产品(Product):衍生自抽象产品,由工厂方法直接创建。

这个图想比大家都很熟悉了,建议初学时,自己画一个工厂就好,这样就不会太乱。图中是有两个具体工厂的即ConcreteFact ory1和ConcreteFactory2。然后,具体工厂又可以产生具体产品A和B的实例,而具体产品A和B又分别是抽象产品A和B的实现。这样,如果客户端在调用时,只需以来抽象工厂就可以直接创建A和B了。

//代码(后续补上)


构造者

构造者是来解决初始化的对象较为复杂时的情形的。如下:

public class BuilderAddress {
    String address;
    String name;
    String tel;
    
    public BuilderAddress(String address, String name){
        //...
    }
    public BuilderAddress(String tel, String name){
       //...
    }
}

如上图所示,我们需要这两的两个构造函数进行对象初始化工作,但此时,编译器即将报错,因为参数类型、个数是完全一样的,不能视为重载。而在构造者模式中,将对象的初始化工作移至到一个所谓的“Bulider”类中

package builder;

public class BuilderAddress {
    String address;
    String name;
    String tel;

    //申明为私有的,这样外部不能直接new创建
    private BuilderAddress(){

    }
    //设置内部类,将BuilderAddress的初始化工作移至到Bulider内部类中
    public static class Bulider{
        private String address;
        private String name;
        private String tel;

        public Bulider(){

        }
        //为各个属性字段设置值,方便将它们赋值到外部类的各个相同属性中
        public Bulider setAddress(String address) {
            this.address = address;
            return this;
        }

        public Bulider setName(String name) {
            this.name = name;
            return  this;
        }

        public Bulider setTel(String tel) {
            this.tel = tel;
            return  this;
        }
        //这个方法就是核心了,初始化BuilderAddress对象后并返回给调用者
        public BuilderAddress bulider(){
            BuilderAddress addr = new BuilderAddress();
            addr.address = address;
            addr.name = name;
            addr.tel = tel;
            return  addr;
        }
    }

调用方式

public static void main(String[] args) {
  //根据实际需要,设置相应的值就可以完成初始化工作了
    BuilderAddress test = new BuilderAddress.Bulider().setAddress("XXXX").bulider();
}

原型

“工厂方法”和“抽象工厂”模式都允许客户端不必承担创建具体类的职责。“原型”模式提供了另一种灵活的方式以获得相同的结果。原型”模式的使用场景如下:(1)当客户端需要创建对象集时,其中对象是相似的,或仅在某些状态有所不同;并且创建此类对象代价较大,所涉及的处理也过多。(2)作为建立大量工厂的替代方法,其中工厂反映要实例化的类*

在上述情况下,“原型”模式建议:先创建一个对象,然后将其指定为原型对象。通过复制原型对象并进行状态修改,以创建其他对象。

默认情况下,所有Java对象都会继承顶层java.lang.Object类的内置clone()方法。内置clone()方法会将原对象的复制项创建为浅复制项。此时,复制完后的对象和原来对象在内存中的位置是一样的,只是引用不一样,这个地方也是很多人经常容易忘记的。此时,如果你修改了复制后的对象里的内容,那么原来对象也将跟着改变。

如下:

package top.fang.pratice;

/**
 * 构造一个教师类,只有一个职称的属性
 */
public class TestTeacherClone {
    public String  profession;

    public TestTeacherClone(){

    }
    public TestTeacherClone(String profession){
        this.profession = profession;
    }
}

package top.fang.pratice;

/**
 * 构造一个学生类,实现了Cloneable接口,有名字name和年龄age属性,并内置一个教师类对象teacher
 */
public class TestStudentClone implements  Cloneable{
    private String name;
    private String age;
    TestTeacherClone teacher;

    public TestStudentClone(String name, String age, TestTeacherClone teacher){
        this.name = name;
        this.age = age;
        this.teacher = teacher;
    }

/**
 * 重写了Object中的clone方法,进行对象的复制
 * @return
 */
public Object clone(){
        TestStudentClone stu = null;
        try {
            stu = (TestStudentClone) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        return  stu;
    }

public static void main(String[] args) {
    // 新建一个教师类对象
    TestTeacherClone teacher1 = new TestTeacherClone("教授");
    
    // 构造第一个学生对象,学生一stu1的老师职称是教授
    TestStudentClone stu1 = new TestStudentClone("fang","50", teacher1);
    
    //  将stu1对象拷贝给stu2,此时,进行的是浅复制,为啥?往下看
    TestStudentClone stu2 = (TestStudentClone) stu1.clone();
    
    // 修改了学生2,stu2的姓名,年龄,和他老师的职称
    stu2.name = "liu";
    stu2.age = "78";
    stu2.teacher.profession = "讲师";
    
    //此时打印出学生一stu1的姓名,年龄,职称
    System.out.printf(" stu1的姓名:%s%n stu1的年龄:%s%n 老师职称:%s%n", 
             stu1.name, stu1.age, stu1.teacher.profession);
}
}
//打印结果
stu1的姓名:fang
stu1的年龄:50
老师职称:讲师

很明显,直接调用object中的clone,虽然实现了复制,但是如果像TestStudent类那样,包含教师类的引用,这个引用经过复制后,它的内存地址是一样的,所以造成了上面的情况,修改了学生二stu2的姓名,年龄,和老师的职称后,对于学生一stu1而言,姓名,年龄不影响,但老师职称变了。正确方法如下:
在TestTeacherClone类中,添加如下代码,记得实现Cloneable接口,不然会报错CloneNotSupported:

在这里插入图片描述
修改TestStudentClone类代码:
在这里插入图片描述
重新打印结果
在这里插入图片描述
**浅复制对应的UML图如下:原顶层对象与所有Primitive成员(可理解为上述代码中的基本变量)都被复制。顶层对象包含的任何Reference(即引用)对象都不会被复制,仅复制这些对象的引用。这将使原对象和复制对象引用底层对象的同一个复制项。
**
在这里插入图片描述

深复制对应的UML图:我们可以看到,在底层中,内存为新复制的对象重新分配了一块地址。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/legendaryhaha/article/details/88122178