【问题标题】:How do I design this procedural code as class based (object oriented)?如何将此程序代码设计为基于类(面向对象)?
【发布时间】:2015-10-01 10:53:02
【问题描述】:

我是一名初中级自学 Python 开发人员,

在我完成的大多数项目中,我可以看到以下过程重复。我没有任何外部家庭代码经验,我认为下面的代码不是那么专业,因为它不可重用,并且看起来它不是在一个容器中组合在一起,而是在不同模块上松散耦合的功能。

def get_query():
    # returns the query string
    pass

def make_request(query):
    # makes and returns the request with query
    pass

def make_api_call(request):
    # calls the api and returns response
    pass

def process_response(response):
    # process the response and returns the details
    pass

def populate_database(details):
    # populates the database with the details and returns the status of population
    pass

def log_status(status):
    # logs the status so that developer knows whats happening
    pass

query = get_query()
request = make_request(query)
response = make_api_call(request)
details = process_response(response)
status = populate_database(details)
log_status(status)

如何将此过程设计为基于类的设计?

【问题讨论】:

  • “它不可重复使用” - 你为什么这么认为?一些简短的、单一用途的函数肯定比试图将所有东西组合成一个更可重用。 “不同模块上的松散耦合函数” - 它们看起来在同一个模块中,松散耦合通常被认为是一件好事!
  • @jonrsharpe 对于分布在某些模块中的功能,我总是发现很难跟踪模块,复制代码并使其在第二个项目的模块结构上的另一个项目上工作
  • 相关的、可重用的功能可能应该被打包到一个单独的模块中,但除此之外,你还不清楚为什么你认为你现在拥有的东西有问题。如果您将其直接转移到课程中,您最终会得到"two methods, one of which is __init__"
  • @jonrsharpe 我想这些课程很容易维护和调用,谢谢你的视频。
  • @jonrsharpe 我觉得你有点迂腐。有很多代码示例,其中方法(例如他/她的问题中的方法)将被包装在一个类中。这个平面文件完全可以按原样使用(我更喜欢这种方式),但是如果它也被写在一个类中,代码也是可以接受的。

标签: python oop class-design


【解决方案1】:

如果我理解正确,您希望这些函数组可以被重用。好的方法是使用这些方法创建抽象基类,如下所示:

from abc import ABCMeta

class Generic(object):
__metaclass__  = ABCMeta


    def get_query(self):
        # returns the query string
        pass

    def make_request(self, query):
        # makes and returns the request with query
        pass

    def make_api_call(self, request):
        # calls the api and returns response
        pass

    def process_response(self, response):
        # process the response and returns the details
        pass

    def populate_database(self, details):
        # populates the database with the details and returns the status of population
        pass

    def log_status(self, status):
        # logs the status so that developer knows whats happening
        pass

现在,每当您需要在项目中使用这些方法中的任何一个时,都可以从这个抽象类继承您的类。

class SampleUsage(Generic):

    def process_data(self):
        # In any of your methods you can call these generic functions
        self.get_query()

然后您可以创建对象以实际获得您想要的结果。

obj = SampleUsage()
obj.process_data()

【讨论】:

  • 用于定义抽象基类的元类
【解决方案2】:

你可能在这里有几个课程。仅举几例,QueryRequestResponseDatabaseLogger

您的一些函数可能映射如下:

  1. make_query -> Query.make() 构造函数或类方法
  2. make_request -> Request.make(query) 构造函数或类方法
  3. make_api_call -> Request.make_api_call()
  4. process_response -> Response.process()
  5. populate_database -> Database.populate()
  6. log_status -> Logger.status 考虑使用日志模块

您必须考虑您的应用程序并将其设计为协作对象的交互。这只是一个起点,以便您在类之间划分应用程序的功能。

其中一些类可能是单例,这意味着它们仅在应用程序开始时实例化一次,并在其他任何地方访问。 DatabaseLogger 适合这个角色。

以下是一些骨架定义:

class Query(object):
    @classmethod
    def make(cls, *args, **kwargs):
        pass

