Java进阶篇——设计模式

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

设计模式

一、代理模式

使用代理类对真实对象进行代理,包括真实对象方法的调用、功能的扩展等。访问的时候也只能访问到代理对象,既保护了真实对象同时可以在原始对象上进行扩展。类似于中介在卖家和买家之间的角色。

代理模式的角色主要有:抽象角色、真实角色、代理角色

1.静态代理

以张三到二手平台售卖二手电脑为例,张三为真实角色,二手平台为代理角色

抽象角色:

public interface User {
    void sell();//购买方法
}

真实角色:

@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserImpl implements User{

    private String name="张三";
    private String thing="二手电脑";

    @Override
    public void sell() {
        System.out.println(name+"要卖掉"+thing);
    }
}

代理角色:

@Component
public class UserProxy implements User {
    UserImpl user;

    @Override
    public void sell() {
        before();
        user.sell();
        after();
    }
    //扩展
    public void before(){
        System.out.println("包装了"+user.getThing());
    }
    public void after(){
        System.out.println("售后服务");
    }
}

测试:

@Component
public class StaticProxyTest {
    public static void main(String[] args) {
        new UserProxy(new UserImpl()).sell();
    }
}

看起来似乎很简单,只是加了一层代理类就实现了扩展功能,但实际上维护时非常困难的。如果用户此时新增了需求,要在平台上买东西。那么直接带来了大量的代码工作,效率很低。也就产生了动态代理。

2.动态代理

和静态代理不同的是,动态代理的代理类是spring为我们生成的。相当于mybatisplus和mybatis的区别。

代理类生成工具类

public class DynamicProxy {
    /*jdk动态代理
      代理类需要是接口实现类impl.getClass().getInterfaces(),接收代理实例也是用接口来接收
      通过反射Instance+拦截器Handler实现
      jdk自带的代理支持
    */
    public static Object jdkProxy(final Object impl){
        Object proxyInstance = Proxy.newProxyInstance(impl.getClass().getClassLoader(), impl.getClass()
                .getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object invoke=null;
                System.out.println("商品包装");
                invoke = method.invoke(impl, args);
                System.out.println("售后服务");
                return invoke;
            }
        });
        return proxyInstance;
    }

    /*CGlib动态代理
    通过对类继承来实现,无需接口实现
    第三方工具,基于ASM实现
    */
    public static Object CGlibProxy(final Object impl){
        Object proxyInstance = Enhancer.create(impl.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object invoke = null;
                System.out.println("商品包装");
                invoke = method.invoke(impl, objects);
                System.out.println("售后服务");
                return invoke;
            }
        });
        return proxyInstance;
    }

}

又或者可以实现对应的接口,以InvocationHandler举例

public class DynamicProxy implements InvocationHandler {
    private User user;
    
    public Object getProxyInstance(User user){
        this.user=user;
        return Proxy.newProxyInstance(user.getClass().getClassLoader(), user.getClass().getInterfaces(),this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object invoke = null;
        System.out.println("商品包装");
        invoke = method.invoke(user, args);
        System.out.println("售后服务");
        return invoke;
    }
}//实际使用中只需要调用getProxyInstance方法,丢入一个原始类即可

使用:

public class DynamicProxyTest {
    public static void main(String[] args) {
        UserImpl user = new UserImpl();
        User o = (User)DynamicProxy.jdkProxy(user);
        o.sell();
//        UserImpl o1 = (UserImpl)DynamicProxy.CGlibProxy(user);
//        o1.sell();
    }
}

3.Spring AOP

springaop便是动态代理实践的一个典例,不改变方法原有的代码,实现对方法功能的增强,使用aop之前,对aop相关的概念是一定要了解清楚的。

(1)aop相关概念

  • 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
  • 接入点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
  • 切入点(PointCut): 可以插入增强处理的连接点。
  • 切面(Aspect): 切面是通知和切点的抽象集合,一般以类的形式呈现。
  • 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。
(2)springaop注解开发

由于目前来说,注解开发是最简单快捷的,这里只介绍注解开发,我们只需要知道底层是用动态代理实现的即可。

  • 创建切面类和通知

    只需要在类上添加@Aspect注解

    @Aspect
    public class MyAspect {
        public void before(){
            System.out.println("方法执行前");
        }
        public void after(){
            System.out.println("方法执行后");
        }
    }
    
