设计模式之学习1

什么情况才会使用设计模式

设计模式是解决问题的方案,学习现有的设计模式可以做到经验复用

设计模式按照用途,可分为三类:创建型、结构型、行为型

创建型

单例模式

单例模式确保一个类只有一个实例,通过一个私有的构造函数、一个私有的静态属性、一个公有的静态函数实现。

私有构造函数保障了用户不能通过构造函数创建对象实例,只能通过公有的静态方法来返回唯一的私有静态变量

懒汉式

在以下实现中,私有静态属性singleton被延迟实例化,这样的好处是,当没有使用到该类时,就不会实例化,从而节约资源。

但是,这种实现是线程不安全的,如果多个线程进入了if (singleton == null)的空判断,并且此时singleton = null时,那么会有多个线程执行**singleton = new Singleton()**创建多个实例,导致不是单实例。

public class Singleton {

    private static Singleton singleton = null;
    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
饿汉式

饿汉式与懒汉式的区别是,在进行类加载时就已经实例化了。在线程调用**getInstance()**方法时,由于已经创建了对象实例,所以多个线程获取的实例都是同一个,是线程安全的。但是在类加载时就创建对象,会失去懒加载的好处。

public class Singleton1 {
    private static Singleton1 instance = new Singleton1();

    private Singleton1() {}

    public static Singleton1 getInstance() {
        return instance;
    }
}

懒汉式-线程安全

既然懒汉式是线程不安全的,所以可以通过对线程不安全的方法getInstance()加锁。但是这种方法会带来性能问题,当多个线程都试图访问getInstance方法时,只有一个线程能够获得锁并进行访问,而其他线程都会被阻塞,即使instance已经实例化了,直到拥有锁的线程释放锁,不推荐使用该方法

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

双重校验锁

我们想要的是,在类未进行实例化时访问getInstance方法,需要对该方法加锁,如果已经实例化了,那么后续的线程应该都可以进行访问,无需加锁阻塞线程。

public class Singleton3 {
    private static volatile Singleton3 instance = null;
    private Singleton3() {}
    
    public static Singleton3 getInstance() {
        if (instance == null) {
            synchronized (Singleton3.class) {
                if (instance == null) {
                    instance = new Singleton3();
                }
            }
        }
        return instance;
    }
}
面试题
  1. 能不能只使用1个if,而不是两个if?
    如果只使用了一个if,当多线程进入了if (instance == null)空值判断,并且此时instance = null,多个线程都能进入到if语句块内,虽然if语句块内有加锁操作,每次只能让一个线程进行访问,但是多个线程都会执行对象实例化操作,创建了多个对象实例;而使用双重校验,第一个if用于对象实例化后不需要再加锁,第二个if用于进入了锁代码块后,只有当前私有静态属性instance为空才能进行实例化,从而避免了多次实例化操作
  2. 为什么需要对instance 添加volatile关键字修饰?
    volatile保证了指令的禁止重排,而执行**instance = new Singleton3()需要执行三个操作:
    a.为instance分配内存空间
    b.instance进行初始化操作
    c.为instance分配的内存地址
    JVM具有指令重排的特性,执行顺序可能会变成a->c->b,在单线程语意下不会出现问题,但是在多线程环境下,线程a的执行顺序可能是a->c->b,此时线程b调用
    getInstance()**方法发现instance不为空,直接返回instance,但是线程a还没有执行初始化,此时返回的是一个没有初始化的对象。使用volatile禁止指令重排,保证在多线程语意下也能正常执行
静态内部类实现*

在进行类加载时,静态内部类SingletonHolder还没有被加载到内存,只有访问**getInstance()**方法时才会进行加载,而且这种方式只会创建一次对象实例,所以这种方式实现懒加载的同时实现了线程安全

public class Singleton4 {
    private Singleton4() {}

    private static class SingletonHolder {
        private static final Singleton4 INSTANCE = new Singleton4();
    }

