目前在开发一个 AI 相关业务的框架。假设用户需要一个 DataLoader 用于实现从硬盘读取数据,其中每一组数据中包含一张图像数据和一份标签数据。
由于无法预知用户所需的图像和标签格式,因此在基类中我将 load_image 和 load_label 定义为抽象方法:
from abc import ABC class BaseLoader(ABC): def __init__(self, image_paths, label_paths) self.image_paths = image_paths self.label_paths = label_paths @abstractmethod def load_image(self, index): pass @abstractmethod def load_label(self, index): pass 用户需要在派生类中自己去定义具体的图像读取方式,比如用户自己写了一个 JpegLoader 类用来读取 jpeg 图像,另一个 TiffLoader 类用来读取 tiff 图像:
@IMAGE_LOADERS.register('jpeg') class JpegLoader(BaseLoader): def load_image(self, index): # load jpeg image @IMAGE_LOADERS.register('tiff') class TiffLoader(BaseLoader): def load_image(self, index): # load tiff image 类似地,也有不同的派生类用来定义标签数据的读取方式:
@LABEL_LOADERS.register('json') class JsonLoader(BaseLoader): def load_label(self, index): # load json label @LABEL_LOADERS.register('yaml') class YamlLoader(BaseLoader): def load_label(self, index): # load yaml label 在运行阶段,用户通过配置文件从 IMAGE_LOADERS 和 LABEL_LOADERS 注册器中分别选取要调用的派生类,并临时通过菱形继承的方式生成一个新的派生类:
def create_data_loader(image_type, label_type): image_loader_cls = IMAGE_LOADERS[image_type] label_loader_cls = LABEL_LOADERS[label_type] class RuntimeLoader(image_loader_cls, label_loader_cls): pass return RuntimeLoader() 以上是我针对这种需求想到的一个设计方案。我的问题是:
1 、当需要解耦一个类中的不同功能组件时,让用户自己去定义派生类(比如上面例子中的 JpegLoader 和 JsonLoader),并由菱形继承的方式再去生成一个新的派生类,这是不是一种合理的设计模式?我总感觉怪怪的,因为一般的对外接口都是提供一个抽象基类,让用户继承自这个基类去定义自己的子类,没见过用这种菱形继承的方式;
2 、最终用来实例化的类是 RuntimeLoader,并不是用户自己去定义的派生类中的任何一个,而且整个 create_data_loader 函数对用户也是封闭的,这会不会让用户觉得很迷惑?比如在 debug 的时候会发现真正的 dataloader 是一个 RuntimeLoader 对象,而自己分明没有开发过这么一个类。
第一次开发比较大的一个工程,希望多家多多指点~
