目前在开发一个 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
对象,而自己分明没有开发过这么一个类。
第一次开发比较大的一个工程,希望多家多多指点~
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.