一般编程问题。何时使用面向对象编程(OOP)?

3

我的程序需要做两件事情。

  1. 从网页中提取信息。

  2. 对网页进行操作。

然而,有许多网页,比如 Twitter 和 Facebook。

我该怎么办?

def facebookExtract():
    code here
def twitterExtract():
    code here
def myspaceExtract():
    code here
def facebookProcess():
    code here
def twitterProcess():
    code here
def myspaceProcess():
    code here

或者,我是否应该有某种类?何时建议使用类,何时建议仅使用函数?

6个回答

22

"我的程序需要做两件事情。"

当你以这种方式开始时,对象是看不见的。你的视角不对。

改变你的思考方式。

"我的程序处理东西(stuff)"

这是面向对象(OO)的思考方式。你的程序处理什么“东西”?定义这些东西,这些就是你的基本类。每种东西都有一个类。

"我的程序从各种来源获取东西"

每个来源都有一个类。

"我的程序展示这些东西"

通常这是一些访问这些东西的方法加上一些“报告”类,收集部分东西来展示它们。

当你开始定义“东西”而不是“做”的时候,你在进行OO编程。OO适用于所有东西,因为每个程序都涉及“做”和“东西”。你可以选择“做”的观点(可以是过程式或函数式),也可以选择“东西”的观点(面向对象)。


16
我最喜欢的经验法则是:如果你不确定(未言明的假设:“而且你是一个理性的人而不是一个狂热分子”;-),就创建并使用一些类。我经常发现自己将最初编写为简单函数的代码重构为类 - 例如,每当简单函数之间相互通信的最佳方法是使用全局变量时,这是一个代码气味,这强烈暗示系统的分解实际上并不好 - 并且通常以面向对象编程的方式进行重构是一个合理的修复方法。
Python是多范式的,但它的中心范式是OOP(就像C ++一样)。当面向过程或函数式方法(可能通过生成器等)对系统的某个部分最优时,通常很明显 - 例如,静态函数也是一种代码气味,如果你的类有任何实质性数量的那些,那么这就提示你重构以避免该要求。
因此,假设您对Python提供的所有范式都有深入了解 - 如果您仍然不确定,则表明您可能希望针对系统的该部分采用OOP!仅仅因为Python比支持函数式编程和类似编程的方式更加完整地支持OOP。
从您非常简洁的代码中可以看出,每个提取/处理对应该一起,并且可能需要通信状态,因此具有提取和处理方法的一小组类似乎是自然适合的。

1
狂热是可以的,只要我不必听它就好了 ;) - Matthew Scharley
2
所有的Python内容都是对象,包括函数。此外,过早地抽象化是万恶之源。 - hasen
不确定你在说什么“狂热主义”(希望修正你拼错的单词也不算?!),@Matthew,我是一名多范式编程的圣骑士,我只是不相信与政府对抗。所以,在范式之间犹豫不决时,在Python中我选择面向对象编程,就像在O'Caml中我选择函数式编程一样——它们都是多范式语言,但很明显O'Caml主要是函数式的(加上一些面向对象的东西),而Python大多是面向对象的(加上一些函数式的东西)。这有什么狂热的地方吗?! - Alex Martelli
@hasen,Python 中的 if 语句(例如)不是一个对象;这与 Smalltalk 非常不同,在 Smalltalk 中没有 "if 语句",而是 Booleans 的一个方法,因此是一个对象。Smalltalk 是狂热的面向对象编程(你必须疯了才会用其他范式编写 Smalltalk!),Python 只是实用主义的面向对象编程(在 Python 中有很多情况下,非面向对象的方法,特别是FP方法,显然更可取)。关于抽象,参见 http://us.pycon.org/2009/conference/schedule/event/75/ -- 我认为我在那里讨论了这个问题。;-) - Alex Martelli
2
过早的抽象是万恶之源。 :-) - foosion
显示剩余4条评论

3

由你决定。个人而言,在使用Python编程时,我会尽量避免使用Java-style类,而是使用字典和/或简单对象。

例如,在定义了这些函数(你在问题中定义的那些函数)之后,我会创建一个简单的字典,可能像这样:

{ 'facebook' : { 'process' : facebookProcess, 'extract': facebookExtract }, 
 ..... 
}

或者,更好的方法是使用内省来自动获取进程/提取函数:
def processor(sitename):
    return getattr(module, sitename + 'Process')

def extractor(sitename):
    return getattr(module, sitename + 'Extractor')

其中module是当前模块(或具有这些函数的模块)。

要将此模块作为对象获取:

import sys
module = sys.modules[__name__]

