单例模式
整个类只能有一个已经预定好的实例,该类保证只有一个对象被创建,这里的一个对象指:不管创建多少个对象,都是相同的对象。
Java反射能够使用private类型的构造器构建实例,遇上反射什么单例模式都不单例了。
单例模式的特点:
- 构造方法为私有
- 持有自己类型的属性
- 对外提供获取单例的静态方法
饿汉式
在程序运行时直接创建单例,线程安全,全局只有一个初始就被实例化的单例,但是因为一开始就初始化,不利于内存利用
//饿汉式
public class Singleton1 {
private static Singleton1 singleton = new Singleton1();
private Singleton1() {
}
public static Singleton1 getInstance() {
if (singleton == null) {
return new Singleton1();
}
return singleton;
}
}
懒汉式
好处是实现了对象的延迟加载,在需要的时候才会创建单例,但是线程不安全,多个线程访问时会出现多个单例。
//懒汉式
public class Singleton2 {
private static Singleton2 singleton;
private Singleton2() {}
public static Singleton2 getInstance() {
if (singleton == null) {
return new Singleton2();
}
return singleton;
}
}
在此有必要说明为什么懒汉式线程不安全,问题就处在下面的代码处:
当线程A运行到(1)处的代码,判定singleton==null的结果为true,准备创建singleton时,线程B获取到了CPU资源开始运行,线程B执行完了以上代码,得到一个singleton对象,但是线程A并不知道,线程A就会继续创建出多余的singleton。
双重加锁模式
保证线程安全。
//双重加锁
public class Singleton4 {
private static volatile Singleton4 singleton;
private Singleton4() {}
public static Singleton4 getInstance() {
//双重判断
if (singleton==null) {
synchronized (Singleton4.class) {
if (singleton==null) {
singleton = new Singleton4();
}
}
}
return singleton;
}
}
静态内部类模式
把singleton的创建放在内部类中,这样当程序运行时,只有在调用getInstance()方法时才会调用内部类创建singleton,既保证了线程安全,又避免了饿汉式一开始就实例化单例。
//静态内部类单例模式
public class Singleton3 {
private Singleton3() {}
public static Singleton3 getInstance() {
return Inner.INSTANCE;
}
//内部类
private static class Inner{
private static final Singleton3 INSTANCE = new Singleton3();
}
}
工厂模式
工厂模式的作用就是解耦,在多个类实现了同一接口的情况下,解接口和实现类之间的耦合。
用户可以根据自己的需求,从工厂中拿走自己需要的产品。当工厂需要增加产品,只要修改工厂类即可,不需要修改客户端代码。
在创建类的实例时,
一个例子:Fruit接口,有Apple,Banana,Orange等实现类。
//水果接口
public interface Fruit {
//得到一种水果
public void get();
}
//苹果
class Apple implements Fruit{
@Override
public void get() {
System.out.println("得到了一个苹果");
}
}
//香蕉
class Banana implements Fruit{
@Override
public void get() {
System.out.println("得到了一根香蕉");
}
}
//橘子
class Orange implements Fruit{
@Override
public void get() {
System.out.println("得到了一个橘子");
}
}
//unknown
class Unknown implements Fruit{
@Override
public void get() {
System.out.println("what the hell do you want from me!");
}
}
简单工厂模式
SimpleFruitFactory.java:
public class SimpleFruitFactory {
public static Fruit getFruit(String FruitName) {
switch (FruitName) {
case "Apple":
return new Apple();
case "Banana":
return new Banana();
case "Orange":
return new Orange();
default:
return new Unknown();
}
}
//客户端代码
public static void main(String[] args) {
Fruit apple = SimpleFruitFactory.getFruit("Apple");
apple.get();
}
}
在客户端代码中,直接从工厂中按需取到产品。在增加其他水果时,只需要增加一种水果类、修改工厂,而客户端代码不需要改变。
简单工厂模式的缺点是:所有产品的逻辑都写在一个工厂类中,会导致工厂类臃肿,不利于维护。
工厂模式
一个工厂接口,派生出多个工厂。
产品逻辑分到不同的工厂来写,容易维护,但缺点是会有多个工厂类,产品大量增多时,工厂类也爆炸式增长。
抽象工厂模式
抽象工厂适用于更复杂的情况,用来创建一组相关或相互依赖的对象。
比如在不同水果的基础上,配额不同口味的果酱(Jam.java),制作不同的水果沙拉。
AbstractSaladFactory.java是一个用于生产水果和果酱的接口:
public interface AbstractSaladFactory {
//获取水果
public Fruit getFruit();
//获取果酱
public Jam getJam();
}
SaladFactory1和SaladFactory2派生自AbstractSaladFactory,是真正生产沙拉的接口。
当需要创建的产品较复杂,比如沙拉同时需要不同种类的水果和果酱,就没有办法再去像工厂模式那样,去定义具体的工厂类,因为没有办法知道到底需要那种水果或是果酱,此时就采用抽象工厂模式。