编程基础--以Java语言为基础的23种设计模式--创建者模式(Builder Pattern)

      看本篇博客需要对JVM有一点点了解,当然可以在不懂的地方自己查也是可以的。另外如果有错误的地方,请评论区指正^^^

1、单例模式(Singleton Pattern)

        单例模式呢,顾名思义,在一个JVM运行实例中,某个类只有这么一个对象,无论在哪里获取该类的对象,得到的都是同一个对象,我们称这样的类的设计模式属于单例模式。

单例模式分类有两种:

        饿汉式:在类的加载(类的加载又分为主动使用和被动使用,一般被动使用不会触发clinit方法)时一般是在<clinit>阶段该单例对象就会被创建。(饿汉给人的感觉就是上来就吃嘛…)

        懒汉式:顾名思义,听过懒加载的可能都知道懒的意思,就是用的时候再加载,放在这也就是第一次使用该单例对象的时候会被加载。

        1.1、饿汉式--(静态变量/静态代码块方式)--代码块里有评价

public class Test_01_StaticVar {
    //将构造器私有
    private Test_01_StaticVar(){}
    //定义一个私有的静态变量
    private static Test_01_StaticVar instance = new Test_01_StaticVar();
    //提供获取单例对象的静态方法
    public static Test_01_StaticVar getInstance(){
        return instance;
    }
}

或是这样子写,其实在字节码中都体现在<clinit>方法中

public class Test_01_StaticVar {
    //将构造器私有
    private Test_01_StaticVar(){}
    //定义一个私有的静态变量
    private static Test_01_StaticVar instance = null;

    static {
       instance =  new Test_01_StaticVar();
    }
    public static Test_01_StaticVar getInstance(){
        return instance;
    }
}

//这种方式会在类加载时直接被创建,有关的评价说是因为可能被加载之后一直不使用造成内存浪费,
//很多人包括我也很纳闷,不主动使用怎么会导致类的加载。

        1.2、懒汉式--基础版本

public class Test_02_Lazy01 {
    private Test_02_Lazy01(){}
    private static Test_02_Lazy01 instance;
    public  static  Test_02_Lazy01 getInstance(){
        if(instance == null){
            instance = new Test_02_Lazy01();
        }
        return instance;
    }
}

//缺点就是在并发获取的情况下会造成不是单例的情况。
//例如但不限于以下一种情况:假设instance是null,当A线程指向到判断语句进入if开始new对象之前此线程的调度时间结束开始调度其他线程,
//另一个线程B在这段时间也执行了这个if语句并且也成功的进入if代码块内,之后就会导致两次创建这个对象。造成的结果就不符合单例这一条件。

一种解决方式如下:

public class Test_02_Lazy01 {
    private Test_02_Lazy01(){}
    private static Test_02_Lazy01 instance;
    public  static synchronized  Test_02_Lazy01 getInstance(){   <---不同点在这行
        if(instance == null){
            instance = new Test_02_Lazy01();
        }
        return instance;
    }
}

//synchronized写方法头上的缺点是性能不高,获取该对象就会获取锁。大多数是只需要读就可以的。只有第一次创建时获取锁才有意义

        1.3、懒汉式--升级版本--双重检查锁

public class Test_02_Lazy02 {
    private Test_02_Lazy02(){}
    private static Test_02_Lazy02 instance;
    
    public  static Test_02_Lazy02 getInstance(){
        if(instance == null){
            synchronized (Test_02_Lazy02.class) {
                if(instance == null)
                instance = new Test_02_Lazy02();
            }
        }
        return instance;
    }
}
//是不是感觉这样就很完美,其实不然
//还是有问题,可能会空指针异常,因为虚拟机优化的问题指令序列会重排(据说学过并发编程的应该知道。博主暂时没学)

睁大眼睛了,最终版本如下:
public class Test_02_Lazy02 {
    private Test_02_Lazy02(){}
    private static volatile Test_02_Lazy02 instance;
    
    public  static Test_02_Lazy02 getInstance(){
        if(instance == null){
            synchronized (Test_02_Lazy02.class) {
                if(instance == null)
                instance = new Test_02_Lazy02();
            }
        }
        return instance;
    }
}

volatile 关键字可以保证可见性和有序性。

        1.4、懒汉式--静态内部类方式

        静态内部类在主类加载的时候是不会被加载的,利用这个特点我们可以这样做:

public class Test_02_LazyInnerClass {
    private Test_02_LazyInnerClass(){}

    private static class TestHolder{
        private static final Test_02_LazyInnerClass INSTANCE = new Test_02_LazyInnerClass();
    }

    public  static Test_02_LazyInnerClass getInstance(){
        return TestHolder.INSTANCE;
    }
}
//这是比较推荐的方式,很多开源项目在用这种方式

         1.5、饿汉式--枚举方式