class Request(object):
    @classmethod
    def make(cls, query):
        pass

    def make_api_call(self, *args, **kwargs):
        # possibly return Response
        pass

class Response(object):
    def process_response(self):
        pass

class Database(object):
    _the_db = None

    @classmethod
    def get_db(cls):
        # Simple man's singleton
        if not cls._the_db:
            cls._the_db = Database()
        return cls._the_db

    def populate(self):
        pass

class Logger(object):
    def log(self):   
        # consider using logging module
        pass

【讨论】:

    【解决方案3】:

    我认为你的问题缺少的是目标感。您不会无缘无故地将完美的过程代码切换到面向对象的代码。根据原因,有几种方法可以做到这一点。由于这个问题很常见,因此有一些常见的技术由于某些常见原因而可以很好地工作。

    因此,假设您将主过程封装在一个对象中。您有什么需求?

    • 允许重新使用该过程,可能会覆盖某些部分?请参阅下面的模板方法模式。
    • 是否允许在运行时根据外部因素动态改变过程的行为?查看Strategy 模式。
    • 允许在运行时根据内部因素动态改变过程的行为吗?例如,如果某些请求可以将程序切换到“维护模式”?查看State 模式。

    我将只描述模板方法模式,它看起来最接近 Marty 的关注点。 为了便于解释,我将示例缩减为 3 个步骤,但我为您设置了 fully working example gist

    template method

    您想提供一种方法来重用该过程,同时允许覆盖一些明确定义的部分?让我们创建一个空的、填空样式的模板:

    class BaseRequestProcesor(object):
        def get_query(self):
            raise NotImplementedError()
    
        def process_query(self, query):
            raise NotImplementedError()
    
        def log_status(self, status):
            raise NotImplementedError()
    
        def process(self): # main procedure
            query = self.get_query()
            status = self.process_query(query)
            self.log_status(status)
    
        __call__ = process # allow "calling" the requestprocessor
    

    我们有我们的基本模板。让我们创建一些模板填充器:

    class DemoQueryReader(object):
        def get_query(self):
            return 'this is a query'
    
    class HelloQueryProcessor(object):
        def process_query(self, query):
            return 'Hello World, {}!'.format(query)
    
    class StdoutLogProcessor(object):
        def log_status(self, status):
            print(status)
    

    现在从我们想要的位构建一个完整的请求处理器。这就是拼凑在一起的地方:

    class DemonstrationProcessor(DemonQueryReader, HelloQueryProcessor, StdoutLogProcessor, BaseRequestProcessor):
        pass
    

    在控制台中演示:

    >>> from marty_example import DemonstrationProcessor
    >>> processor = DemonstrationProcessor()
    >>> processor()
    Hello World, this is a query!
    

    这是您可以构建的最迂腐的示例。你可以在有意义的时候提供默认实现(大多数情况下什么都不做)。如果有意义的话,您可以将覆盖组合在一起。

    关键是,您将流程设为模板,可以轻松覆盖所选细节,同时仍能控制整个工作流程。这是inversion of control 的一种形式。

    【讨论】:

      【解决方案4】:

      您还可以使用类名保存 Python 文件,或者您可以创建具有某些功能的外部模块,根据它们的功能将它们组织到模块中。有些模块只包含一个功能;其他将包含 很多

      【讨论】:

      • 我发现管理大量模块非常困难,这可能是由于命名不当。
      • 处理面向对象时会很容易。这实际上比在单个模块中完成所有操作更容易和更好,因为当import主模块时,不必要的功能将一起加载。尝试做一个层次结构,比如主模块和附加模块。
      • **哦,更重要的是,它还允许将插件制作为模块,甚至是文件夹。 _注意:我还没有理解您在顶部的问题。请在提问时更容易;) _
      • 多年后,当我认为他不是指实际面向对象编程时,我意识到自己犯了一个巨大的错误。大声呐喊。
      猜你喜欢
      • 2014-02-24
      • 2012-10-02
      • 1970-01-01
      • 1970-01-01
      • 2013-09-05
      • 1970-01-01
      • 2022-01-08
      • 2018-12-29
      • 1970-01-01
      相关资源
      最近更新 更多