欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

一文详细介绍Java设计模式--单例模式,工厂模式,抽象工厂模式

程序员文章站 2024-03-07 18:25:57
...

JAVA设计模式

JAVA设计模式共有23中,其中:
(1)创建型模式(五种):工厂方法模式抽象工厂模式单例模式、建造者模式、原型模式;
(2)结构型模式(七种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
(3)行为型模式(十一种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

创建型模式,顾名思义,是创建实例对象时使用的设计模式。

1 单例设计模式(Singleton)

单:唯一;
例:实例;
即,某个类在整个系统中只能有一个实例对象被获取和使用的代码模式,比如说我们电脑上的进程管理器,双击图标打开,再次双击图标依然弹出第一次双击出来的对话框,类似应用还有回收站/打印机,JVM运行环境Runtime类等。

常见场景:Windows 任务管理器;
Windwos 回收站;
项目中,读取配置文件的类,一般也只有一个对象,没有必要每次都去 new对象读取;
网站的计数器一般也会采取单例模式,可以保证同步;
数据库连接池的设计一般也是单例模式;
在Spring中,每个Bean就是默认单例的。

要点:1.某个类只能有一个实例;(构造器私有化)
2.他必须自行创建该实例;(含有一个该类的静态变量去保存这个唯一的实例)
3.他必须自行向系统提供整个实例。(对外提供获取该实例对象的方式:1.直接暴露;2.用静态变量的get方法获取)

拓展:
     JAVA的成员变量有两种:
    一种是被static关键字修饰的变量,叫类变量或者静态变量。
    另一种没有static修饰,为成员变量
    类的静态变量在内存中只有一个,Java虚拟机在加载类的过程中为静态变量分配内存,静态变量位于方法区,被类的所有实例所共享。静态变量可以通过类名去访问,而不要new一个对象去访问,它的生命周期取决于类的声明周期;
    而实例变量取决于类的实例,也就是对象。每创建一个实例,Java虚拟机就会为变量分配一次内存,实例变量位于堆中,其生命周期取决于实例的生命周期。

    JAVA中初始化的顺序:
    1.加载类;
    2.静态变量初始化;(方法区)
    3.静态块;【其只能调度静态的,不能调度非静态的】
    4.成员变量;(堆)
    5.构造方法; 
    
    构造方法:一般是用于初始化实例时使用的方法,与普通方法的区别在于,普通方法需要新建一个对象,去调用,而构造方法是对象生来就有的,不需要调用。
    构造方法的几点特殊性:
    1.构造方法的名字必须和定义它的类名称完全相同,没有返回类型,连void也没有;
    2.类中必定有构造方法,不写系统默认赠送一个无参的构造方法。如果自己写了构造方法,系统便不在赠送;
    接口不允许被实例化,因此接口中没有构造方法;
    3.不能被static,final,synchronized,abstrctic和native修饰;
    4.构造方法在初始化对象时自动执行,一般不能被显式的直接调用。当同一个类存在多个构造方法是,Java编译系统会自动按照初始化时最后面括号的参数个数以及参数类型来自动一一对应,完成构造函数的调用。
    5.构造方法可以被重载,没有参数的构造方法称为默认构造方法,与普通方法一样,构造方法也是方法,可以将其设计为进行各种初始化活动,初始化对象的属性。

饿汉式

饿汉式,直接创建对象,不存在线程安全问题

/**
 * 饿汉式:直接创建对象,不管你需不需要
 */
public class Singleton1 {
    //(2)自行创建对象,并用静态变量保存
    //(3)向外提供这个实例,因此用public修饰,
    // (4)为了强化这个实例不能被修改,用final在此修饰--是一个单例
    public final static Singleton1 INSTANCE  = new Singleton1();
    //(1)构造器私有化,不能让外部去实现这个实例
    private Singleton1(){
        
    }
}
/**
 * 枚举类型:表示该类型的对象是有限的几个
 * 我们可以限定为一个,就成了单例
 */
public enum  Singleton2 {
    INSTANCE
}

//测试代码
public class tesT {
    public static void main(String[] args) {
        Singleton1 s = Singleton1.INSTANCE;//不是new出来的,直接调用这个实例
        System.out.println(s);
        Singleton2 s2 = Singleton2.INSTANCE;
        System.out.println(s2);
    }
}

输出结果:
yu.practiceLeetCode02.Singlenton.Singleton1@10f87f48
INSTANCE
枚举类型重写了toString 方法
public class Singleton3 {
    //构造器私有化
    private Singleton3(){

    }
    //创建唯一实例--类初始化的时候就立即加载该实例
    private static Singleton3 instance = new Singleton3();
    //向外提供该实例--方法二,利用getter()方法
    //由于没有存在并发问题,因此不用加synchronized修饰,效率高
    public static Singleton3 getter(){
        return instance;
    }
}

class Singleton3Test{
    public static void main(String[] args) {
        //不能new
        //通过get方法来得到
        Singleton3 instance = Singleton3.getter();
        Singleton3 instance2 = Singleton3.getter();
        System.out.println(instance);
        System.out.println(instance2);
        System.out.println(instance == instance2);

    }
}

懒汉式

懒汉式,延迟创建对象,保证线程安全,又实现了懒加载,但是效率比较低

/*
懒汉式:在类初始化时,并不立即加载该对象,为了避免当该对象长期不被调用时,占内存空间较大;
只有当调用getInstance()方法时,才加载该对象;
而且当instance不是null时,第二次就直接返回instance了,不再new
 */
public class Singleton4 {
    //构造器私有化
    private Singleton4(){

    }
    //类初始化时,不立即加载该对象
    private static Singleton4 instance ;

    //向外提供获取该对象的方法,
    //为了避免多线程同时进入该方法,造成负载较大,用synchronized,效率低
    public static synchronized Singleton4 getInstance(){
        if(instance == null) instance = new Singleton4();
        return instance;
    }
}

class Singleton4Test{
    public static void main(String[] args){
        Singleton4 s1 = Singleton4.getInstance();
        Singleton4 s2 = Singleton4.getInstance();
        System.out.println(s1.equals(s2));
        
    }
}

DCL:double click lock,双重锁进行优化

public class Singleton5 {
    //构造器私有化
    private Singleton5(){}

    //创建唯一实例
    //此处可能出现一种情况就是,一个线程进去了锁内,但是还未完全走出来,
    //另一个线程就准备进去,这样就会出现意想不到的错误,所以需要加入volatile关键词
    //volatile 关键词,可以保证有线程在对该变量进行修改时,另一个变量中该线程的缓存就失效了
    //总之,一个线程如果改变了某个变量的值,在另一个线程看来是立即生效的
    private volatile static Singleton5 instance;

    //向外提供该实例(改变锁的地方)
    public static Singleton5 getInstance(){
        if(instance == null) {
            //和同类竞争锁,进来之后如果发现instance不为空,代表锁已经被用过了
            //直接返回对象就好了
            synchronized(Singleton3.class){
                if(instance == null){
                    instance = new Singleton5();
                }
            }
        }
        return instance;      
    }
}

volatile,易变的,无常的,不稳定的,在Java并发编程中,常用来保持内存可见性以及防止指令重排。

静态内部类方法:在内部类被加载和初始化时,才创建实例对象;
静态内部类不会随着外部类的加载和初始化而初始化,他是要单独加载和初始化的。(保证了线程安全)

public class Singleton6 {
    private Singleton6(){}
    //内部类
    private static class InnerClass{
        private static final Singleton6 instance = new Singleton6();
        //保证在内存中只有一个线程的存在
    }
    public static Singleton6 getInstance(){
        return  InnerClass.instance;
        //通过调用内部类的方法去调用实例
    }

}
class test{
    public static void main(String[] args) {
        Singleton6 instance = Singleton6.getInstance();
        Singleton6 instance2 = Singleton6.getInstance();
        System.out.println(instance == instance2);

    }
}

2 工厂模式

工厂模式:实现创建者和调用者的分离
详细分类:

  1. 简单工厂模式
    new 实例
  2. 工厂方法模式
    new 工厂
  3. 抽象工厂模式
    超级工厂,创建工厂的工厂

简单工厂模式

也称静态工厂,在工厂类内部都是用static修饰。
1.先写一个车的接口:定义车的一些属性(例如name)

//车的接口,只定义属性,但不实现
public interface Car {
    void name();
}

2.写两个具体的接口实现类:实现车这个接口,比如(特斯拉,五菱宏光)

public class WuLing implements Car{
   @Override
    public void name(){
       System.out.println("五菱宏光");
   }
}

//实现类必须重写接口中的所有方法
public class Tesla implements Car{
    @Override
    public void name(){
        System.out.println("特斯拉");
    }
}

3.建一个消费者类,消费者获得车

public class consumer {
    public static void main(String[] args) {
        //实例化对象时不使用new,而使用工厂来创建
        //需要接口,以及所有的实现类,多态写法
        Car car  = new WuLing();
        Car car2 = new Tesla();
        car.name();
        car2.name();
}

但是看消费者类,在调用接口时,既需要直到接口名称,还需要直到所有实现类的名称,是不是感觉太麻烦?而且不妥当,消费者是来买车的,还需要直到内部原理,怎么样把这个车建出来吗?因此我们需要另外一个类,CarFactory,工厂负责来调用这个接口,把车造好,消费者只需要告诉工厂我需要什么车,提车就好了。

public class CarFactory {
   //写一个方法去拿到车
   //工厂类调用接口,用getCar()方法得到车
    public static Car getCar(String car){
        if(car.equals("五菱")){
            return new WuLing();
        }else if (car.equals("特斯拉")){
            return new Tesla();
        }else{
            return null;
        }
    }

}

4.再回头看consumer类得到车的方式

public class consumer {
    public static void main(String[] args) {
        //实例化对象时不使用new,而使用工厂来创建
        //需要接口,以及所有的实现类,多态写法
        //Car car  = new WuLing();
        //Car car2 = new Tesla();
        Car car = CarFactory.getCar("五菱");
        Car car2 = CarFactory.getCar("特斯拉");

        car.name();
        car2.name();

        /*
        interface Car 是接口,里面定义了车的名字属性
        class WuLing 和 class Tesla 实现了接口interface里面的属性(通过重写方法来实现)

        对于consumer类来说,有两种方式来使用这个接口:
        第一种  接口名称  对象名 = new 实现类名称  通过变量去调用接口里面的属性方法
        因此第一种我们需要直到接口名称和实现类名称,对于面向对象的语言来说,太麻烦了

        第二种,我们建一个工厂,让工厂来调用接口,而作为消费者的我们不需要在new对象了
        直接通过工厂就可以得到
        class CarFactory  里面写getter方法,传入参数(String),这样consumer类在调用CarFactory
        时直接调用getCar()方法就可以得到。

         */
    }

}

一文详细介绍Java设计模式--单例模式,工厂模式,抽象工厂模式
但是,这种简单工厂方法,如果再增加一个车的话(也就是新增一个接口的实现类),就需要对CarFactory进行改动,这样的话依然违背设计原则中的开闭原则
所以扩展代码时为了不去改变原来的类,引入第二种模式:工厂方法模式。

工厂方法模式

1.接上面例子,我们依然需要创建一个Car接口,两个实现类(Tesla,WuLing),

public interface Car {
    void name();
}
public class Tesla implements Car {
    @Override
    public void name(){
        System.out.println("特斯拉");
    }
}

public class WuLing implements Car{
    @Override
    public void name(){
        System.out.println("五菱");
    }
}

2.我们要把工厂设置称为一个接口

public interface CarFactory {
    Car getCar();
}

3.每辆车拥有一个自己的工厂,两个实现类实现工厂接口

public class TeslaFactory implements CarFactory {
    @Override
    public Car getCar() {
        return new Tesla();
    }
}
public class WuLingFactory implements CarFactory{
    @Override
    public Car getCar(){
        return new WuLing();
    }

}

4.最后创建消费者类,消费者直接new工厂,拿出车就可以了

public class Consumer {
    public static void main(String[] args) {
        Car car = new WuLingFactory().getCar();
        Car car1 = new TeslaFactory().getCar();
        car1.name();
        car.name();
    }
}

这样的话如果在新增一个车,只需要添加一个车接口,一个工厂接口,一个车的实现类(实现车),一个车工厂接口(实现工厂),消费者new车工厂,拿到车;
一文详细介绍Java设计模式--单例模式,工厂模式,抽象工厂模式

3 抽象工厂模式

问题又出现了:如果接口很多的情况下,我们怎么处理呢
抽象工厂:超级工厂的工厂
一文详细介绍Java设计模式--单例模式,工厂模式,抽象工厂模式
1.建立两个接口,手机和路由器

//手机产品
public interface IphoneProduct {
    void start();
    void shutdown();
    void call();

}
//路由器产品
public interface RouterProduct {
    void start();
    void shutdown();
    void openwifi();

}

2.品牌1:小米手机实现手机接口,小米路由器实现小米路由器接口

//小米手机--实现功能
public class xiaomiPhone implements IphoneProduct {
    @Override
    public void start() {
        System.out.println("开启小米机");
    }

    @Override
    public void shutdown() {
        System.out.println("关闭小米手机");
    }

    @Override
    public void call() {
        System.out.println("小米手机打电话");
    }
}

public class xiaomiRouter implements RouterProduct {
    @Override
    public void start() {
        System.out.println("开启小米路由器");
    }

    @Override
    public void shutdown() {
        System.out.println("关闭小米路由器");
    }

    @Override
    public void openwifi() {
        System.out.println("开启小米路由器WiFi");
    }
}

3.品牌二:华为手机实现手机接口,华为路由器实现路由器接口

public class huaweiPhone implements IphoneProduct {
    @Override
    public void start() {
        System.out.println("开启华为手机");
    }

    @Override
    public void shutdown() {
        System.out.println("关闭华为手机");
    }

    @Override
    public void call() {
        System.out.println("华为手机打电话");
    }
}

public class huaweiRouter implements RouterProduct {
    @Override
    public void start() {
        System.out.println("开启华为路由器");
    }

    @Override
    public void shutdown() {
        System.out.println("关闭华为路由器");
    }

    @Override
    public void openwifi() {
        System.out.println("开启华为路由器WiFi");
    }
}

4.抽象工厂:建立工厂接口,里面不指定生产具体品牌,只生产手机和路由器(抽象产品)

//抽象产品工厂:工厂的工厂
public interface IproductFactory {
    //生产手机
    IphoneProduct iphoneProduct();
    //生产路由器
    RouterProduct routerProduct();
    //这些抽象的产品需要具体的工厂去实现
}
//接口调用接口,顶层抽象的接口方法中可以提供一个方法,参数是另外一个接口类型,就可以调用了。

5.建立实现类:小米工厂实现工厂(生产小米品牌的手机,路由器)
华为工厂实现工厂(生产华为品牌的手机,路由器)

//小米工厂,实现抽象工厂
public class xiaomiFactory implements IproductFactory {
    @Override
    public IphoneProduct iphoneProduct() {
        return new xiaomiPhone();
    }

    @Override
    public RouterProduct routerProduct() {
        return new xiaomiRouter();
    }
}

public class huaweiFactory  implements IproductFactory{
    @Override
    public IphoneProduct iphoneProduct() {
        return new huaweiPhone();
    }

    @Override
    public RouterProduct routerProduct() {
        return new huaweiRouter();
    }
}

至此,抽象工厂代码也就写完了,现在我们测试一下

public class Client {
    public static void main(String[] args) {
        System.out.println("=================小米系列产品==============");
        //小米工厂
        xiaomiFactory XiaoMiFactory = new xiaomiFactory();
        //生产手机
        IphoneProduct iphoneProduct = XiaoMiFactory.iphoneProduct();
        iphoneProduct.call();
        iphoneProduct.shutdown();
        iphoneProduct.start();

        System.out.println("=================华为系列产品==============");
        huaweiFactory HuaWeiFactory =  new huaweiFactory();
        //生产路由器
        RouterProduct routerProduct = HuaWeiFactory.routerProduct();
        routerProduct.openwifi();
        routerProduct.shutdown();
        routerProduct.start();


    }
}

输出结果:
=================小米系列产品==============
小米手机打电话
关闭小米手机
开启小米机
=================华为系列产品==============
开启华为路由器WiFi
关闭华为路由器
开启华为路由器

Process finished with exit code 0

小结:简单工厂模式(静态工厂模式),虽然某种程序上不符合设计原则,但是实 际使用最多。
工厂方法模式:不修改已有类的前提下,通过增加新的工厂类实现拓展。
抽象工厂模式:不可以增加产品,可以增加产品族。

相关标签: 随笔