我是菜菜🥬,一个人菜瘾大的互联网从业者🎉
用功不求太猛,但求有恒🎈
看完本文📖
你将:
- 清楚多来源的数据如果做到结构统一
- 对模板方法模式有更多的了解
- 对简单工厂(虽说不是设计模式中的一种,但是用的还是挺多的)能有更深的理解
- 知道设计模式之间的组合使用
前言
本文主要是记录和分享我在做ETL的业务时解决多种不同来源的数据进行结构化统一的问题。本文涉及了23种设计模式中的责任链模式和模板方法模式。对这两种模式不太熟悉的同学可以看我之前写的相关文章:
👉模板方法模式👈 👉工厂模式👈
业务说明
在消息队列中,有各种不同平台的素材数据,但是每个平台的素材数据可能都稍有不同,我们需要将这些数据处理一下,最后输出统一的数据格式供下游进行计算处理;同时也要有良好的拓展性,以供我们后续拓展素材来源。
我画了下面这张图来帮助大家理解业务场景
数据抽取
首先数据抽取转换的结果是获得一个统一的数据结构的对象,而且是多种来源。所以我们可以使用工厂模式来进行解耦。这里我就使用简单工厂模式了,虽然它不是23种设计模式中的,但是用起来比较简单。
由于虽然是不同来源的数据,但是多多少少还是会有一些相同的属性的,我们可以将这些共同的属性统一进行设置,不同的属性再单独进行处理,这也符合我们的模板方法模式的应用场景。
具体类的设计如下:
准备工作
引入maven依赖:
1 2 3 4 5 6 7 8 9 10 11 12
| <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.78</version> </dependency> </dependencies>
|
本文主要用来讲解使用设计模式来解决实际业务,所以其他的依赖,诸如:kafka
、flink
等都没有引入
功能实现
MediaEnum
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| package enums;
import lombok.Getter;
@Getter public enum MediaEnum { UNKNOWN("未知", 0,"unknown"), FIRST_TRANSFORM("来源一", 1,"first_transform"), SECOND_TRANSFORM("来源二", 2,"second_transform"), THIRD_TRANSFORM("来源三", 3,"third_transform"); private String name; private int code; private String alias;
MediaEnum(String name, int code, String alias) { this.name = name; this.code = code; this.alias = alias; }
public static MediaEnum create(String name) { for (MediaEnum media : values()) { if (media.name.equals(name)) { return media; } } return UNKNOWN; } }
|
KafkaData
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| package entity;
import com.alibaba.fastjson.annotation.JSONField; import lombok.Data;
import java.time.LocalDateTime; import java.util.List; import java.util.Map;
@Data public class KafkaData {
private String title;
private String description;
private String author;
private List<String> tags;
private String mediaName;
private String ossPath;
@JSONField(name = "spider_time", format = "yyyy-MM-dd HH:mm:ss") private LocalDateTime spiderTime;
@JSONField(name = "raw_data") private Map<String, Object> rawData;
}
|
rawData
属性记录的是素材原始的数据,而其他属性其实都是从rawData
中提取出来的,只不过各个来源的数据都拥有这些属性,所以将其提取出来。
Material
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| package entity;
import lombok.Data;
import java.time.LocalDateTime; import java.util.HashMap; import java.util.List; import java.util.Map;
@Data public class Material {
private String title;
private String description;
private String author;
private List<String> tags;
private Integer media;
private String ossPath;
private LocalDateTime spiderTime;
private String md5;
private String cover;
private Integer duration;
private Integer width;
private Integer height;
private String format;
private Map<String, Object> expand = new HashMap<>(); }
|
TransformMetaData
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package meta;
import entity.KafkaData; import entity.Material; import enums.MediaEnum; import lombok.Data;
@Data public abstract class TransformMetaData {
public final Material getMaterial(KafkaData source) { if (source != null) { Material material = new Material(); material.setTitle(source.getTitle()); material.setDescription(source.getDescription()); material.setAuthor(source.getAuthor()); material.setTags(source.getTags()); MediaEnum mediaEnum = MediaEnum.create(source.getMediaName()); material.setMedia(mediaEnum.getCode()); material.setSpiderTime(source.getSpiderTime()); material.setOssPath(source.getOssPath()); this.fill(source, material); return material; } return null; }
public abstract void fill(KafkaData source, Material material); }
|
这里就用到了模板方法模式,将相同部分的代码放在抽象的父类的getMaterialBo
方法中,而将不同的代码放入不同的子类的fill
方法中实现
FirstTransform
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package meta;
import entity.KafkaData; import entity.Material;
public class FirstTransform extends TransformMetaData{ @Override public void fill(KafkaData source, Material material) {
} }
|
SecondTransform
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package meta;
import entity.KafkaData; import entity.Material;
public class SecondTransform extends TransformMetaData{ @Override public void fill(KafkaData source, Material material) {
} }
|
ThirdTransform
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package meta;
import entity.KafkaData; import entity.Material;
public class ThirdTransform extends TransformMetaData{ @Override public void fill(KafkaData source, Material material) {
} }
|
TransformFactory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| package meta;
import entity.KafkaData; import entity.Material; import enums.MediaEnum; import lombok.extern.slf4j.Slf4j;
import java.io.InputStream; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Set;
@Slf4j public class TransformFactory { private static Map<String, TransformMetaData> map = new HashMap();
private final static String PROPERTIES_NAME = "transform.properties";
static { Properties p = new Properties(); InputStream is = TransformFactory.class.getClassLoader().getResourceAsStream(PROPERTIES_NAME); try { p.load(is); Set<Object> keys = p.keySet(); for (Object key : keys) { String className = p.getProperty((String) key); Class clazz = Class.forName(className); TransformMetaData obj = (TransformMetaData) clazz.newInstance(); map.put((String) key, obj); } } catch (Exception e) { log.error("数据转换类加载失败", e); e.printStackTrace(); } }
public static Material getMaterial(KafkaData source) { if (source == null || source.getMediaName() == null) { log.error("source为空或source中不包含素材来源,data=[{}]", source); return null; } MediaEnum mediaSource = MediaEnum.create(source.getMediaName()); TransformMetaData transformMetaData = map.get(mediaSource.getAlias()); if (transformMetaData == null) { log.error("无法解析的素材来源:data=[{}]", source.getMediaName()); return null; } return transformMetaData.getMaterial(source); } }
|
然后我们需要在创建一个transform.properties
文件用来工厂类的初始化
1 2 3
| first_transform=meta.FirstTransform second_transform=meta.SecondTransform third_transform=meta.ThirdTransform
|
Client
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import entity.KafkaData; import entity.Material; import meta.TransformFactory;
public class Client { public static void main(String[] args) { KafkaData kafkaData = new KafkaData(); Material material = TransformFactory.getMaterial(kafkaData); } }
|
对于不同来源的素材,我们可以分别在对应的Transform
类下的fill
方法做处理;同时后期拓展的时候也很容易,只需要在transform.properyties
文件中添加相应的对应规则,properties的key命名可以通过TransformFactory
类的60行知晓,并且再添加相应的Transform
类继承TransformMetaData
类即可