  • 创建并注入切点

    @Aspect
    public class MyAspect {
        //表示将com.amlia.service包下的所有的类的所有方法(任何参数)定义为切点
        @Pointcut("execution(* com.amlia.service.*.*(..))")
        public void pointCut(){}
        @Before("pointCut()")
        public void before(){
            System.out.println("方法执行前");
        }
        //也可以直接在通知上面定义切点
        @After("execution(* com.amlia.service.*.*(..))")
        public void after(){
            System.out.println("方法执行后");
        }   
    }
    
  • 除了before和after类型的通知外,还有其他类型

    @Before:方法执行前通知

    @After:方法执行后通知

    @Around:方法环绕通知

    @AfterReturning:方法返回后通知

    @AfterThrowing:方法错误抛出之后

    可以测试他们的执行顺序:

(3)aop的应用
  • 打印日志(方法执行前后打印参数方法名返回值或者调用关系等信息)

    日志级别:

    • OFF 关闭日志
    • FATAL 较严重的问题,高于ERROR
    • ERROR 打印错误信息
    • WARN 打印告警信息
    • INFO 打印日常信息
    • DEBUG 打印调试信息
    • TRACE 打印追踪信息
    • ALL 打印所有信息
  • 性能检测(方法执行前和方法执行后分别进行时间截取求差值)

  • 事务控制(抛出错误后进行事务回滚)

  • 权限控制(方法执行前检测用户是否有权限)

二、单例模式

单例模式顾名思义就是该类只能有一个实例,而且是被类自己创建的。外界不能访问该类的构造方法,因为他是私有的。这种模式为了解决单个类频繁的创建和销毁的情况或者说是某种单个实例的场景,比如只能有一个中国实例,并且很多框架底层都使用了单例模式,比如bean的生命周期中就有singleton单例。

单例模式有很多实现方式,为了适应不同种情况:

1.懒汉模式

客人点单了厨师才开始做菜,线程不安全,如果需要线程安全,单体架构下,在方法上加synchronize关键字即可,多体架构下,方法体内加分布式锁。

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}//构造方法私有
  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();//需要时加载——懒式加载
        }  
        return instance;  
    }  
}

2.饿汉模式

厨师提前做好菜,客人点了直接上菜。由于类加载过程中是阻塞等待机制,所以是线程安全的。缺点是产生了大量的"垃圾"对象,比较占用资源。

public class Singleton {  
    private static Singleton instance = new Singleton();//加载时初始化
    private Singleton (){}//构造方法私有
  
    public static Singleton getInstance() {  
        return instance;  
    }  
}

3.双重校验的单例模式

大部分情况下,懒汉饿汉已经满足了。但是如果要求多线程下安全且高性能,那么还有一个较为复杂的模式

public class Singleton {
    //volatile防止初始化指令重排,导致其他线程误以为初始化完成,空指针
    private volatile static Singleton singleton;排
    private Singleton (){}
    public static Singleton getSingleton() {
    if (singleton == null) {//提升性能
        synchronized (Singleton.class) {
            if (singleton == null) {//防止阻塞等待下实例化已经完成的情况
                singleton = new Singleton();
            }  
        }
    }
    return singleton;
    }
}

4.静态内部类单例模式

这种方法同样利用了类加载机制的线程安全,但是巧妙的规避了资源的浪费。他利用内部类静态域的加载特性(使用时加载)达到了懒汉饿汉结合的效果。

public class Singleton {
    private static class SingletonHolder {
    //静态内部类的静态域
    private static final Singleton INSTANCE = new Singleton();
    }  
    private Singleton (){}
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

三、观察者模式

观察者模式有点类似于发布订阅模式,是由一个主题和订阅他的观察者们组成的。观察者模式的客观定义是这样的:定义了对象之间的一对多的依赖,
这样一来,当一个对象改变时,它的所有的依赖者都会收到通知并自动更新。

也就是我们要向发布者提供一个主题接口,接口里有添加订阅者、移除订阅者方法,以及设置订阅消息方法和通知订阅者消息方法。

先看主题接口:这是一个实现了观察者最基本功能的主题接口,实际中可按业务场景增加。

public interface Topic {
    void register(Observer observer);//注册观察者
    void remove(Observer observer);//移除观察者
    void setMessage(String message);//设置信息
    void notifyObservers();//广播信息
}

主题的广播消息方法肯定是逐个调用观察者的某个更新消息方法来实现低耦合。那么最简单的实现里,观察者接口就只有一个更新方法:

public interface Observer {
    void update(String massage);//更新消息
}

接着来看,主题的实现类,我们以博主和他的订阅者为例。

public class Bloggers implements Topic{
    private List<Observer> subscribers = new ArrayList<>();
    private String message;
    @Override
    public void register(Observer observer) {
        subscribers.add(observer);
    }