前提知识:

//枚举的学习
public class TheEnumClass {
    public enum Enum1{
        E1("E1"){
            @Override
            public void SayHello() {
                System.out.println("我是E1");
            }
        },              //反编译后会被显示为   public static final TheEnumClass E1; ,
                        // 并且其初始化是在静态代码块中 static{ E1 = new Enum1("E1")  }
        E2 {
              @Override
              public void SayHello() {
                  System.out.println("我是E2");
              }
        },
        E3(){
            @Override
            public void SayHello() {
                System.out.println("我是E4");
            }
        },
        E4(){
            @Override
            public void SayHello() {
                System.out.println("我才是E4");
            }
        }; //分号可加可不加
        private String name;
        private Enum1(String a){
            name = a;
            System.out.println("一个实例被创建");
        }
        private Enum1(){
            System.out.println("一个实例被创建");
        }
        //直接调用
        public void fun(){
            System.out.println("testFunc");
            this.SayHello();
        }

        public abstract void SayHello();

    }

    public static void main(String[] args) {
        Enum1 enum1 = Enum1.E2;
        enum1.fun();
    }


}

//1、注意枚举类是不可继承拓展的
//2、枚举类的对象默认都是 public static final
//3、枚举类的构造器只能是私有的,但是其他的没用限制,你可以重写,包括重载多个
//4、enum类默认extends java.lang.Enum,所以无法再继承其他类

//5、一般来说枚举类经过反编译之后是非抽象的,但是拥有抽象方法的枚举类,你必须在定义枚举类的时候在内部的枚举项也就是那些被psf修饰的变量中直接实现,如上面例子中的那样


idea 反编译的 class文件
public class TheEnumClass {
    public TheEnumClass() {
    }

    public static void main(String[] args) {
        TheEnumClass.Enum1 enum1 = TheEnumClass.Enum1.E2;
        enum1.fun();
    }

    public static enum Enum1 {
        E1("E1") {
            public void SayHello() {
                System.out.println("我是E1");
            }
        },
        E2 {
            public void SayHello() {
                System.out.println("我是E2");
            }
        },
        E3 {
            public void SayHello() {
                System.out.println("我是E4");
            }
        },
        E4 {
            public void SayHello() {
                System.out.println("我才是E4");
            }
        };

        private String name;

        private Enum1(String a) {
            this.name = a;
            System.out.println("一个实例被创建");
        }

        private Enum1() {
            System.out.println("一个实例被创建");
        }

        public void fun() {
            System.out.println("testFunc");
            this.SayHello();
        }

        public abstract void SayHello();
    }
}

使用枚举创建: 

enum Test_05_HangryEnum {
     INSTANCE;
     public void testmethod1(){
         System.out.println("我是实例方法一");
     }
    public void testmethod2(){
        System.out.println("我是实例方法二");
    }
    public Test_05_HangryEnum getInstance(){
         return INSTANCE;
    }
}

        1.6、存在的问题及解决方法

序列化破坏单例模式

//序列化破坏单例模式
public class Test_06_TheProblems {
    @Test
    public void Test() throws Exception {
        Test_04_LazyInnerClass test_04_lazyInnerClass = Test_04_LazyInnerClass.getInstance();
        System.out.println(test_04_lazyInnerClass); //Test_04_LazyInnerClass要序列化
        writeObjectTofile(test_04_lazyInnerClass);
        Object o = readObjectTofile();  // ctrl + alt +v  自动赋值
        System.out.println(o);
    }
    public Object readObjectTofile() throws Exception {
        ObjectInputStream osi = new ObjectInputStream(new FileInputStream("Y:abc.txt"));
        Object o = osi.readObject();
        return o;
    }
    public void writeObjectTofile(Object o) throws Exception {
        File f = new File("Y:abc.txt");
        if(!f.exists()){f.createNewFile();}
        ObjectOutputStream owi = new ObjectOutputStream(new FileOutputStream(f));
        owi.writeObject(o);
    }
}

//com.L.singnalton.Test_04_LazyInnerClass@6cc7b4de
//com.L.singnalton.Test_04_LazyInnerClass@2d127a61
//可以看出两个对象并不是同一个,说明序列化可以破坏单例模式

//解决方法是在单例类中加一个方法(这个方法不可以是静态的),返回单例对象,该方法的内容写成和getInstance方法一样即可。
//这样在反序列化时会将该方法的返回值返回

    //测试4中内部类方式创建单例的类中加入下面这个方法即可,其他的类似,只要符合单例逻辑
    public Object readResolve(){
        return TestHolder.INSTANCE;
    }
