likes
comments
collection
share

2.抽象工厂

作者站长头像
站长
· 阅读数 9

抽象工厂

2.抽象工厂

抽象工厂能创建一系列相关的对象,而无需指定具体类。顾名思义,他是一个中心工厂,而中心工厂中有许多其他工厂的情况。

在我的理解中,相当于客户如果需要订阅一套家具,他并不需要知道中心工厂是生产茶桌还是茶椅,他只需要将他需要的风格交给工厂,中心工厂自然会调配对应的产品给他。

结构实现

2.抽象工厂 1.抽象产品:定义产品的接口,可以定义多个抽象产品接口 2.具体产品:实现抽象产品接口,产品的具体实现。 3.抽象工厂:声明一组创建成品的接口,每个方法对应一个产品 4.具体工厂:实现抽象接口,创建一组具体产品的对象

示例说明

我们可以拿家具厂举例,此时一套家具可分为椅子和沙发两种,二者都有现代风和古典风椅子,现在家具厂要根据客户不同要求生成不同风格的套组。

类图分析:

2.抽象工厂

代码实现:

package org.itstack.demo.design.test;

import org.junit.Test;

import java.util.Scanner;

/**
 * @title: DemoTest
 * @date: 2024/3/29 14:13
 * @description:
 */


// 抽象产品
interface Chair {
    void showInfo();
}

// 具体现代风格椅子
class ModernChair implements Chair {
    @Override
    public void showInfo() {
        System.out.println("现代风 chair");
    }
}

// 具体古典风格椅子
class ClassicalChair implements Chair {
    @Override
    public void showInfo() {
        System.out.println("古典风 chair");
    }
}

// 抽象沙发接口
interface Sofa {
    void displayInfo();
}

// 具体现代风格沙发
class ModernSofa implements Sofa {
    @Override
    public void displayInfo() {
        System.out.println("modern sofa");
    }
}

// 具体古典风格沙发
class ClassicalSofa implements Sofa {
    @Override
    public void displayInfo() {
        System.out.println("classical sofa");
    }
}

// 抽象厂接口
interface FurnitureFactory {
    Chair createChair();
    Sofa createSofa();
}

// 具体现代风格家居工厂
class ModernFurnitureFactory implements FurnitureFactory {
    @Override
    public Chair createChair() {
        return new ModernChair();
    }

    @Override
    public Sofa createSofa() {
        return new ModernSofa();
    }
}

// 具体古典风格家居工厂
class ClassicalFurnitureFactory implements FurnitureFactory {
    @Override
    public Chair createChair() {
        return new ClassicalChair();
    }

    @Override
    public Sofa createSofa() {
        return new ClassicalSofa();
    }
}

public class DemoTest {
    @Test
    public void demo() {
        Scanner scanner = new Scanner(System.in);
        // 读取订单数量
        int N = scanner.nextInt();
        // 处理每个订单
        for (int i = 0; i < N; i++) {
            // 读取家具类型
            String furnitureType = scanner.next();
            // 创建相应风格的家居装饰品工厂
            FurnitureFactory factory = null;
            if (furnitureType.equals("modern")) {
                factory = new ModernFurnitureFactory();
            } else if (furnitureType.equals("classical")) {
                factory = new ClassicalFurnitureFactory();
            }
            // 根据工厂生产椅子和沙发
            Chair chair = factory.createChair();
            Sofa sofa = factory.createSofa();
            // 输出家具信息
            chair.showInfo();
            sofa.displayInfo();
        }
    }
}

如上,我们简单的实现了抽象工厂。当然,主要业务代码中我们也不会如此直白的套用以上公式,那我们就用 redis 集群来进项实例分析。

实例分析

当系统前期,QPS较低,系统压力不大的情况下,我们为了敏捷开发并不会花费太多时间去部署非常完善的系统。比如对于Redis的使用,前期使用单机环境就能满足需求。 而随着系统的发展,他所需要的负载能力也要越来越大,此时我们就需要在不影响之前业务的情况下,平滑的将将单机Redis过渡为集群环境。

