亲宝软件园·资讯

展开

java大话之创建型设计模式教程示例

流浪汉kylin 人气:0

前言

本文针对一些基础的知识进行一下总结。

创建型模式相对其它两种模式也比较简单,用的地方也会更多,理解起来也会相对更容易,所以可以放在一起一次性讲完。

1. 原型模式

没想到吧,我不从单例开始讲,当然要从最简单的开始讲,原型模式是干嘛的呢?通俗的讲就是拷贝

我已经有一个对象了,我要改这个对象的参数,但是我又不能直接改,因为其它地方也引用到了这个对象,我直接改的话其它地方的引用就有问题,那我只能创建一个一模一样的对象,然后在修改。那怎么做呢,总不能创建一个对象然后根据前一个对象一个一个属性赋值吧?这时候就需要用原型模式了

原型模式在java中的实现是继承Cloneable接口然后实现clone()方法

public class Test implements Cloneable{
    public String name;
    public int age;
    public Object testObj;
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

调用时

Test testA = new Test();
try {
    Test testB = (Test) testA.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}

看得出很简单,也很容易理解,也很常用。
除此之外,它还有一个比较重要的知识点就是深拷贝和浅拷贝

有什么区别呢?简单来说就是内部的对象参数也是拷贝的对象,还是指向同一个地址的同一个对象。
上面的Demo就是浅拷贝,要实现深拷贝的话就要自己实现clone方法的具体操作,比如(伪代码,大概能看懂就行)

public class Test implements Cloneable {
    public String name;
    public int age;
    public Object testObj;
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Test testB = (Test) super.clone();
        Object newObj = new Object();
        newObj.xxx = testObj.xxx;
        testB.testObj = newObj;
        return testB;
    }
}

2. 建造者模式

又称为builder模式,主要使用场景是一个对象有多个参数的时候,而且这些参数可传可不传,不传的话就用默认值。

这种情况下,如果你用构造参数来做,你不知道要写多少个构造参数,你用set方法来做,你就要写很多行set操作,而且不美观。这时候就需要用到builder模式,它是基于一个链式调用的方法,写起来很美观。

public class Test {
    public Test(Builder builder) {
    }
    public static class Builder {
        private Context context;
        private String name = "";
        private int age = 18;
        private int sex = 0;
        private String phone = "";
        public Builder(Context context) {
            this.context = context;
        }
        public Builder setName(String name) {
            this.name = name;
            return this;
        }
        public Builder setAge(int age) {
            this.age = age;
            return this;
        }
        public Builder setSex(int sex) {
            this.sex = sex;
            return this;
        }
        public Builder setPhone(String phone) {
            this.phone = phone;
            return this;
        }
        public Test build() {
            return new Test(this);
        }
    }
}

大概这样,然后调用的时候

Test testA = new Test.Builder(this)
        .setName("name")
        .setAge(16)
        .setSex(1)
        .setPhone("phone")
        .build();

这样的链式调用看起来就比较美观,如果你只想传部分参数

Test testA = new Test.Builder(this)
        .setName("name")
        .build();

看得出很简单,也很容易理解,也很常用。
实用?嗯...... 准确来说,应该是对java来说比较实用,但如果你是用kotlin的话,用data更爽。

我个人还有个习惯是如果参数少于5个,我都是用构造方法来做,如果超过4个,我才考虑用Builder

3. 工厂模式

工厂模式简单来说就算抽象一个工厂,你创建对象的操作就交给这个工厂来处理。使用的场景也比较广泛,我个人使用比较多的场景是和业务逻辑挂钩的,受业务的影响导致有些对象我没办法复用,不同的业务分支要创建不同的业务对象,我就会用工厂来管理创建的逻辑,这样以后要改或者怎样的时候去改工厂这个对象就行,还是很方便的。

工厂模式又分为几种,简单工厂、工厂方法和抽象工厂,我这里只讲简单工厂。为什么呢?因为你要知道当简单工厂的创建逻辑超复杂导致很臃肿的时候(或在用我的话来说创建的逻辑有多个维度的时候),会用到工厂方法和抽象工厂,可以简单理解成他们是简单工厂的升级版本。

