8

我想编写一个模块(特定于框架),它将包装和扩展 Facebook PHP-sdk(https://github.com/facebook/php-sdk/)。我的问题是 - 如何以一种好的方式组织课程。

所以进入细节 - Facebook PHP-sdk 由两个类组成:

  • BaseFacebook - 包含 sdk 所做的所有事情的抽象类
  • Facebook - 扩展 BaseFacebook,并使用默认会话使用实现父抽象持久性相关方法

现在我要添加一些功能:

  • Facebook 类替换,与框架会话类集成
  • 运行 api 调用的速记方法,我主要使用(通过 BaseFacebook::api()),
  • 授权方法,所以我不必每次都重写这个逻辑,
  • 配置,从框架类中吸取,作为参数传递
  • 缓存,与框架缓存模块集成

我知道有些地方出了问题,因为我有太多看起来不太正常的继承。将所有内容包装在一个“复杂扩展”类中似乎也太多了。我想我应该很少有工作在一起的类 - 但我遇到了这样的问题:如果缓存类没有真正扩展和覆盖 BaseFacebook::api() 方法 - 速记和身份验证类将无法使用缓存。

也许某种模式就在这里?您将如何组织这些类及其依赖项?

编辑 04.07.2012

与主题相关的代码位:

Facebook PHP-sdk 的基类是这样的:

abstract class BaseFacebook {

    // ... some methods

    public function api(/* polymorphic */) 
    {
        // ... method, that makes api calls
    }

    public function getUser()
    {
        // ... tries to get user id from session
    }

    // ... other methods

    abstract protected function setPersistentData($key, $value);

    abstract protected function getPersistentData($key, $default = false);

    // ... few more abstract methods

}

Normaly Facebook 类对其进行了扩展,并引入了这些抽象方法。我用我的替代品代替了它 - Facebook_Session 类:

class Facebook_Session extends BaseFacebook {

    protected function setPersistentData($key, $value)
    {
        // ... method body
    }

    protected function getPersistentData($key, $default = false)
    {
        // ... method body
    }

    // ... implementation of other abstract functions from BaseFacebook
}

好的,然后我使用速记方法和配置变量对此进行更多扩展:

class Facebook_Custom extends Facebook_Session {

    public function __construct()
    {
        // ... call parent's constructor with parameters from framework config
    }

    public function api_batch()
    {
        // ... a wrapper for parent's api() method
        return $this->api('/?batch=' . json_encode($calls), 'POST');
    }

    public function redirect_to_auth_dialog()
    {
        // method body
    }

    // ... more methods like this, for common queries / authorization

}

我不确定,如果这对于单个类(授权/速记方法/配置)来说不是太多。然后是另一个扩展层——缓存:

class Facebook_Cache extends Facebook_Custom {

    public function api()
    {
        $cache_file_identifier = $this->getUser();

        if(/* cache_file_identifier is not null
              and found a valid file with cached query result */)
        {
            // return the result
        }
        else
        {
            try {
                // call Facebook_Custom::api, cache and return the result
            } catch(FacebookApiException $e) {
                // if Access Token is expired force refreshing it
                parent::redirect_to_auth_dialog();
            }
        }

    }

    // .. some other stuff related to caching

}

现在这非常有效。Facebook_Cache 的新实例为我提供了所有功能。Facebook_Custom 中的速记方法使用缓存,因为 Facebook_Cache 覆盖了 api() 方法。但这是困扰我的事情:

  • 我认为这是太多的继承。
  • 这一切都非常紧密 - 就像看看我如何必须指定 'Facebook_Custom::api' 而不是 'parent:api',以避免 Facebook_Cache 类扩展上的 api() 方法循环。
  • 整体混乱和丑陋。

再说一遍,这可行,但我只是在询问以更清洁和更智能的方式执行此操作的模式/方式。

4

4 回答 4

2

确实是继承太多了。看起来像是外观设计模式的工作。使用组合而不是继承来获得更大的灵活性。将您使用的任何方法委托给适当的对象。

例如,如果任何底层类发生更改,您只需更改方法以适应更改,而不必担心覆盖任何父方法。

通常是的,将多个职责分配给一个类并不是一个好主意。在这里,类的职责是代表一个外部 API。

于 2012-07-10T02:55:03.980 回答
2

我已经为 yahoo sdk 做了一些类似的事情,让我说一下,试一试 :)

