Java进阶篇——设计模式

设计模式

一、代理模式

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

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

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();
    }
}

六、原型模式

原型模式同样是一种创建型设计模式,它提供了一种快速创建已有对象的方式,可以抛开构造函数创建对象。
比如有一个复杂构造函数的对象,想要构造他十分困难,但是构造的场景又很多,比如一道佛跳墙。那么就可以构造一次,
之后采用原型模式复制产生新的对象。其原理是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,
则采用这种模式。

他的官方定义是这个:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
通俗了讲就是利用一个原型,无限制的复制与他相似的新对象。

1.浅拷贝

我们还是以餐厅这个例子举例,这家餐馆想把特色菜系做成模型展示给用户,但是用户点餐时肯定不能把"模型"给用户而是重新做一份,
那么这份新菜就可以完全按照模型的思路去重新做一遍,而不用厨师从头思考材料配比。那么重新复制一份菜的工作就可以使用原型模式来做。

来看代码,代码实现是非常简单的,只需要一个克隆工厂提前存好对象实例,再需要的时候返回他的克隆对象。

public class DishesCache {
    private HashMap<String,AbstractDishes> hashMap = new HashMap();
    //添加一个菜系模型
    public void addDishes(AbstractDishes dishes){
        if(EggsWithTomatoe.class.isInstance(dishes))
            hashMap.put("eggs",dishes);
        else if(MeatWithChili.class.isInstance(dishes))
            hashMap.put("meat",dishes);
    }
    //获取一个菜系模型
    public Dishes getDishes(String type) throws CloneNotSupportedException {
        if(type.equals("eggs"))
            return (AbstractDishes) hashMap.get("eggs").clone();
        else if(type.equals("meat"))
            return (AbstractDishes)hashMap.get("meat").clone();
        else return null;
    }
}

底层代码还是复用原来的番茄炒蛋和辣椒炒肉。只是克隆对象需要实现克隆接口,所以我又加了一层抽象类来实现克隆接口。
即为AbstractDishes

那么可以直接测试一下

public class PrototypeDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        DishesCache dishesCache = new DishesCache();
        EggsWithTomatoe tomatoe1 = new EggsWithTomatoe();
        dishesCache.addDishes(tomatoe1);
        Dishes tomatoe2 = dishesCache.getDishes("eggs");
        System.out.println(tomatoe1.hashCode());
        System.out.println(tomatoe2.hashCode());
    }
}

发现实际输出的两个对象是不一样的

但这样真的可行么?我们常听说原型模式有深拷贝和浅拷贝,但是这明显是另一个对象了,似乎已经是深拷贝了。

我们点开源码来看看,发现这个克隆方法调用的是底层的c++代码,但是我们可以看他的注释

大概意思是说clone方法对于深层的引用对象,系统只实现了引用替换。如果一个类含有可变的引用对象,需要对克隆进行重写。
也就是如果该对象里含有对其他对象的引用,那么拷贝对象和被拷贝的对象的引用将指向同一个对象。我们可以测试一下,
向番茄炒蛋类中加一个鸡蛋类的引用,然后打印他的hashcode,会发现是一样的

这当然是我们所不期望的,如何解决这种浅拷贝呢?

2.深拷贝

1)多层级克隆

多层级克隆很容易理解,深层对象只进行复制,那我就重写clone,让深层对象也实现clone接口,对他也进行克隆。

比如,我们在番茄炒蛋类里新加入了对番茄类和对鸡蛋类的引用,那我们就对鸡蛋对象也进行克隆,然后再set进克隆对象中就可以

@Override
protected Object clone() throws CloneNotSupportedException {
    EggsWithTomatoe eggsWithTomatoe = (EggsWithTomatoe) super.clone();
    eggsWithTomatoe.setEgg((Egg) eggsWithTomatoe.getEgg().clone());
    return eggsWithTomatoe;
}