我这里先拿个别人的Demo来举个例子

public interface Shape {
    void draw(); 
}
public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Inside Rectangle::draw() method.");
    }
}
public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Inside Circle::draw() method.");
    }
}

定义了这些对象,然后写个工厂来管理创建的流程

public class ShapeFactory {
    public Shape getShape(String shapeType){
        if(shapeType == null){
            return null;
        }
        if(shapeType.equalsIgnoreCase("CIRCLE")){
            return new Circle();
        } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
            return new Rectangle();
        }
        return null;
    }
}

重点在于ShapeFactory这个类,有这么一个工厂类的概念,工厂是一个抽象,工厂里面提供相对应的方法来写创建的逻辑。

工厂基本不会复用,因为它里面的创建逻辑都是和业务逻辑相关的,所以在复杂的情况中,不光会只写if-else或者switch。那这里我可以抛出一个问题,如果这个工厂创建对象时,有些对象是同步创建,有些对象是异步创建,你会怎么处理?

4. 单例模式

什么时候会使用到单例,简单来说就是全局共用一个对象的时候,那么明显可以看出它的生命周期很长,所以很容易知道它有个缺点,很容易造成内存泄露 ,比如你在这个单例里面存了很多全局变量又不释放,那就会内存泄露。

单例又分为懒汉模式和俄汉模式

// 懒汉
public class Test {
    private Test(){}
    private static Test instance = null;
    public static Test getInstance(){
        if (instance == null){
            instance = new Test();
        }
        return instance;
    }
}
// 饿汉
public class Test {
    private Test(){}
    private static final Test instance = new Test();
    public static Test getInstance(){
        return instance;
    }
}

区别是饿汉在类加载的时候对象就会创建出来,懒汉是在第一次调用的时候对象被创建出来。饿汉是线程安全的,懒汉是线程不安全的,所以我们一般在多线程的环境中使用懒汉的话要通过一些方式保证线程安全。

这里有两种方法来保证线程安全,双重检锁和静态内部类。

// 双重检锁
public class Test {
    private static volatile Test mTest = null;
    private Test() {
    }
    public static Test getInstance() {
        if (mTest == null) {
            synchronized (Test.class) {
                if (mTest == null) {
                    mTest = new Test();
                }
            }
        }
        return mTest;
    }
}

相信都能一眼看懂什么意思,唯一注意的是要加volatile来保证有序性,因为new Test()不是一个原子性操作。

// 静态内部类
public class Test {
    private static class LazyHolder{
        private static final Test INSTANCE = new Test();
    }
    private Test(){}
    public static final Test getInstance(){
        return LazyHolder.INSTANCE;
    }
}

这是通过使用类加载机制来保证对象只创建一次从而保证线程安全。 这两种方法都可以保证线程安全,两种方法都可以使用,但我更倾向于双检锁,因为能省去一步类加载,虽然这个影响不大。

5. 总结

创建型模式主要是为了解决创建对象时的场景而被设计出来的,原型模式是为了处理复制对象的情况,建造者模式是为了处理创建对象时灵活传参的情况,工厂模式是为了处理创建对象的逻辑,单例模式是为了处理全局共用一个对象的情况。

相信看到这里相信都能了解创建型模式的概念和其中各个模式的使用。好了,正文开始,我的总结主要是想做一个反推,这些东西是怎么被设计出来的,无非就是同个场景的代码敲多了,然后进行总结,设计出一套东西来方便这个场景的开发。

比如想象一下没有单例时会怎么去实现单例的功能。(或者说怎么使用low一点的方法来实现单例效果),如果你让我来做,我可能会想到通过序列化来实现,当需要多个地方改同一个对象的某个属性时(不考虑多线程的情况),把这个对象序列化本地,反序列化改属性之后再序列化回去,然后把这套东西封装起来,这也能实现单例的效果,但是这样一比就会发现单例的解决方案比这个方便得多,所以才需要学习设计模式。

加载全部内容

相关教程
猜你喜欢
用户评论