设计模式
单例模式
介绍
单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
分类
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
代码实现
饿汉式实现
/**
* 饿汉式,类加载时就创建实例
*/
public class Hungry {
private Hungry() {
}
private static Hungry instance = new Hungry();
public static Hungry getInstance(){
return instance;
}
}
public class Client {
public static void main(String[] args) {
Hungry instance1 = Hungry.getInstance();
Hungry instance2 = Hungry.getInstance();
System.out.println(instance1==instance2);
}
}
执行结果
true
Process finished with exit code 0
懒汉式实现
/**
* 懒汉式
*/
public class Lazy {
private Lazy() {
}
private static Lazy instance;
/**
* 需要synchronized锁住,原因:
* 假设线程1进到if判断,但此时CPU执行权被线程2抢走
* 如果不加锁,线程2也进入if判断,此时两个线程都发现instance为null,线程2创建一个实例,
* cpu执行权交回给线程1,由于CPU执行权被抢走之前线程1判断的instance为null,所以线程1又会创建一个实例
* 此时就无法保证单例了
* @return
*/
public static synchronized Lazy getInstance() {
if (instance==null){
instance = new Lazy();
}
return instance;
}
}
上面的方式实现了线程安全,但是单例绝大多数情况下都是读操作,很少是写操作,锁住整个方法势必会造成效率低下。所以可以使用双重检查锁来解决这个问题,把锁放到方法里面,只有判断到了instance为null,需要创建时才进行加锁,这样效率会高很多,加锁之后再判断一次instance是否为null,就可以实现线程安全了。但是现在还存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作,要解决双重检查锁模式带来的空指针异常的问题,需要使用volatile关键字,volatile关键字可以保证可见性和有序性。
/**
* 懒汉式,双重检查锁
*/
public class Lazy2 {
private Lazy2() {
}
private static volatile Lazy2 instance;
/**
* 需要synchronized锁住,原因:
* 假设线程1进到if判断,但此时CPU执行权被线程2抢走
* 如果不加锁,线程2也进入if判断,此时两个线程都发现instance为null,线程2创建一个实例,
* cpu执行权交回给线程1,由于CPU执行权被抢走之前线程1判断的instance为null,所以线程1又会创建一个实例
* 此时就无法保证单例了
* @return
*/
public static synchronized Lazy2 getInstance() {
if (instance==null){
synchronized (Lazy2.class){
if (instance==null){
instance = new Lazy2();
}
}
}
return instance;
}
}
静态内部类实现
静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序。
/**
* 懒汉式,静态内部类
*/
public class Singleton {
private Singleton() {
}
//定义一个静态内部类
private static class SingletonHolder {
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
public Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
说明:
第一次加载Singleton类时,不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证Singleton类的唯一性。
枚举方式实现
枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所有单例实现中唯一一种不会被破坏的单例实现模式。
/**
* 枚举实现方式
*/
public enum Singleton {
INSTANCE;
}
返回值可以使用枚举类定义
/**
* 定义全局返回的枚举类
*/
@Getter // 提供获取属性值的getter方法
public enum ResultCodeEnum {
SUCCESS(200 , "操作成功") ,
LOGOUT_SUCCESS(200,"注销成功"),
LOGIN_ERROR(201 , "用户名或者密码错误"),
VALIDATECODE_ERROR(202 , "验证码错误") ,
DATA_ERROR(204, "数据异常"),
LOGIN_AUTH(208 , "用户未登录"),
SYSTEM_ERROR(9999 , "您的网络有问题请稍后重试"),
;
private Integer code ; // 业务状态码
private String message ; // 响应消息
private ResultCodeEnum(Integer code , String message) {
this.code = code ;
this.message = message ;
}
}
工厂模式
介绍
工厂模式属于创建型模式,它提供了一种创建对象的最佳方式,在创建对象时不会被客户端暴露创建逻辑
设计一个咖啡店点餐系统来模拟工厂模式
设计一个咖啡类(Coffee),并定义其两个子类美式咖啡AmericanCoffee和拿铁咖啡LatteCoffee,再设计一个咖啡店类CoffeeStore,咖啡店具有点咖啡的功能。
具体的设计如下
public abstract class Coffee {
public abstract String getName();
public void addSugar(){
System.out.println("加糖");
}
public void addMilk(){
System.out.println("加奶");
}
}
public class AmericanCoffee extends Coffee{
@Override
public String getName() {
return "美式咖啡";
}
}
public class LatteCoffee extends Coffee{
@Override
public String getName() {
return "拿铁咖啡";
}
}
public class CoffeeStore {
public Coffee orderCoffee(String type){
//声明coffee类型的变量,根据不同类型创建不同的coffee子类对象
Coffee coffee = null;
if ("american".equals(type)){
coffee = new AmericanCoffee();
}else if ("latte".equals(type)){
coffee = new LatteCoffee();
}else {
throw new RuntimeException("对不起,您所点的咖啡没有了");
}
System.out.println(coffee.getName());
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
分析:上面这种设计方法,每增加一种咖啡,都要去修改咖啡店的orderCoffee方法,不符合软件设计的开闭原则。如果使用工厂来生产对象,我们就应该只和工厂打交道。
简单工厂模式
public abstract class Coffee {
public abstract String getName();
public void addSugar(){
System.out.println("加糖");
}
public void addMilk(){
System.out.println("加奶");
}
}
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
public class LatteCoffee extends Coffee {
@Override
public String getName() {
return "拿铁咖啡";
}
}
/**
* 简单工厂模式
*/
public class SimpleCoffeeFactory {
public Coffee createCoffee(String type){
Coffee coffee = null;
if ("american".equals(type)){
coffee = new AmericanCoffee();
}else if ("latte".equals(type)){
coffee = new LatteCoffee();
}else {
throw new RuntimeException("咖啡没有了");
}
return coffee;
}
}
public class CoffeeStore {
public Coffee orderCoffee(String type){
SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
Coffee coffee = factory.createCoffee(type);
coffee.getName();
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
测试
public class Client {
public static void main(String[] args) {
CoffeeStore coffeeStore = new CoffeeStore();
Coffee coffee = coffeeStore.orderCoffee(" ");
System.out.println(coffee.getName());
}
}
分析:简单工厂用来处理创建对象细节,解除了CoffeeStore和Coffee的耦合,但同时又产生了CoffeeStore和SimpleCoffeeFactory工厂对象的耦合,还有Coffee商品和SimpleCoffeeFactory的耦合,如果后期再加新品种的Coffee,我们必须去修改SimpleCoffeeFactory的代码,违反了开闭原则。
优点:封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更容易扩展。
缺点:增加新产品时还是要修改工厂类的代码,违背了开闭原则
工厂方法模式
针对上例的缺点,使用工厂方法模式就可以完美解决,完全遵循开闭原则
工厂方法模式概念:定义一个用于创建对象的接口,让子类决定实例化那个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。
结构:工厂方法模式的主要角色
抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品
具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建
抽象产品:定义了产品的规范,描述了产品的主要特性和功能
具体产品:实现了抽象产品角色所定义的接口
//抽象工厂
public interface CoffeeFactory {
Coffee createCoffee();
}
//具体工厂
public class LatteCoffeeFactory implements CoffeeFactory{
@Override
public Coffee createCoffee() {
return new LatteCoffee();
}
}
//具体工厂
public class AmericanCoffeeFactory implements CoffeeFactory{
@Override
public Coffee createCoffee() {
return new AmericanCoffee();
}
}
//抽象产品
public abstract class Coffee {
abstract String getName();
void addMilk() {
System.out.println("加奶");
}
void addSugar(){
System.out.println("加糖");
}
}
//具体产品
public class LatteCoffee extends Coffee {
@Override
public String getName() {
return "拿铁咖啡";
}
}
//具体产品
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
public class CoffeeStore {
private CoffeeFactory factory;
public void setFactory(CoffeeFactory factory){
this.factory = factory;
}
public Coffee orderCoffee(){
Coffee coffee = factory.createCoffee();
coffee.addMilk();
coffee.addSugar();
System.out.println(coffee.getName());
return coffee;
}
}
public class Client {
public static void main(String[] args) {
CoffeeStore coffeeStore = new CoffeeStore();
coffeeStore.setFactory(new AmericanCoffeeFactory());
coffeeStore.orderCoffee();
coffeeStore.setFactory(new LatteCoffeeFactory());
coffeeStore.orderCoffee();
}
}
执行结果
加糖
加奶
美式咖啡
加奶
加糖
拿铁咖啡
分析:工厂方法,如果要增加一个具体产品时,只需要新建一个产品类和增加一个产品工厂类,不需要修改工厂类的代码了,这样就符合开闭原则了。
优点:用户只需要知道具体工厂的名称就可以得到所需要的产品,无须知道产品的具体创建过程,在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂代码进行修改,符合开闭原则
缺点:每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,增加了系统的复杂度
抽象工厂模式
前面介绍的工厂方法模式,考虑的是一类产品的生产,而在实际的工厂中,一般都是一个工厂生产好几种产品,如一个电子品牌设备厂家,会生产电脑、手机、平板等多品类设备。下面介绍解决此问题的抽象工厂模式
结构:
抽象工厂模式的主要角色:
抽象工厂:提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同级别的产品
具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建
抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品
具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系
//抽象产品类(手机)
public abstract class Phone {
abstract String getName();
}
//具体产品类(小米手机)
public class MiPhone extends Phone{
@Override
String getName() {
return "xiaomi miPhone";
}
}
//具体产品类(苹果手机)
public class IPhone extends Phone{
@Override
String getName() {
return "apple iphone";
}
}
//抽象产品类平板
public abstract class Pad {
abstract String getName();
}
//具体产品类(小米平板)
public class MiPad extends Pad{
@Override
String getName() {
return "xiaomi MiPad";
}
}
//具体产品类(苹果平板)
public class IPad extends Pad{
@Override
String getName() {
return "apple iPad";
}
}
//抽象工厂
public interface Factory {
Phone createPhone();
Pad createPad();
}
//具体工厂(小米工厂)
public class XiaoMiFactory implements Factory{
@Override
public Phone createPhone() {
return new MiPhone();
}
@Override
public Pad createPad() {
return new MiPad();
}
}
//具体工厂(苹果工厂)
public class AppleFactory implements Factory{
@Override
public Phone createPhone() {
return new IPhone();
}
@Override
public Pad createPad() {
return new IPad();
}
}
//商场订购产品
public class Market {
private Factory factory;
public void setFactory(Factory factory){
this.factory = factory;
}
public Phone orderPhone(){
Phone phone = factory.createPhone();
System.out.println(phone.getName());
return phone;
}
public Pad orderPad(){
Pad pad = factory.createPad();
System.out.println(pad.getName());
return pad;
}
}
//测试类
public class Client {
public static void main(String[] args) {
Market market = new Market();
market.setFactory(new AppleFactory());
market.orderPhone();
market.orderPad();
market.setFactory(new XiaoMiFactory());
//购买小米平板
market.orderPad();
//购买小米手机
market.orderPhone();
}
}
执行结果:
apple iphone
apple iPad
xiaomi MiPad
xiaomi miPhoneProcess finished with exit code 0
代理模式
介绍
由于某些原因需要给某对象提供一个代理控制对该对象的访问。这时,访问对象不适合或不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
应用场景
统一异常处理
mybatis使用的代理
spring aop
日志框架
分类
java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类是在java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
-
静态代理(静态代理代理类在编译期就生成)
-
动态代理(动态代理代理类是在java运行时动态生成)
- JDK动态代理
- CGLIB动态代理
代理模式分为三种角色:
抽象主题类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
真实主题类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象
代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
静态代理
案例:顾客买电脑的时候不是找联想公司厂家,而是找地方上的联想代理商
案例:火车站卖票
如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点,买票就方便多了。这个例子是典型的代理模式,火车站是目标对象,代售点是代理对象。类图如下
/**
* 抽象主题类
*/
public interface SellTickets {
public abstract void sellTickets();
}
/**
* 真实主题类
*/
public class TrainStation implements SellTickets{
@Override
public void sellTickets() {
System.out.println("火车站售票");
}
}
/**
* 代理类
*/
public class ProxyPoint implements SellTickets{
private TrainStation trainStation = new TrainStation();
@Override
public void sellTickets() {
System.out.println("代售点收取一些服务费用");
trainStation.sellTickets();
}
}
测试类
public class Client {
public static void main(String[] args) {
//创建代售点对象
ProxyPoint proxyPoint = new ProxyPoint();
proxyPoint.sellTickets();
}
}
执行结果
代售点收取一些服务费用
火车站售票Process finished with exit code 0
分析:从上面代码来看,测试类Client直接访问的是ProxyPoint类对象,也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强。
动态代理
jdk动态代理
java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。
代码如下
/**
* 抽象主题类
*/
public interface SellTickets {
public abstract void sellTickets();
}
/**
* 真实主题类
*/
public class TrainStation implements SellTickets {
@Override
public void sellTickets() {
System.out.println("火车站售票");
}
}
/**
* 获取代理对象的工厂类,在程序运行过程中动态在内部生成的类
*/
public class ProxyFactory {
//声明一个目标对象
private TrainStation station = new TrainStation();
//获取代理对象的方法
public SellTickets getProxyObject(){
//返回代理对象
/**
* 参数介绍
* ClassLoader loader, 类加载器,用于加载代理类,代理类是运行中动态生成的类,可以通过目标对象获取类加载器
* Class<?>[] interfaces,代理类实现的接口的字节码对象
* InvocationHandler h 代理对象的调用处理程序,是一个内部类
*/
SellTickets proxyInstance= (SellTickets)Proxy.newProxyInstance(
station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
/**
*
* @param proxy 代理对象。和proxyObject对象是同一个对象,在invoke方法中基本不用
* @param method:对接口中的方法进行封装的method对象
* @param args:调用方法的实际参数
* @return`返回值就是方法的返回值 sellTickets()的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代售点收取一点的服务费用(JDK动态代理)");
Object obj = method.invoke(station, args);
//执行目标对象的方法
return obj;
}
}
);
return proxyInstance;
}
}
public class Client {
public static void main(String[] args) {
//获取代理对象
//1.创建代理工程对象
ProxyFactory factory = new ProxyFactory();
//2.使用factory对象获取代理对象
SellTickets proxyObject = factory.getProxyObject();
//3.调用售卖方法
proxyObject.sellTickets();
}
}
执行结果
代售点收取一点的服务费用(JDK动态代理)
火车站售票Process finished with exit code 0
工作原理:
jdk动态代理本质上是使用JDK的Proxy类的newProxyInstance()方法来实现动态代理,实现步骤是
代理工厂类使用Proxy.newProxyInstance()方法获取代理对象
在newProxyInstance()中实现InvocationHandler接口重写invoke方法增强代理对象的所有方法
cglib动态代理
如果没有定义接口,如上例中如果没有定义SellTickets接口,只定义了TrainStation(火车站类)jdk动态代理就无法使用了,因为
newProxyInstance的参数需要接口的字节码。此时,实现动态代理就可以通过cglib代理实现。
cglib代理是第三方提供的包,它为没有实现接口的类提供代理,为jdk的动态代理提供了很好的补充。使用时需要引入依赖的jar包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
/**
* 真实主题类
*/
public class TrainStation {
public void sell() {
System.out.println("火车站售票");
}
}
/**
* 代理对象工厂,用来获取代理对象
*/
public class ProxyFactory implements MethodInterceptor {
//声明火车站对象
private TrainStation station = new TrainStation();
public TrainStation getProxyObject(){
//1.创建Enhancer对象,类似于JDK动态代理中的Proxy类
Enhancer enhancer = new Enhancer();
//2.设置父类的字节码对象
enhancer.setSuperclass(TrainStation.class);
//3.设置回调函数
enhancer.setCallback(this);
//4.创建代理对象
TrainStation proxyObject = (TrainStation) enhancer.create();
return proxyObject;
}
/**
* 代理方法实际执行的方法是intercept方法
* @param obj
* @param method
* @param args
* @param proxy
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("代售点收取一定的服务费用(CGLib代理)");
Object invoke = method.invoke(station, args);
return invoke;
}
}
public class Client {
public static void main(String[] args) {
//创建代理工程对象
ProxyFactory factory = new ProxyFactory();
//获取代理对象
TrainStation proxyObject = factory.getProxyObject();
//调用代理类卖票
proxyObject.sell();
}
}
cglib代理工作原理
本质上是动态生成一个要代理类的子类,子类重写要代理的类的所有不是 final 的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,实现步骤是:
通过
Enhancer
类,生成一个要代理类的子类实现MethodInterceptor接口的方法intercept在子类中增强所有方法
jdk动态代理和cglib动态代理的区别
- jdk代理的代理对象需要有接口,在jdk1.8以后效率普遍高于cglib代理
- 没有接口时jdk代理无法实现,需要使用第三方包的cglib实现动态代理
动态代理和静态代理的区别
静态代理代理类在编译期就生成,而动态代理代理类是在java运行时动态生成
静态代理中如果接口增加了一个方法,所有实现类和代理类都需要实现此方法,而动态代理的代理类中不需要单独实现此方法,因为所有的方法都被转移到处理器一个集中的方法中处理,在jdk动态代理中是在invoke方法中集中处理,在cglib动态代理中是intercept方法中集中处理。