アダプター

はじめに

アダプターはクラスの外側でクラスの振るまいを拡張することを可能にします。 これはクラスごとに数百もの利用出来るメソッドがある複雑なシステムを、よりモジュール化してコードを見やすくします。

  • クラスのインターフェースは読みやすくなります。(ごみは見えなくなります)
  • クラスの機能はクラスのソースコードの外で拡張可能になります。
  • アドオンプロダクトはクラスの機能の一部を拡張または上書きする場合があります。 アダプターが簡単に結合できる機能を提供するため、フレームワークは過度にアダプターを使用します。 外部コードはアダプターを上書きして機能を改造/変更できます。 例としてはテーマプロダクトはそのテーマに合うように、検索ボックス viewlet を上書きする場合があります。

欠点としては、アダプターがクラスやソースコードを 調査 しても見つけられないということです。 アダプターを見つけられるようにするためにはきちんと資料に残す必要があります。

アダプターの詳細については zope.component README を参照してください。

Adapter ZCML

アダプターは以下によってマッチングされます。

  • プロバイダーのインターフェース(どの機能アダプターが提供したか)
  • パラメーターのインターフェース

2種類のアダプターがあります。

  • 一つだけのパラメーターを使用する通常のアダプター
  • たくさんのパラメーターをタプルで受け取るマルチアダプター

アダプターを登録する

アダプターは機能をクラスに対して提供します。そして、インターフェースがクラスのインスタンスから問い合わせられるときにこの機能が有効になります。

以下はカスタマイズした “image provider” を作成する方法の例です。 “image provider” は任意のコンテンツから画像の一覧を取得します。

これは “image provider” インターフェースです。:

from zope.interface import Interface

class IProductImageProvider(Interface):

    def getImages(self):
        """ プロダクトに付属する画像取得します。

        @return: 反復可能な画像オブジェクト
        """

これはコンテンツのクラスです。:

class MyShoppableItemType(folder.ATFolder):
    """ Buyable physical good with variants of title and price and multiple images """
    implements(IVariantProduct)

    meta_type = "VariantProduct"
    schema = VariantProductSchema

これはコンテントクラスのアダプターです。

import zope.interface

from getpaid.variantsproduct.interfaces.multiimageproduct import IProductImageProvider

class FolderishProductImageProvider(object):
    """ Mix-in class which provide product image management functions.

    Assume the content itself is folderish archetype content type and
    all contained image objects are product images.
    """

    zope.interface.implements(IProductImageProvider)

    def __init__(self, context):
        # Each adapter takes the object itself as the contruction parameter
        # and possibly provided other parameters for the interface adaption
        self.context = context

    def getImages(self):
        """ Return sequence of images.

        Perform folder listing and filter image content from it.
        """

        images = self.context.listFolderContents(contentFilter={"portal_type" : "Image"})
        return images

configure.zcml で MyShoppableItemType というコンテントタイプに対してアダプターを登録します。:

<adapter for=".shop.MyShoppableItemType"
         provides=".interfaces.IProductImageProvider"
         factory=".images.FolderishProductImageProvider" />

これでアダプターを使用して問い合わせが実行出来るようになります。以下はユニットテストの例です。:

def test_get_images(self):

    self.loginAsPortalOwner()

    self.portal.invokeFactory("MyShoppableItemType", "product")

    product = self.portal.product

    image_provider = IProductImageProvider(product)

    images = image_provider.getImages()

    # Not yet any uploaded images
    self.assertEqual(len(images), 0)

一般的なアダプターのコンテクスト

以下のインターフェースはアダプターを登録するときに役に立ちます。

  • zope.interface.Interface: 各オブジェクトに適応します
  • Products.CMFCore.interfaces.IContentish: plone のコンテンツに適応します
  • zope.publisher.interfaces.IBrowserView: BrowserView(コンテクスト、リクエスト)オブジェクトに適応します

マルチアダプターの登録

<adapter for="" /> の中に複数のインターフェースを記述することが可能です。 その場合は空白か改行で区切る必要があります。

以下は、2つのインターフェースに対してアダプターを登録する例です。

  • いくつかのコンテクスト(zope.interface.Interace)
  • HTTP リクエストオブジェクト(zope.publisher.interfaces.browser.IBrowserRequest)

登録されたビュー(コンテクスト、リクエスト)をアダプターで置き換えます。

<adapter for="zope.interface.Interface zope.publisher.interfaces.browser.IBrowserRequest"
       provides="gomobile.mobile.interfaces.IMobileTracker"
       factory=".bango.BangoTracker" />

アダプターを取得する

2種類の関数があります。

  • zope.component.getAdapter はアダプターが見つからない場合に例外が発生します。
  • zope.component.queryAdapter はアダプターが見つからない場合に None を返します。

getAdapter/queryAdapter は以下の引数が必要です。

  • (最初の <adapter for=”“> 宣言のインターフェースを実装しているオブジェクト、二番目の <adapter for=”“> 宣言のインターフェースを実装しているオブジェクト、...) という内容で構成されたタプル
  • アダプターのマーカーとなるインターフェース

登録を行う例です。:

<!-- Register header animation picking logic - override this for your custom logic -->
<adapter
 provides="plone.app.headeranimation.interfaces.IHeaderAnimationPicker"
 for="plone.app.headeranimation.behaviors.IHeaderBehavior
      Products.CMFCore.interfaces.IContentish
      zope.publisher.interfaces.browser.IBrowserRequest
      "
 factory=".picker.RandomHeaderAnimationPicker" />

対応する問い合わせのコードです。:

from zope.component import getUtility, getAdapter, getMultiAdapter

# header implements IHeaderBehavior
# doc implements Products.CMFCore.interfaces.IContentish
# request implements zope.publisher.interfaces.browser.IBrowserRequest

from Products.CMFCore.interfaces import IContentish
from zope.publisher.interfaces.browser import IBrowserRequest

self.assertTrue(IHeaderBehavior.providedBy(header))
self.assertTrue(IContentish.providedBy(doc))
self.assertTrue(IBrowserRequest.providedBy(self.portal.REQUEST))

# Throws exception if not found
picker = getMultiAdapter((header, doc, self.portal.REQUEST), IHeaderAnimationPicker)

ノート

zope コンポーネントアーキテクチャーがまだ初期化されていないため、 import の間はモジュールの本文レベルのコードではアダプターは取得できません。

登録したアダプターの一覧

IHeaderBehavior アダプターが正しく登録されたかは以下のコードで確認できます。:

from zope.component import getGlobalSiteManager
sm = getGlobalSiteManager()

registrations = [a for a in sm.registeredAdapters() if a.provided == IHeaderBehavior ]
self.assertEqual(len(registrations), 1)

一覧を取得するもう一つの方法

全てのマルチアダプター(コンテクスト、リクエスト)を取得します。

例:

from zope.component import getAdapters
adapters = getAdapters((context, request), provided=Interface)

警告

zope ビューのように局所的に登録されたアダプターは取得できません。

ローカルアダプター

ローカルアダプターはフォルダーのような特定のコンテナーの中でのみ有効です。 five.localsitemanager を使用してコンテナーに対してアダプターを登録します。