这样就会有一下几个问题: 1.很多系统/服务需要一起升级Redis集群。 2.需要兼容 集群A 和集群B,避免出现问题 3.可能需要进行redis方法适配

问题说明

我们假设需要将不同系统下的Redis进行适配。 此时 EGM 系统 Redis 的接口如下:

方法名方法说明
String gain(String key)获取数据
void set(String key,String value)存放数据
void setEx(String key,String value,long timeout,TimeUnit timeunit)设置锁
void delete(String key)删除数据

此时 IIR 系统 Redis 接口如下:

方法名方法说明
String get(String key)获取数据
void set(String key,String value)存放数据
void setExpire(String key,String value,long timeout,TimeUnit timeunit)设置锁
void del(String key)删除数据

如上,两个系统的接口功能都是相同的,但是命名不同,以及对应的系统Redis不同,此时我们要部署集群并且适配两个系统,这个时候如何修改呢?

常规实现

interface CacheService {

    String get(final String key, int redisType);

    void set(String key, String value, int redisType);

    void set(String key, String value, long timeout, TimeUnit timeUnit, int redisType);

    void del(String key, int redisType);

}

class CacheServiceImpl implements CacheService {

    private RedisUtils redisUtils = new RedisUtils();

    private EGM egm = new EGM();

    private IIR iir = new IIR();

    public String get(String key, int redisType) {
        if (1 == redisType) {
            return egm.gain(key);
        }
        if (2 == redisType) {
            return iir.get(key);
        }
        return redisUtils.get(key);
    }

    public void set(String key, String value, int redisType) {
        if (1 == redisType) {
            egm.set(key, value);
            return;
        }
        if (2 == redisType) {
            iir.set(key, value);
            return;
        }
        redisUtils.set(key, value);
    }

    public void set(String key, String value, long timeout, TimeUnit timeUnit, int redisType) {
        if (1 == redisType) {
            egm.setEx(key, value, timeout, timeUnit);
            return;
        }
        if (2 == redisType) {
            iir.setExpire(key, value, timeout, timeUnit);
            return;
        }
        redisUtils.set(key, value, timeout, timeUnit);
    }

    public void del(String key, int redisType) {
        if (1 == redisType) {
            egm.delete(key);
            return;
        }
        if (2 == redisType) {
            iir.del(key);
            return;
        }
        redisUtils.del(key);
    }
}

/**
 * 测试
 */
public class ApiTest {
    @Test
    public void test_CacheService() {
        CacheService cacheService = new CacheServiceImpl();
        cacheService.set("token", "token", 1);
        String val = cacheService.get("token", 1);
        System.out.println("测试结果:" + val);
    }
}

通过以上代码,我们也能达到目标。他和简单工厂的实例一样,通过 if 判断来实现我们要生成哪种对象,并且调用方法即可。 但老问题又来了,当我们系统过多时,单个方法 if 判断都那么长了,更何况 redis 中有那么多方法提供调用,会使得后期维护会更加麻烦。我们就可以使用抽象工厂模式来进行优化重构

抽象工厂

有业务可以分析,集群A和集群B在部分方法上提供是不同的,因此需要做一个接口适配,而这个适配类就相当于工厂中的工厂,用于创建不同的服务抽象为统一接口做相同的业务。 这一部分又和动态代理十分相似,因此我们可以结合动态代理实现功能。 结构类图如下: 2.抽象工厂 代码如下:

package com.example.demo.factory.impl;

import com.example.demo.factory.ICacheAdapter;
import com.example.demo.systems.EGM;

import java.util.concurrent.TimeUnit;

/**
 * @title: EGMCacheAdapter
 * @date: 2024/3/29 16:00
 * @description: EGM集群 操作
 */
public class EGMCacheAdapter implements ICacheAdapter {

    private EGM egm = new EGM();

    public String get(String key) {
        return egm.gain(key);
    }

