Singleton单例模式
在一个应用程序中,某个类只有一个实例,而且需要自行实例化并向整个系统提供这个实例;使用单例模式主要是为了避免不一致状态;一般通过getInstance()的方法来获取这个唯一的实例对象;构造方法为private避免了类在外部被实例化,单例模式主要有如下实现方式:
1.懒汉式+synchronized同步锁+双重检查(线程安全,延迟加载方式)
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance(){
if(singleton==null){
synchronized (Singleton.class) {
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
在方法上加synchronized同步锁或是用同步代码块对类加同步锁可以防止多线程环境下会产生多个实例对象;双重检查可以提高代码执行效率,下一个线程想要获取对象,就无须等待上一个线程释放锁之后;缺点是除了安全问题(反射机制),获取对象的速度还是慢于饿汉式;
2.饿汉式(线程安全,立即加载方式)
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
在类加载初始化时就创建好一个静态的实例对象供外部使用,除非系统重启,否则这个对象不会改变;无需关注多线程问题;如果是一个工厂模式、缓存了很多实例,这个时候就需要考虑到效率问题,一般情况推荐饿汉式;
3.静态内部类
public class Singleton {
private static class SingletonInner {
private static final Singleton instance= new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonInner .instance;
}
}
静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果就是多例的;
4.内部枚举类(比较新,但使用方便且安全)
public class Singleton {
// 内部枚举类
private enum EnmuSingleton{
Singleton;
private Singleton singleton;
//枚举类的构造方法在类加载是被实例化
private EnmuSingleton(){
singleton = new Singleton();
}
public Singleton getInstance(){
return singleton;
}
}
public static Singleton getInstance(){
return EnmuSingleton.Singleton.getInstance();
}
}
不仅能避免多线程同步问题,而且还能防止反射机制重新创建新的对象;
Factory工厂模式
简单工厂模式:一个抽象类或接口,多个实现类;由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类实例;
缺点:可扩展性差,每增加一种动物就要增加一个实现类并修改工厂类方法;
//抽象类
public abstract class Animal {
public abstract void run();
public abstract void jump();
}
//实现类
public class Cat extends Animal{
@Override
public void run() {
System.out.println("Cat is running.");
}
@Override
public void jump() {
System.out.println("Cat is jumping");
}
}
public class Dog extends Animal{
@Override
public void run() {
System.out.println("Dog is running.");
}
@Override
public void jump() {
System.out.println("Dog is jumping");
}
}
//工厂类
public class AnimalFactory {
public static Animal getInstance(String type) { //静态方法
Animal a = null;
if ("Cat".equals(type)) {
a=new Cat();
}
if ("Dog".equals(type)) {
a=new Dog();
}
return a;
}
}
//测试
public class Test {
public static void main(String[] args) {
Animal a=AnimalFactory.getInstance("Cat");
a.run();
a.jump();
}
}
工厂方法模式:一个产品抽象类或接口,多个产品实现类;一个工厂抽象类或接口,多个产品工厂实现类;由产品工厂类创建产品类的实例;
//与简单工厂的抽象类及实现类一样,此处省略,也可以用接口
//工厂抽象类
public abstract class AnimalFactory {
public abstract Animal getInstance();
}
//工厂实现类
public class CatFactory extends AnimalFactory{
@Override
public Animal getInstance() {
return new Cat();
}
}
public class DogFactory extends AnimalFactory{
@Override
public Animal getInstance() {
return new Dog();
}
}
//测试
public class Test {
public static void main(String[] args) {
AnimalFactory af=new CatFactory();
Animal a=af.getInstance();
a.run();
a.jump();
}
}
抽象工厂模式:与工厂方法模式不同的是,工厂方法模式中的工厂只生产单一的产品,而抽象工厂模式中的工厂生产多个产品;
//产品1
public interface IAnimal1 {
public void run();
}
public class Animal1 implements IAnimal1{
@Override
public void run() {
System.out.println("我是动物1");
}
}
//产品2
public interface IAnimal2 {
public void run();
}
public class Animal2 implements IAnimal2{
@Override
public void run() {
System.out.println("我是动物2");
}
}
//工厂类
public interface IFactory {
public Animal1 createAnimal1();
public Animal2 createAnimal2();
}
public class Factory implements IFactory{
@Override
public Animal1 createAnimal1() {
return new Animal1();
}
@Override
public Animal2 createAnimal2() {
return new Animal2();
}
}
//测试
public class Test {
public static void main(String[] args) {
IFactory f=new Factory();
f.createAnimal1().run();
f.createAnimal2().run();
}
}
总结:简单工厂将对象进行封装,用户调用工厂类获取对象,对象的实例化都在工厂类;工厂方法在简单工厂的基础上再包了一层工厂,所有的工厂都是此工厂的子类,产生对象的类型由子类工厂决定;抽象工厂为不同产品族的对象创建提供接口,涉及到多个相关对象;
Proxy代理模式
就是给某一个对象创建一个代理对象,由这个代理对象控制对原对象的引用,并可以添加一些额外的操作;根据创建代理类的时间点,分为静态代理和动态代理;
代理模式的结构分为三部分:
-Subject:抽象主题,一般由一个或多个接口组成;
-ProxySubject:代理类,实现抽象主题的所有接口,并持有所代理类的对象引用;
-RealSubject:被代理类,即目标对象;
(1)静态代理:在编译时就已经将接口,被代理类,代理类确定,即有.class文件生成;
公共接口:
public interface A {
public void test();
}
被代理类:
public class B implements A{
private String name;
public B(String name) {
this.name = name;
}
@Override
public void test() {
System.out.println(name+"-----test-----");
}
}
代理类:
public class C implements A{
private B b; //定义一个被代理类对象
public C(A a) {
//只代理B类对象
if(a.getClass()==B.class){
this.b=(B)a;
}
}
//代理执行:调用被代理对象执行方法
@Override
public void test() {
System.out.println("----1----");
b.test();
System.out.println("----2----");
}
}
测试类:
public class Test {
public static void main(String[] args) {
//被代理的b
A b=new B("bb");
//生成代理对象,并将b传给代理对象
A c=new C(b);
c.test(); //被代理执行
}
}
打印:
----1----
bb-----test-----
----2----
本例中并未直接调用B对象的方法,而是通过调用C对象进行代理;且这种间接性,我们可以在代理类代理test()方法时,插入一些自己的操作,这就跟AOP类似了,test()就如一个切点,代理类可以在切点前后插入自己的操作;
(2)动态代理:代理类在运行时被创建;在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,生成JDK动态代理类和动态代理对象;动态代理的优势是对很方便的对代理类的方法做统一处理,而不用修改每个代理类中的方法,是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了;
A接口和B被代理类不变,动态生成代理类如下:
public class MyInvocationHandler<T> implements InvocationHandler {
private T target; //InvocationHandler持有的被代理对象
public MyInvocationHandler(T target) {
this.target = target;
}
/**
* proxy:代表动态代理对象
* method:代表正在执行的方法
* args:代表调用目标方法时传入的实参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理执行"+method.getName()+"方法。");
System.out.println("-----3-----");
Object result = method.invoke(target, args);
System.out.println("-----4-----");
return result;
}
}
//测试
public class Test {
public static void main(String[] args) {
A b=new B("bb");
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler handler=new MyInvocationHandler<A>(b);
//创建一个代理对象proxyA来代理b,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
A proxyA=(A)Proxy.newProxyInstance(A.class.getClassLoader(),new Class<?>[]{A.class}, handler);
proxyA.test();
}
}
打印:
代理执行test方法。
-----3-----
b-----test-----
-----4-----
整个过程并未看到实际的代理类,代理对象通过InvocationHandler来持有对目标对象的引用,因此动态代理中目标对象和代理对象是通过InvocationHandler来完成的代理过程;现在分析一下是具体是怎么操作的;在JDK的java.lang.reflect包下有个Proxy类,是构造代理类的入口,其中newProxyInstance就是创建代理对象的方法,源码如下:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{
if (h == null) {
throw new NullPointerException();
}
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, interfaces);
}
/*
* Look up or generate the designated proxy class.
* 此为关键步骤,生成代理类,这个类文件是缓存在java虚拟机中的
*/
Class<?> cl = getProxyClass0(loader, interfaces);
/*
* Invoke its constructor with the designated invocation handler.
* 获取代理类中带InvocationHandler参数的构造器constructor
* 通过构造器constructor来创建一个动态实例
*/
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
// create proxy instance with doPrivilege as the proxy class may
// implement non-public interfaces that requires a special permission
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
return newInstance(cons, ih);
}
});
} else {
return newInstance(cons, ih);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
}
}
将JVM中的代理类获取出来,并反编译可以看到如下源码:
public $Proxy0(InvocationHandler paramInvocationHandler)throws {
super(paramInvocationHandler);
}
因此,代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。
$Proxy0 extends Proxy implements A,代理类继承了Proxy类,所以也就决定了java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对类的动态代理。
Delegate委派模式
类B和类A是两个互相没有任何关系的类,但是B具有和A一模一样的方法和属性;并且调用B中的方法/属性就是调用A中同名的方法和属性,这样既可以使用A中的功能,又可以将A很好的保护起来;委托的缺点:代码量大,类更多;如下是一个简单的例子,实际可以使用接口,使委托更加安全与灵活;
public class A{
public void run(){
System.out.println("-----A-----");
}
}
public class B{
A a=new A(); //委派
public void run(){
a.run();
}
}
public class Main {
public static void main(String[] args) {
B b= new B();
b.print();
}
}
委托VS代理:代理是若干个对象实现了一个共同的接口,而委派只是说明一个对象引用了另一个对象,并不牵扯接口。