    @Override
    public void remove(Observer observer) {
        subscribers.remove(observer);
    }

    @Override
    public void setMessage(String message) {
        this.message=message;
    }

    @Override
    public void notifyObservers() {
        for (Observer subscriber : subscribers) {
            subscriber.update(message);
        }
    }
}

由于博主—粉丝关系主动权在粉丝,粉丝可以决定是否订阅博主,那么订阅者中就要有订阅和取消订阅方法。

public class Subscriber implements Observer{
    private Bloggers blogger;
    private String message;
    @Override
    public void update(String message) {
        System.out.print("消息更新"+this.message);
        this.message=message;
        System.out.println("====>"+this.message);
    }
    //订阅博主
    public void subscribe(Bloggers blogger){
        this.blogger=blogger;
        this.blogger.register(this);
    }
    //取关博主
    public void takeOff(){
        blogger.remove(this);
        blogger=null;
    }
}

简单测试一下:

public class ObserverTest {
    public static void main(String[] args) {
        Bloggers blogger = new Bloggers();
        Subscriber subscriber = new Subscriber();
        subscriber.subscribe(blogger);
        blogger.setMessage("源码探究.md");
        blogger.notifyObservers();
    }
}

订阅者成功收到消息:

这是我们自己实现的观察者模式,实际上java为我们已经提供有观察者的api,直接使用即可。

可以大大简化我们的代码,并且底层带有多线程安全的处理。

//继承主题类
public class Bloggers extends Observable {
    private String message;

    public void setMessage(String message) {
        this.message=message;
        setChanged();
        notifyObservers(this.message);
    }
}
//实现观察者接口
public class Subscriber implements Observer {
    private Bloggers blogger;
    private String message;
    @Override
    public void update(Observable o, Object arg) {
        System.out.print("消息更新"+this.message);
        this.message=arg.toString();
        System.out.println("====>"+this.message);
    }
    //订阅博主
    public void subscribe(Bloggers blogger){
        this.blogger=blogger;
        this.blogger.addObserver(this);
    }
    //取关博主
    public void takeOff(){
        blogger.deleteObserver(this);
        blogger=null;
    }
}

测试类:

public class ObserverTest {
    public static void main(String[] args) {
        Bloggers blogger = new Bloggers();
        Subscriber subscriber = new Subscriber();
        subscriber.subscribe(blogger);
        blogger.setMessage("源码探究.md");
    }
}

四、工厂模式

工厂模式顾名思义就是一个工厂,一个生产类实例的工厂。可以对用户屏蔽底层的实现细节,直接根据实例的标志名获取到对应的实例。

1.简单工厂模式

简单工厂就是生产一系列具有相同点或者相似性的实例,比如获取不同种类的汽车,就可以根据汽车的名字获取到汽车的实例。
官方术语是这样定义的:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

也就是两大特点:1.创建对象(目的) ,2.由子类创建(方法),来上代码:

我们以一个做菜机举例,该机器可以做全国各地的菜。用户只需要报出菜名就可以拿到对应的菜品。

比如一个番茄炒蛋类:这里只举一个例子,其他不做赘述。

public class EggsWithTomatoes implements Dishes{
    private List<Egg> eggs;
    private List<tomato> tomatoes;
    public EggsWithTomatoes(){
        prepare();
        add();
        exit();
    }
    public void prepare(){
        sout("起锅烧油...");
    }
    public void prepare(){
        sout("加入西红柿");
        Thread.sleep(10000);
        sout("加入番茄");
    }
    //实现dishes接口,重写出锅方法。
    @override
    public void exit(){
        sout("番茄炒蛋出锅...");
    }
}

然后我们需要一个工厂类,接收用户的要求,然后做出对应的菜品

public class DishesFactory {
    