这种解决办法虽然简单,但是却比较冗杂。当引用关系比较复杂之后,clone方法体也将会越来越庞大,维护性和可读性都会大大降低,有没有更高级一点的方法呢?

2)序列化

序列化解决浅拷贝问题其实很容易理解,他是将对象所有相关的引用全部"打包"到流中,取得时候再反序列化提取。
需要注意的是,所有相关的引用类都要实现Serializable接口。

@Override
protected Object clone() throws CloneNotSupportedException {
    try {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        objectOutputStream.writeObject(this);//写入流
        ByteArrayInputStream stream = new ByteArrayInputStream(outputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(stream);
        EggsWithTomatoe eggsWithTomatoe = (EggsWithTomatoe) objectInputStream.readObject();//读出流
        return eggsWithTomatoe;
    } catch (IOException e) {
        throw new RuntimeException(e);
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    }
}

测试出来同样是不同对象:

七、适配器模式

适配器模式顾名思义就是一个适配器,他将接口转化为合适的模样,官方是这样定义的:将一个类的接口转换成客户希望的另外一个接口。
适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。比如我们小时候玩的小霸王游戏机,
那时候游戏都是磁带的形式,但是电脑不能读取磁带,这时候游戏机就是适配器。或者说耳机就是人和电脑的适配器。
java中常见的"加一层"其实就是适配器原理,比如jdbc、springcloud stream等。

我们来举个例子,依旧是上面的菜店。菜店有各种炒菜机器,煮东西的机器以及炒菜的机器等,那么我们怎么让炒菜机器也可以煮东西呢?

我们先建一个煮接口和一个炒接口:

public interface Boil {
    void boil();//煮菜
    void over();
}
public interface Fried {
    void fried(String type);//炒菜
    void over();//出锅
}

煮菜类还是保留原样,直接实现接口重写方法即可,但是炒菜机器如果想要煮菜,就需要内嵌一个适配器。

适配器是这样的:也就是实现炒菜接口但是调用煮菜方法。

public class BoilAdapter implements Fried{
    private Boil boilDishes;

    @Override
    public void fried(String type) {
        boilDishes= new BoilMachine();
        boilDishes.boil();
        boilDishes.over();
    }

    @Override
    public void over(){}
}

那么炒菜机如何装配适配器呢,同样是内嵌一个适配器,然后在接收到煮菜请求时初始化适配器,调用适配器方法煮菜。

public class FriedMachine implements Fried{

    private BoilAdapter boilAdapter;
    @Override
    public void fried(String type) {
        if(type.equals("番茄炒蛋")){
            System.out.println("翻炒中...");
            over();
        }
        else {//使用适配器煮饭
            boilAdapter = new BoilAdapter();
            boilAdapter.fried(type);
        }
    }

    @Override
    public void over() {
        System.out.println("番茄炒蛋出锅!");
    }
}

简单测试一下:

public class FriedDemo {
    public static void main(String[] args) {
        FriedMachine eggsTomatoe = new FriedMachine();
        eggsTomatoe.fried("番茄炒蛋");
        eggsTomatoe.fried("八宝粥");
    }
}

发现炒菜机既可以炒菜也可以煮菜

八、桥接模式

桥接模式是把实体类和抽象类剥离开来,使其可以独立实现某些功能、自主变化。什么是抽象类和实体类呢,通俗来讲,
抽象类就是实现了抽象接口的类,实体类是具体执行功能的类。在一般的开发中,这两者是合二为一的,比如一个炒菜机实现了炒菜接口。
那么他是一个炒菜抽象类,但同时,炒菜机也能炒出美味的饭菜,他也是实体类。

官方的定义是这样的:将抽象部分与实现部分分离,使它们都可以独立的变化。

我们还是以炒菜机举例。我们有一个做饭接口,和两个实现了他的抽象类,炒菜机和煮饭机。
我们希望最终提供给客户的需要根据实际需要动态的做出美味的饭菜(炒的或者煮的),这就很符合桥接模式的定义。

首先我们看一下做饭接口和抽象类

public interface Cooking {
    void cooking();
    void over();
}
public class BoilMachine implements Cooking{
    @Override
    public void cooking() {
        System.out.println("煮饭中");
        over();
    }

    @Override
    public void over() {
        System.out.println("稀饭出锅");
    }
}
public class FriedMachine implements Cooking{
    @Override
    public void cooking() {
        System.out.println("翻炒中");
        over();
    }

    @Override
    public void over() {
        System.out.println("炒菜出锅");
    }
}

然后需要一个将实现类和抽象类搭接起来的桥接器,桥接器中需要有和做饭接口一样的抽象方法,称之为桥接口,也就是模拟接口实现

public abstract class AbstractMachine {
    private Cooking cooking;
    public AbstractMachine(Cooking cooking){
        this.cooking = cooking;
    }

    public Cooking getCooking(){
        return cooking;
    }
    public abstract void cooking();
}

实体类不能实现接口,所以只能去继承抽象桥接类。从而实现cooking方法

public class CookingMachine extends AbstractMachine{
    public CookingMachine(Cooking cooking){
        super(cooking);
    }
    @Override
    public void cooking() {
        getCooking().cooking();
    }
}

简单测试一下:

public class CookingDemo {
    public static void main(String[] args) {
        CookingMachine machine = new CookingMachine(new BoilMachine());
        machine.cooking();
        CookingMachine machine1 = new CookingMachine(new FriedMachine());
        machine1.cooking();
    }
}

九、过滤器模式

过滤器模式我们在web开发中见的比较多了,很多时候用来做权限校验、请求处理等。其实就是把接收到的对象、
请求种种按照一种或者多种"标准"进行筛选。符合标准的目标将会从过滤器网中出去。

官方是这样定义的:过滤器模式(Filter Pattern)或标准模式(Criteria Pattern)是一种设计模式,
这种模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。
这种类型的设计模式属于结构型模式,它结合多个标准来获得单一标准。

依旧来到我们的饭店,饭店为了更好的服务客户,进购了一大批车厘子。但是车厘子有大有小、有红有粉。
那么就要筛选出又大又红的给客户,其他的留着自己吃。

首先我们需要一个车厘子类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Cherries {
    private String size;//尺寸
    private int level;//颜色等级
}

一个过滤器接口和实现了过滤器接口的过滤器类:

public interface CherriesFilter {
    List<Cherries> filter(List<Cherries> list);
}
//颜色过滤器
public class ColorFilter implements CherriesFilter{
    @Override
    public List<Cherries> filter(List<Cherries> list) {
        List<Cherries> collect = list.stream().filter(cherries ->
                cherries.getLevel() > 4).collect(Collectors.toList());
        return collect;
    }
}
//尺寸过滤器
public class SizeFilter implements CherriesFilter{
    @Override
    public List<Cherries> filter(List<Cherries> list) {
        List<Cherries> collect = list.stream().filter(cherries ->
                cherries.getSize().length() > 3).collect(Collectors.toList());
        return collect;
    }
}

最后测试一下:

public class FilterDemo {
    public static List<Cherries> list;
    public static void main(String[] args) {
        init();
        List<Cherries> list1 = new SizeFilter().filter(list);
        List<Cherries> list2 = new ColorFilter().filter(list1);
        for (Cherries cherries : list2) {
            System.out.println(cherries.toString());
        }
    }

    public static void init() {
        list = new ArrayList<>();
        list.add(new Cherries("JJ",5));
        list.add(new Cherries("JJ",4));
        list.add(new Cherries("JJ",3));
        list.add(new Cherries("JJJJ",1));
        list.add(new Cherries("JJJJ",5));
        list.add(new Cherries("JJJJ",3));
        list.add(new Cherries("JJJ",8));
        list.add(new Cherries("JJJ",7));
    }
}

最后只有一颗合格,先上给客人吧~

posted @ 2023-01-29 17:24  姬如乀千泷  阅读(135)  评论(0)    收藏  举报