让我们假设 Facebook 是 sdk 中用于所有结束方法调用的类。您可以创建一个新类(在您的框架允许的情况下)并将该类的变量分配给 Facebook Class 的实例。

对 Facebook 的所有方法使用 __call() 并将您的自定义方法放在包装类中。对于它包装的所有未定义方法,它将转到 Facebook 类,并且根本不涉及继承。它对我有用。希望能帮助到你 :)

  Class MyWrapper
    {
       protected $facebook;
       public function __construct()
       {
          $this->facebook = new FaceBook();
       }

       public function __call($method,$args)
       {
          return $this->facebook->$method($args);
       }

       ///define  Your methods //////////

      ///////////////////////////////////
    }

    $t = new MyWrap;
    $t->api(); // Whatever !!!!

编辑:

您不需要为多个类创建多个包装器以下可以完成,您只需要在方法调用时注意,必须为保存包装类的实例的变量名添加后缀。

 Class MyWrapper
    {
       protected $facebook;
       protected $facebookCache;
       public function __construct()
       {
          $this->facebook = new FaceBook();
          $this->facebookCache = new FacebookCache();
       }

       public function __call($method,$args)
       {
          $method = explode('_',$method);
          $instance_name = $method[0];
          $method_name = $method[1];
          return $this->$instance_name->$method_name($args);
       }

       ///define  Your methods //////////

      ///////////////////////////////////
    }

    $t = new MyWrap;
    $t->facebook_api(); // Whatever !!!!
    $t->facebookCache_cache();
于 2012-07-12T12:36:42.673 回答
2

缓存等辅助功能通常作为装饰器实现(我看到你已经在另一条评论中提到过)。装饰器最适合使用接口,所以我将从创建一个开始:

interface FacebookService {
  public function api();
  public function getUser();
}

保持简单,不要在外部添加任何你不需要的东西(例如setPersistentData)。然后将现有BaseFacebook类包装在新界面中:

class FacebookAdapter implements FacebookService {
  private $fb;

  function __construct(BaseFacebook $fb) {
    $this->fb = $fb;
  }

  public function api() {
    // retain variable arguments
    return call_user_func_array(array($fb, 'api'), func_get_args());
  }

  public function getUser() {
    return $fb->getUser();
  }
}

现在很容易编写缓存装饰器:

class CachingFacebookService implements FacebookService {
  private $fb;

  function __construct(FacebookService $fb) {
    $this->fb = $fb;
  }

  public function api() {
    // put caching logic here and maybe call $fb->api
  }

  public function getUser() {
    return $fb->getUser();
  }
}

进而:

$baseFb = new Facebook_Session();
$fb = new FacebookAdapter($baseFb);
$cachingFb = new CachingFacebookService($fb);

两者都$fb公开$cachingFb相同的FacebookService接口——因此您可以选择是否要缓存,其余代码根本不会改变。

至于你的Facebook_Custom类,它现在只是一堆辅助方法;您应该将其分解为一个或多个独立的类,这些类包装FacebookService并提供特定的功能。一些示例用例:

$x = new FacebookAuthWrapper($fb);
$x->redirect_to_auth_dialog();

$x = new FacebookBatchWrapper($fb);
$x->api_batch(...);
于 2012-07-15T10:52:49.817 回答
0

我认为在这种情况下存储库设计模式会更好。虽然我不是来自 php 但根据 oops 它应该可以解决您的问题..

于 2012-06-30T17:07:06.787 回答