    public static Singleton4 getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
枚举实现?
public enum Singleton5 {
    INSTANCE;

}

通过枚举实现,可以避免反射攻击,在其他实现中,通过setAccessiable()方法可将私有函数访问级别改成public的,此时单实例模式就会失效。枚举实现由JVM保证只会实例化一次,因此不会出现反射攻击的情况。

在实现序列化和反序列化后,也不会出现多个实例对象。而其他的实现方法,需要额外添加transient关键字,并且要手动实现序列化和反序列化方法

思考
  1. 枚举实现如何保证线程安全
  2. 枚举如何防止被反射破坏

具体可参考文章:单例模式竟然有这么多种写法

工厂模式

工厂模式可分为简单工厂、工厂方法、抽象工厂方法

简单工厂

简单工厂提供了一个创建对象的接口,不会向客户暴露内部细节

简单工厂把实例化操作单独放到一个类中,这个类就成为了简单工厂类,让简单工厂类来决定用哪个子类进行实例化。

这样做让客户类和具体子类实现解耦,客户类不再需要知道有哪些子类和子类的实现细节。一旦子类发生变化,如增加一个子类,只需在简单工厂类进行修改即可。

interface Product {

}

class ConcreteProduct implements Product {

}

class ConcreteProduct1 implements Product {

}

class ConcreteProduct2 implements Product {

}

public class SimpleFactory {
    public Product createProduct(int type) {
        if (type == 1) {
            return new ConcreteProduct();
        } else if (type == 2) {
            return new ConcreteProduct1();
        }
        return new ConcreteProduct2();
    }
}

客户端可直接通过简单工厂类来创建对象

class Client {
    public static void main(String[] args) {
        SimpleFactory factory = new SimpleFactory();
        Product product = factory.createProduct(1);
    }
}
工厂方法

定义了一个创建对象的接口,与简单工厂方法不同,由子类去决定实例化哪个对象,把实例化操作推迟到子类。

Factory中有一个doSomething()方法,这个方法需要用到一个产品对象,这个产品对象由一个**factoryMethod()**方法创建,该方法是抽象的,需要子类去实现。

public abstract class Factory {
    abstract public Product factoryMethod();
    public void doSomething() {
        Product product = factoryMethod();
    }
}

class ConcreteFactory extends Factory {

    @Override
    public Product factoryMethod() {
        return new ConcreteProduct();
    }
}

class ConcreteFactory1 extends Factory {

    @Override
    public Product factoryMethod() {
        return new ConcreteProduct1();
    }
}

抽象工厂

提供一个接口,用于创建相关的对象家族。

工厂模式用于创建一个对象,而抽象工厂用于创建对象的家族,创建的多个对象之间是有相关性的。例如创建一台电脑,电脑是通过键盘、鼠标、显示器、主机组成的。而键盘,也是由多家厂商的键盘。但是一般情况下,有些产品是有相关性的,比如苹果的设备和安卓的设备不一定都能进行组合。

public abstract class AbstractFactory {
    abstract AbstractProductA createProductA();
    abstract AbstractProductB createProductB();
}
class ConcreteFactory11 extends AbstractFactory {

    @Override
    AbstractProductA createProductA() {
        return new ProductA1();
    }

    @Override
    AbstractProductB createProductB() {
        return new ProductB1();
    }
}
class ConcreteFactory22 extends AbstractFactory {

    @Override
    AbstractProductA createProductA() {
        return new ProductA2();
    }

    @Override
    AbstractProductB createProductB() {
        return new ProductB2();
    }
}
class client {
    public static void main(String[] args) {
        AbstractFactory abstractFactory = new ConcreteFactory11();
        AbstractProductA productA = abstractFactory.createProductA();
        AbstractProductB productB = abstractFactory.createProductB();
    }
}
class AbstractProductA {
    // 相当于键盘产品
}
class AbstractProductB {
    // 相当于显示器产品
}
class ProductA1 extends AbstractProductA {
    // 相当于安卓的键盘
}
class ProductA2 extends AbstractProductA {
    // 相当于苹果的键盘
}
class ProductB1 extends AbstractProductB {
    // 相当于安卓的显示器
}
class ProductB2 extends AbstractProductB {
    // 相当于苹果的显示器
}


生成器模式/建造者模式

原型模式

总结

  1. 单例模式:保证一个类只有一个实例,并提供一个它的全局访问点
  2. 抽象工厂:提供一个创建一系列相关或相互依赖对象的接口
  3. 工厂方法:定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使类的实例化延迟到子类
  4. 建造模式:
  5. 原型模式:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值