单例模式:
1.饿汉式加载
简单的来说就是构造器私有并且一上来就先把对象new出来
典型的问题就是浪费空间
public class Hungry {
private Hungry(){
}
private final static Hungry hungry =new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
2.懒汉模式:
原始版本
简单的来说就是要用的时候再加载、
最大的问题就是存在线程安全问题
public class LazyMan {
private LazyMan(){
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){
lazyMan=new LazyMan();
}
return lazyMan;
}
}
双重检测锁模式(DCL懒汉式):
要注意:这个lazyMan = new LazyMan();
不是原子性操作,可能发生指令重排,
所以必须加上volatile
public class LazyMan {
private LazyMan(){
}
private static volatile LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
}
3.静态内部类
public class Holder {
private Holder(){
}
private static Holder getInstance(){
return InnerClass.holder;
}
public static class InnerClass{
private static final Holder holder =new Holder();
}
}
问题:归根结底也是不安全的,反射太牛逼,破坏一切
反射大法
反射可以破坏单例模式,举个栗子
public class LazyMan {
private LazyMan(){
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
public static void main(String[] args) throws Exception {
LazyMan instance =LazyMan.getInstance();
Constructor<LazyMan> de =LazyMan.class.getDeclaredConstructor(null);
de.setAccessible(true);
LazyMan instance2=de.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
结果:单例模式被破坏
最后的结论是道高一尺魔高一丈。怎么优化也顶不住反射
最后,我们说一下枚举,枚举很特殊,它在类加载的时候会初始化里面的所有的实例,而且 JVM 保证了它们不会再被实例化,所以它天生就是单例的。
虽然我们平时很少看到用枚举来实现单例,但是在 RxJava 的源码中,有很多地方都用了枚举来实现单例。
工厂模式
工厂模式主要用到的原则:
开闭原则:一个软件的实体应当对扩展开放,对修改关闭
依赖倒转原则:要针对接口编程,不要针对实现编程
迪米特法则:只与直接朋友通信,而避免和陌生人通信
工厂模式的核心:
实例化对象不使用new,用工厂方法代替
将选择实现类,创海对象统一管理和控制,从而将调用者跟我们的实现类解耦
三种模式:
简单工厂模式(又叫做静态工厂模式):
用来生产统一等级结构的任意产品(对于增加新的产品,需要覆盖已有的代码)
虽然某种程度上不符合设计原则,但实际使用较多
简单地说,简单工厂模式通常就是这样,一个工厂类
XxxFactory,里面有一个静态方法,根据我们不同的参数,返回不同的派生自同一个父类(或实现同一接口)的实例对象。我们客户取产品只需要告诉工厂需要产品的名字,不用管实现
要增加一个产品是需要修改代码的,但是由于该模式比较简单,仍然被大量使用
工厂方法模式:
用来生产统一等级结构的固定产品(支持增加任意产品)
不修改已有类的前提下,通过增加新的工厂类实现扩展
其实就是每一种产品对应一个工厂,这会便于横向扩展
抽象工厂模式:
围绕一个超级工厂创造其他工厂。该超级工厂又称为其他工厂的工厂。
一个工厂生产的是一个产品系列。
优点:
- 具体产品在应用层的代码隔离,无需关心创建细节。
- 将一个系列的产品统一到一起创建
缺点:
- 规定了所用可能被创建的产品集合,产品簇中扩展新的产品困难
- 增加了系统的抽象性和理解难度
举个栗子
//手机产品接口
public interface IphoneProduct {
void start();
void shutdown();
void callup();
void sendSMS();
}
//路由器产品接口
public interface IRouteProduct {
void start();
void shutdown();
void openwifi();
void setting();
}
//使用到了以上定义的两种工厂,但是工厂生产什么不需要关心
public interface IProductFactory {
IphoneProduct iphoneProduct();
IRouteProduct routerProduct();
}
实现具体的设计
//小米生产手机
public class XiaomiPhone implements IphoneProduct{
public void start() {
System.out.println("开启小米手机");
}
public void shutdown() {
System.out.println("关闭小米手机");
}
public void callup() {
System.out.println("小米手机打电话");
}
public void sendSMS() {
System.out.println("小米手机发短信");
}
}
//小米生产路由器
public class XioamiRouter implements IRouteProduct {
public void start() {
System.out.println("开启小米路由器");
}
public void shutdown() {
System.out.println("关闭小米路由器");
}
public void openwifi() {
System.out.println("小米路由器打开wifi");
}
public void setting() {
System.out.println("小米路由器设置密码");
}
}
//小米工厂,实现了产品工厂类(而产品工厂类的规范又来自于路由器和手机的抽象类)
public class XiaomiFactory implements IProductFactory {
public IphoneProduct iphoneProduct() {
return new XiaomiPhone();
}
public IRouteProduct routerProduct() {
return new XioamiRouter();
}
}
实现:
public class Clinet {
public static void main(String[] args) {
System.out.println("===============小米系列产品========================");
XiaomiFactory xiaomiFactory=new XiaomiFactory();
IphoneProduct iphoneProduct=xiaomiFactory.iphoneProduct();
iphoneProduct.callup();
iphoneProduct.sendSMS();
IRouteProduct iRouteProduct= xiaomiFactory.routerProduct();
iRouteProduct.openwifi();
}
}
结果:
难扩展性:比如要生产一个键盘,那么所有工厂都加上制造键盘的方法。这显然违反了对修改关闭对扩展打开的原则
再谈一下适用条件:
当涉及到产品族(它代表了组成某个产品的一系列附件的集合)的时候,就需要引入抽象工厂模式了。当涉及到这种产品族的问题的时候,就需要抽象工厂模式来支持了。我们不再定义
CPU 工厂、主板工厂、硬盘工厂、显示屏工厂等等,我们直接定义电脑工厂,每个电脑工厂负责生产所有的设备,这样能保证肯定不存在兼容问题。