    public Object cooking(String dishesName){
        if(dishesName==null)
            return null;
        else if(dishesName.equals("番茄炒蛋"))
            return new EggsWithTomatoes();
        else if(dishesName.equals("辣椒炒肉"))
            return new MeatWithChili();
        ...
    }
}

这种情况看似没有问题,但是当用户越来越多,甚至有云南的游客点了一份油炸竹虫,那么造成机器的负担越来越大,代码就很冗余,
出现大量的if-else。当然,我们可以对这些所有的菜分品系,比如粤菜、陕菜、鲁菜等等,可以实现一个抽象接口,各种菜系的机器去实现该接口。就可以有效减少机器负担。

2.抽象工厂模式

其实上面这种分类方式并不是很合理,想象一下,我想去点一个菜,我还要事先知道这个菜是什么品系,然后去对应的机器点,
那么能不能将这个细节也屏蔽掉呢,也就是我们有一个点菜系统,可以对用户点的菜分类然后去对应的机器做。再抽象一些就是,
我们根据一些标签或者字符获得对应的做菜机实例,然后使用对应的做菜机。再抽象一些就是抽象工厂模式。官方的定义是这样的:
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。在我理解看来,就是生产简单工厂的工厂。

比如我们已经有了陕菜工厂、粤菜工厂、鲁菜工厂。

那么抽象工厂就是这样的。

public class FactoryProducer {
   public static AbstractFactory getFactory(String cuisine){
      if(cuisine.equalsIgnoreCase("陕")){
         return new SHANFactory();
      } else if(cuisine.equalsIgnoreCase("粤")){
         return new YUEFactory();
      }
      ....
      return null;
   }
}

当然,这时候就要求这些工厂类都去实现一个共同的接口,使用多态来调用实例方法。

五、建造者模式

建造者模式也是为了建造一个实例,相比工厂模式来说,建造者模式的实例更加复杂一些,复杂到啥程度呢,复杂到实例可以进行拆分,
而且建造者模式注重的就是这些拆分子实例的组装顺序,就像一栋房子,有不同的沙发实例、电视实例、门实例等等,不同的组装代表着不同的房子实例。

官方的定义是这样的:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

建造者模式中有两个角色:工人和总工(建造者和导演),工人负责生产对应的家具,总工负责对子实例进行组合和搭配产生最终的实例。

多说无益,上代码:

还是以做菜机器举例,针对一些选择困难症的客户,上面的餐馆打算推出套餐来满足他们的需要,那么实际上重视的就是组合而不是生产,那么就可以使用建造者模式。

首先我们需要一个食品接口:

public interface Dish {
    int getPrice();//获取价格
    String getName();//获取名字
}

实现了食品接口的实体类,这里只展示一个:

public class CokeCola implements Dish{
    @Override
    public int getPrice() {
        return 3;
    }

    @Override
    public String getName() {
        return "可口可乐";
    }
}

以及一个建造者:这里的建造者实际上就是已经点的餐品或者说是点餐器

public class Combo {
    private List<Dish> combo = new ArrayList<>();
    public void addDish(Dish dish){//添加餐品
        combo.add(dish);
    }
    public int getSumPrice(){//获取总价格
        int sum= 0;
        for (Dish dish : combo) {
            sum+=dish.getPrice();
        }
        return sum;
    }
    public void print(){//打印套餐
        System.out.print("套餐中有:");
        for (Dish dish : combo) {
            System.out.print(dish.getName()+";");
        }
    }
}

最后就是导演——套餐组合器,它可以提供多种套餐的组合

public class DishesBuilder {
    private Combo combo = new Combo();
    public void prepareEggCola(){//番茄炒蛋+可乐
        combo.addDish(new EggsWithTomatoes());
        combo.addDish(new CokeCola());
        System.out.println("总价格是:"+combo.getSumPrice());
        combo.print();
    }
    public void prepareEggJuice(){//番茄炒蛋+果汁
        combo.addDish(new EggsWithTomatoes());
        combo.addDish(new FruitJuice());
        System.out.println("总价格是:"+combo.getSumPrice());
        combo.print();
    }
    ...
}

最后测试一下:

public class BuliderTest {
    public static void main(String[] args) {
        DishesBuilder dishesBuilder = new DishesBuilder();
        dishesBuilder.prepareEggCola();
    }
}

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java