Python中的工厂方法

5 投票
2 回答
5096 浏览
提问于 2025-04-17 06:19

我正在做一个小项目:

https://github.com/AndreaCrotti/project-organizer

简单来说,这个项目的目的是让管理不同的项目变得更简单。

其中一个很有用的功能是能自动识别我正在做的项目类型,这样就可以正确设置一些命令。

目前我使用了一个叫“match”的类方法和一个检测函数,这个检测函数会遍历各种“match”。

我相信可能有更好的设计方案,但我找不到。

有什么想法吗?

class ProjectType(object):
    build_cmd = ""

    @classmethod
    def match(cls, _):
        return True


class PythonProject(ProjectType):
    build_cmd = "python setup.py develop --user"

    @classmethod
    def match(cls, base):
        return path.isfile(path.join(base, 'setup.py'))


class AutoconfProject(ProjectType):
    #TODO: there should be also a way to configure it
    build_cmd = "./configure && make -j3"

    @classmethod
    def match(cls, base):
        markers = ('configure.in', 'configure.ac', 'makefile.am')
        return any(path.isfile(path.join(base, x)) for x in markers)


class MakefileOnly(ProjectType):
    build_cmd = "make"

    @classmethod
    def match(cls, base):
        # if we can count on the order the first check is not useful
        return (not AutoconfProject.match(base)) and \
            (path.isfile(path.join(base, 'Makefile')))


def detect_project_type(path):
    prj_types = (PythonProject, AutoconfProject, MakefileOnly, ProjectType)
    for p in prj_types:
        if p.match(path):
            return p()

2 个回答

3

我觉得这个看起来不错,不过我会做两个改进。

1- 使用元类来自动收集所有的项目类型,而不是手动列出它们。这样可以避免因为疏忽漏掉某个项目类型或者顺序搞错,比如:

class ProjectTypeManger(type):
    klasses = []
    def __new__(meta, classname, bases, classDict):
        klass = type.__new__(meta, classname, bases, classDict)
        meta.klasses.append(klass)
        return klass

    @classmethod
    def detect_project_type(meta, path):
        for p in meta.klasses:
            if p.match(path):
                return p()

class ProjectType(object):
    __metaclass__ = ProjectTypeManger
    build_cmd = ""

    @classmethod
    def match(cls, _):
        return None

2- match 方法应该返回对象本身,而不是返回真或假。这样一来,类就可以按照自己的方式配置对象,而且你还可以调用基类的 match 方法。例如,MakefileOnly 可以从 AutoconfProject 继承,这样它就可以先检查基类是否有匹配的内容,不过我不太确定这种继承是否合理。

class MakefileOnly(AutoconfProject):
    build_cmd = "make"

    @classmethod
    def match(cls, base):
        ret = super(MakefileOnly, cls).match(base)
        if ret is not None:
            return ret
        if path.isfile(path.join(base, 'Makefile')):
            return cls()

        return None
5

这段话是在说,把工厂函数用作类的方法是个不错的选择。

一个可能的改进是让所有的类都继承自一个共同的父类,这个父类里可以有一个方法,专门处理detect_project_type里的所有逻辑。

也许可以像下面这样做:

class ProjectType(object):
    build_cmd = ""
    markers = []

    @classmethod
    def make_project(cls, path):
        prj_types = (PythonProject, AutoconfProject, MakefileOnly, ProjectType)
        for p in prj_types:
            markers = p.markers
            if any(path.isfile(path.join(path, x)) for x in markers):
                return p()

class PythonProject(ProjectType):
    build_cmd = "python setup.py develop --user"
    markers = ['setup.py']

class AutoconfProject(ProjectType):
    #TODO: there should be also a way to configure it
    build_cmd = "./configure && make -j3"
    markers = ['configure.in', 'configure.ac', 'makefile.am']

class MakefileOnly(ProjectType):
    build_cmd = "make"
    markers = ['Makefile']

撰写回答