    public void set(String key, String value) {
        egm.set(key, value);
    }

    public void set(String key, String value, long timeout, TimeUnit timeUnit) {
        egm.setEx(key, value, timeout, timeUnit);
    }

    public void del(String key) {
        egm.delete(key);
    }

}
package com.example.demo.factory.impl;

import com.example.demo.factory.ICacheAdapter;
import com.example.demo.systems.IIR;

import java.util.concurrent.TimeUnit;

/**
 * @title: IIRCacherAdapter
 * @date: 2024/3/29 16:17
 * @description: IIR集群 Redis操作
 */
public class IIRCacherAdapter implements ICacheAdapter {

    private IIR iir = new IIR();

    public String get(String key) {
        return iir.get(key);
    }

    public void set(String key, String value) {
        iir.set(key, value);
    }

    public void set(String key, String value, long timeout, TimeUnit timeUnit) {
        iir.setExpire(key, value, timeout, timeUnit);
    }

    public void del(String key) {
        iir.del(key);
    }

}

适配接口:

package com.example.demo.factory;

import java.util.concurrent.TimeUnit;

/**
 * @title: CacheService
 * @date: 2024/3/29 15:31
 * @description: 让所有集群提供方,能在统一的方法名称下进行操作。
 */
public interface ICacheAdapter {

    String get(final String key);

    void set(String key, String value);

    void set(String key, String value, long timeout, TimeUnit timeUnit);

    void del(String key);

}

抽象工厂: 生成 CacheService 接口的动态代理对象,方法调用将被代理并委托给 CacheAdapter 实现类使用。

package com.example.demo.factory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * @title: JDKProxy
 * @date: 2024/4/8 10:40
 * @description: 抽象工厂类
 */
public class JDKProxy {

    /**
     * 创建动态代理对象
     * @param interfaceClass 泛型参数,生成代理对象的接口类型
     * @param cacheAdapter cacheAdapter对象
     * @return 代理类
     * @param <T> 泛型对象
     */
    public static <T> T getProxy(Class<T> interfaceClass, ICacheAdapter cacheAdapter) {
        InvocationHandler handler = new JDKInvocationHandler(cacheAdapter);
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class<?>[] classes = interfaceClass.getInterfaces();
        return (T) Proxy.newProxyInstance(classLoader, new Class[]{classes[0]}, handler);
    }
}

实际工厂: 根据方法来实现具体的方法

package com.example.demo.factory;

import com.example.demo.utils.ClassLoaderUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @title: JDKInvocationHandler
 * @date: 2024/4/8 10:42
 * @description: 代理工厂(用于动态代理)
 */
public class JDKInvocationHandler implements InvocationHandler {

    private ICacheAdapter cacheAdapter;


    public JDKInvocationHandler(ICacheAdapter cacheAdapter) {
        this.cacheAdapter = cacheAdapter;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return ICacheAdapter.class.getMethod(method.getName(), ClassLoaderUtils.getClazzByArgs(args)).invoke(cacheAdapter, args);
    }
}

测试类:

public class Test {
    @org.junit.jupiter.api.Test
    public void test() {
        ICacheService proxy_egm = JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());
        proxy_egm.set("123", "123");

    }
}

以上,我们便利用抽象工厂模式完成了本次设计,并且留下了以后可以增加的空间,尽管相比于之前的代码量多了一点,但是技术上给别人留下了深刻的方式,这又何尝不是一种装呢(笑)!

总结

抽象工厂,可以解决一种产品,但存在多个不同类型的产品情况下,接口选择的问题。但是当业务不断的扩展时,可能会造成类实现上的复杂度。但这并不意味这是个重大问题,我们可以引入其他设计模式和代理类以及自动生成加载来减低这个缺点

参考资料

  1. 设计模式 | 菜鸟教程 (runoob.com)
  2. 《深入设计模式》 PDF版
  3. 付政委. 重学Java设计模式(全彩). 电子工业出版社, 2021.