假设通用的主函数像这样执行:

    根据输入内容确定网站名称
    获取该网站的抽取函数
    获取该网站的处理函数
    调用抽取函数,然后再调用处理函数

“Java-style class”是什么意思? - naught101

3
将常见的内容放在一个函数中。一旦你尽可能地削减了代码,就建立一个机制,根据每个网站分支到相应的函数。
这可以用Python的if/else语句来实现,但如果你有许多这样的函数,你可能需要更优雅的解决方案,例如 F = __import__('yourproject.facebookmodule') 这样可以让你将特定于Facebook的代码放在自己的区域中。由于你向__import__()传递了一个字符串,你可以根据你正在访问的网站在运行时修改它,然后在通用的工作代码中调用F函数。
更多信息请参见:http://effbot.org/zone/import-confusion.htm

1
我经常定义类来解决问题,原因有几个。下面我将模拟我的思考过程。我对混合面向对象模型和过程式风格没有任何顾虑,这往往更多地反映了你所在的工作社会而非个人信仰。如果其他维护者期望一个过程式外观的类层次结构,那么这通常是可行的。
(请原谅PHP语法)
  • 我正在开发策略,可以遵循通用模型。因此,您的任务可能涉及获取要处理的URL。如果您想大大简化外部逻辑并消除条件语句,则此方法有效。这表明我可以在gather()方法中使用DNRY。
  • // batch process method
    function MunchPages( $list_of_urls )
    {
        foreach( $list_of_urls as $url )
        {
            $muncher = PageMuncher::MuncherForUrl( $url );
            $muncher->gather();
            $muncher->process();
        }
    }
    // factory method encaps strategy selection
    function MuncherForUrl( $url )
    {
        if( strpos( $url, 'facebook.com' ))
             return new FacebookPageMuncher( $url );
        if( ... ) 
             return new .... ;
    }
    // common tasks defined in base PageMuncher
    class PageMuncher 
    {
        function gather() { /* use some curl or what */ }
        function process() {}
    }
    class FacebookPageMuncher extends PageMuncher
    {
        function process() { /* I do it 'this' way for FB */ }
    }
    

  • 我正在创建一组理想情况下隐藏且最好是共享的例程。其中一个示例可能是拥有一个定义了任务常见工具方法的类。更具体的任务可以扩展该工具箱以开发自己的行为。
  • class PageMuncherUtils
    {
        static function begin( $html, $context )
        {
           // process assertions about html and context
        }
        static function report_fail( $context ) {}
        static function exit_retry( $context ) {}
    }
    // elsewhere I compose the methods in cases I don't wish to inherit them
    class TwitterPageMuncher
    {
        function validateAnchor( $html, $context )
        {
            if( ! PageMuncherUtils::begin( $html, $context )) 
                return PageMuncherUtils::report_fail( $context );
        }
    }
    

  • 我想要组织我的代码,以便向维护者传达更广泛的含义。考虑到如果我有一个远程服务需要接口,我可能会深入到其接口内部的不同API中,并且我希望将这些例程按照相似的主题进行分组。下面,我展示了一个示例,说明我喜欢如何定义一个类来定义常见常量,一个类来定义基本服务方法,以及一个更具体的天气警报类,因为警报应该知道如何刷新自己,它比天气服务本身更具体,但也利用了WeatherAPI常量。
  • class WeatherAPI
    {
        const URL = 'http://weather.net';
        const URI_TOMORROW = '/nextday/';
        const URI_YESTERDAY= '/yesterday/';
        const API_KEY = '123';
    }
    class WeatherService
    {
        function get( $uri ) {  }
        function forecast( $dateurl ) {  }
        function alerts( $dateurl )
        {
            return new WeatherAlert( 
                $this->get( WeatherAPI::URL.$date
                           ."?api=".WeatherAPI::API_KEY ));
        }
    }
    class WeatherAlert
    {
        function refresh() {}
    }
    // exercise:
    $alert = WeatherService::alerts( WeatherAPI::URI_TOMORROW );
    $alert->refresh();
    

    1

    在开发解决方案更快、结果更易读、易懂和易于维护时,使用面向对象编程是有意义的。

    在这种情况下,创建一个通用的 Extractor 接口/类,然后为 Twitter、MySpace、Facebook 等创建子类可能是有意义的,但这实际上取决于提取的特定于网站的程度。这种抽象的想法是隐藏这些细节。如果你能做到,那就有意义。如果不能,你可能需要另一种方法。

    通过良好的过程分解,也可以获得类似的好处。

    记住,在一天结束时,所有这些都只是工具。选择最适合特定工作的工具,而不是选择锤子,然后试图把一切都变成钉子。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接