//序列化破坏单例模式
public class Test_07_TheProblems {
    @Test
    public void Test() throws Exception {
        Class clazz = Test_04_LazyInnerClass.class;
        //获取Class三种方法,1,Class.forname   对象.getClass()  直接使用类名.class

        Constructor constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        System.out.println(constructor.newInstance());  //ctrl + D 快速复制当前行到下一行
        System.out.println(constructor.newInstance());
    }
}
//com.L.singnalton.Test_04_LazyInnerClass@276438c9
//com.L.singnalton.Test_04_LazyInnerClass@588df31b
//可以看出调用了本来被隐藏的构造方法,得到的两个对象并不一样。
//那么聪明的你一定想到解决方法了,没错就是在构造方法里面搞了,怎么搞你随便,就是阻止这么创建对象,例如你可以在构造方法里面抛异常,随便,就是不让成功创建。

2、工厂模式(Factory Pattern)

        在java中万物皆对象,这些对象都需要创建,如果创建的时候使用new的方式,就会造成严重的耦合,假如我们需要对某个接口使用其他的实现类替换,那么我们就需要对代码中所有的new的地方替换右侧的new到的对象,这显然是很麻烦的。如果使用工厂模式来设计,我们只需要和工厂打交道,如果更换对象,只需要在工厂中改变实现类即可。

        2.1、简单工厂模式

//抽象产品角色,可以是接口或者抽象类
public abstract class TheAbstractFoodProduct {
    public abstract String getFoodName();
}
//具体产品1
public class TheNoodles extends TheAbstractFoodProduct {
    public String getFoodName() {
        return "面条";
    }
}
//具体产品2
public class TheRice extends TheAbstractFoodProduct {
    public String getFoodName() {
        return "大米";
    }
}
//工厂角色
public class SimpleFactory {
    private SimpleFactory(){}
    public static TheAbstractFoodProduct getFood(String type){  //因为这个类并没用什么其他的作用,声明称静态方法更合适
        if(type==null){
            return null;
        }
        if(type.equals("面条")){
            return new TheNoodles();
        }
        else if(type.equals("米饭")){
            return new TheRice();
        }
        else return null;
    }
}
//测试类
public class Use_Test {
    @Test
    public void test1() {
        TheAbstractFoodProduct food = SimpleFactory.getFood("米饭");
        System.out.println(food.getFoodName());
    }
}

 

        其实还是可以体会出来,如果在多个类中使用到抽象产品,那么我们一个个的new的话,将来不改的话挺好,如果改起来的话就很麻烦,这里的改包括具体产品的类名、所处的包的位置等的变化,使用简单工厂可以以后只修改工厂类这一个类。

不过,简单工厂的缺点也是显而易见的,那就是如果增加新的产品实现,必然要更改工厂里面的逻辑(不符合开闭原则),所以呢就出现了下面这种方式,那就是将工厂类也抽象出来,形成抽象工厂。

        2.2、工厂方法模式

        就如同上面结束所说,工厂方法模式将具体的工厂类和具体的产品对应起来,所以缺点也是明显的那就是类太多出一类产品就要多一个工厂。有如下角色

        抽象工厂:提供了创建产品的接口,调用者通过它访问具体的工厂创建产品。

        具体工厂:主要是实现抽象工厂中的抽象方法,完成具体的产品的创建。

        抽象产品:定义产品的规范,描述了产品的主要特性和功能。

        具体产品:实现了抽象产品角色所定义的接口,由具体的工厂创建,它同具体的工厂之间一一对应。

 

//抽象产品角色,可以是接口或者抽象类
public interface TheAbstractFoodProduct {
     String getFoodName();
}

        

//具体产品1
public class TheNoodles implements TheAbstractFoodProduct {
    public String getFoodName() {
        return "面条";
    }
}
//具体产品2
public class TheRice implements TheAbstractFoodProduct {
    public String getFoodName() {
        return "大米";
    }
}
//抽象工厂角色
public interface FoodFactory {
     TheAbstractFoodProduct createFood();
}
//具体工厂1
public class NoodlesFactory implements FoodFactory {
    public TheAbstractFoodProduct createFood() {
        return  new TheNoodles();
    }
}
//具体工厂2
public class RiceFactory implements FoodFactory {
    public TheAbstractFoodProduct createFood() {
        return  new TheRice();
    }
}
//测试类
public class Use_Test {
    @Test
    public void test1() {
        TheAbstractFoodProduct food = new NoodlesFactory().createFood();
        System.out.println(food.getFoodName());
    }
}

正如简单工厂的问题,当我们增加新的产品的时候,我们需要修改原来的工厂类,而使用工厂方法模式之后我们增加新的一个产品,就需要增加一个工厂类,这样也是有问题的,这样产生的类就太多了,我们生活中的很多产品其实有这样一种规律,一个工厂其实是可以生产一类属性相近产品,如华为的电脑,手机,平板等,下面讲的抽象工厂模式就是这种思想,将生产一类产品的工厂抽象出来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值