3. コンポーネントとインターフェース

Zope は内部の多くの箇所でコンポーネントアーキテクチャを使用しています。 Zope のコンポーネントとは単なる Python のオブジェクトで、インターフェース によってその特性が記述されているに過ぎません。 Zope の開発者となる あなたは、このインターフェースを使用して独自の Zope コンポーネントを 構築することが出来ます。

3.1. Zope コンポーネント

コンポーネントとは、インターフェースに結びつけられたオブジェクトの事です。 インターフェースとは、他の Python オブジェクトとどのように協調動作するのか が記述されている Python のオブジェクトです。 本章では、1つのインターフェースと、いくつかのシンプルなコンポーネントの 作成を通してそれらがどのように動作するかを見ていきます。

ここに挨拶するだけの非常にシンプルなコンポーネントがあります。 このように全てのコンポーネントはこの例のように一般的には2つの部品、 1つのインターフェースと1つの実装で構成されます:

from zope.interface import Interface
from zope.interface import implements

class IHello(Interface):
    """The Hello interface provides greetings."""

    def hello(name):
        """Say hello to the name"""

class HelloComponent(object):

    implements(IHello)

    def hello(self, name):
        return "hello %s!" % name

それでは1行ずつ見ていきましょう。まず2つの Python のクラスが 定義されています。最初のクラスは インターフェース を作成しており、 2つめのクラスは 実装 を作成しています。

最初のクラス定義は IHello インターフェースを作成しています。 このインターフェースは1つのメソッド hello を記述しています。 注意して欲しいのは、ここにはメソッドの実装は無く、インターフェース は動作を決めずに、単に仕様のみを記述していると言うことです。

2つめの class 定義では HelloComponent クラスを定義しています。 このクラスは IHello の記述が実際に何を 行う のかを定義した コンポーネントです。通常これを IHello実装 と言います。 ここで、 HelloComponent が何のインターフェースを実装しているのか という事をどうにかしてそのクラスとインターフェースとで表す必要があり、 クラス内で呼ばれている implements 関数がそれを行っています。 これは “このクラスはこれらのインターフェースを実装します” という意味になります。 この例の場合、 HelloComponent は1つのインターフェース IHello を実装していることを表明しています。

インターフェースはオブジェクトがどのような機能を持つかを記述しますが、 その実装がどのように行われるべきかを決めたりはしません。 例えば、ここにもう少し複雑な IHello の実装があります:

import xmlrpclib
class XMLRPCHello:

    implementats(IHello)

    def hello(self, name):
        """Delegates the hello call to a remote object
        using XML-RPC.

        """
        s = xmlrpclib.Server('http://www.zope.org/')
        return s.hello(name)

このコンポーネントはリモートサーバーに問い合わせを行い、リモート コンポーネントから挨拶文を取得します。

これもまた一つのコンポーネントです。本章の残りの部分では、 インターフェースについてと、インターフェースとコンポーネントの協調動作 について説明します。3章の中で、これらを Zope プロジェクトに当てはめて 見ていきます。

3.2. Python インターフェース

インターフェースは、オブジェクトに関する役に立つ情報を含むことによって、 オブジェクトの動きについて説明します。役に立つ情報とは:

  • オブジェクトに関する自由形式のドキュメント。Python の用語で言うと これはインターフェースの “doc string” です。この項目には、 オブジェクトがどのように動作するのかを自然言語で記載したり、 オブジェクトについてのその他の有用な事を記載します。
  • 属性についての説明。属性説明には属性の名前や、属性が何に使用される のかなどを自由形式で記載します。
  • メソッドについての説明。メソッド説明には以下を含めることが出来ます:
    • メソッドについての詳細や使用方法などを記載した “doc string” 。
    • メソッドが期待するパラメータオブジェクトの列挙とその説明。
  • オプションのタグデータ。 インターフェースオブジェクト (とその 属性、メソッド、メソッドの引数) には、アプリケーションで 定義したタグデータを持たせることを選択出来ます。 例えば、インターフェースや属性に関連づけたい、セキュリティー情報、 事前/事後の状態、ユニットテスト、その他必要な情報などです。

これらの情報全てが義務ではありません。例えば、作成したインターフェース のメソッドの説明ドキュメントは記載しても引数の説明は詳細には書かない といった事もできます。インターフェースオブジェクトはフレキシブルなので、 コンポーネントが何を取捨選択するかを決めることが出来ます。

3.3. なぜインターフェースを使うのですか?

インターフェースは、多くの開発者が参加する大きいシステムの開発で見られる、 いくつかの問題を解決します。

  • 開発者たちは、各オブジェクトの挙動を知るためにあなたのシステムの ソースコードを読み、これに多くの時間を浪費します。さらに悪い事に、 既に誰かが同じような時間の浪費をしていることがあります。
  • 新しく参加した開発者がオブジェクトの動作、結果、影響範囲、使い方など を勘違いする可能性があります。
  • オブジェクトの使い方をソースから推測した結果、開発者は “内部利用専用” のメソッドや属性を使おうとするかもしれません。
  • 初心者のプログラマーにとって、コードから読み取るのは困難で、 グル (非常に技術レベルの高い人) が書いたコードを読んで理解しようと する事は、やる気をなくさせてしまいます。

インターフェースは、オブジェクトの使い方を記述する方法を提供し、 記述された説明を見つける仕組みを提供することで、このような問題を 解決しようとしています。

3.4. インターフェースを作る

コンポーネントを作成する最初のステップは、これまで見てきたように、 インターフェースを作ることです。

インターフェースオブジェクトは Python の class 構文で簡単に 構築することが出来ます。覚えておいて欲しい事として、 class 構文を使っているためすこし紛らわしいのですが、 インターフェースは class では ありません 。 Python のクラスの構文を使用しているのは単に便利だからであり、 結果として得られるオブジェクトはクラスではなく インターフェース だという事を理解しておくのは重要です。

Python のクラス構文を使用してインターフェースを作成するには、 zope.interface.Interface を継承した Python クラスを作成して ください:

from zope.interface import Interface

class IHello(Interface):

    def hello(name):
        """Say hello to the world"""

このインターフェースは、各メソッドの挙動を実装しておらず、ただ単に “IHello” オブジェクトが実装された場合のインターフェース仕様を 記述しています。 zope.interface.Interface を継承することにより、 得られる IHello オブジェクトはインターフェースオブジェクト になります。 Python インタープリタを使って以下のように確認出来ます:

>>> IHello
<InterfaceClass __main__.IHello>

ここで、利用者の挙動を定義した具象クラスと IHello インターフェース を関連づける事が出来ます。例えば:

class HelloComponent:

    implements(IHello)

    def hello(self, name):
        return "Hello %s!" % name

この新しい HelloComponent クラスは IHello インターフェースを 実装した具象クラスです。クラスは1つ以上のインターフェースを持つこと も出来ます。例えば、 “Container” オブジェクト内でどのように動作するのか という事が記述された ‘IItem’ というインターフェースがあるとします。 もし HelloConponent インスタンスに IHello と同じように、 IItem インターフェースを実装することを宣言するのであれば、 ‘HelloComponent’ クラスの宣言で以下のように、インターフェースオブジェクト を並べて記載します:

class HelloComponent:

    implements(IHello, IItem)

3.5. インターフェースモデル

インターフェースは他のインターフェースに拡張できます。例えば、 IHello インターフェースを拡張してメソッドを追加してみましょう:

class ISmartHello(IHello):
    """A Hello object that remembers who it's greeted"""

    def lastGreeted(self):
        """Returns the name of the last person greeted."""

ISmartHelloIHello インターフェースを拡張しています。 これは、クラス構文が他のクラスを継承するのと同じ構文で実現できます。

ここで、 ISmartHello に対して、自身が拡張しているインターフェース の一覧を getBases を使って問い合わせることが出来ます:

>>> ISmartHello.getBases()
(<InterfaceClass __main__.IHello>,)

インターフェースは複数のインターフェースから拡張することが出来、 getBases はそれらのインターフェースの一覧を返すでしょう。 ISmartHello があるインターフェースを拡張しているかどうかを 知りたい場合、 getBases を呼び出し、結果の一覧を検索して探す ことも出来ますが、 extends メソッドを呼び出せばより簡単に 目的の結果を得られます:

>>> ISmartHello.extends(IHello)
True
>>> ISandwich(Interface):
...     pass
>>> ISmartHello.extends(ISandwich)
False

extends を使って、あるインターフェースが他のあるインターフェース を拡張しているのかどうかを判断出来る事が分かりました。

あなたはインターフェースが他のインターフェースを拡張することと、 クラスが他のクラスをサブクラス化することとに類似性を見いだすでしょう。 これらは同じコンセプトですが、この二つを同じと考えるべきではありません。 クラスとインターフェースが1対1に対応づけられているという前提はありません。 1つのクラスが複数のインターフェースを実装することもあるでしょうし、 あるクラスは基底クラスのインターフェースを実装しないかもしれません。

クラスとインターフェースとの区別は常に明確に保たれるべきです。 クラスの目的はオブジェクトがどのように動作するかについての実装を 共有することです。インターフェースの目的はオブジェクト どのように 動作するのかを記載することであり、オブジェクトがどのように実装されるか を決めることではありません。これにより、同じインターフェースの実装 として全く異なる実装を持ついくつものクラスを持てるようになります。 このような違いがあるので、インターフェースとクラスを混同するべきでは ありません。

3.6. インターフェース問い合わせ

インターフェースは情報取得のための問い合わせの仕組みがあります。 最もシンプルなケースとしては、インターフェースを構成している全ての 要素名を問い合わせることがあります。例として Python インタープリタを 使って以下のようにインターフェースが持っている 名前 を問い合わせます:

>>> User.names()
['getUserName', 'getFavoriteColor', 'getPassword']

インターフェースは各要素についてさらに詳細な情報を取得する方法も 提供しています。インターフェースオブジェクトは、 namesAndDescriptions メソッドを使うことで各要素についての ‘(name, description)’ タプルの リストを返します。

例:

>>> User.namesAndDescriptions()
[('getUserName', <Interface.Method.Method object at 80f38f0>),
('getFavoriteColor', <Interface.Method.Method object at 80b24f0>),
('getPassword', <Interface.Method.Method object at 80fded8>)]

この例ではインターフェースの3つの要素の “description” は全て Method オブジェクトです。 ‘Attribute’ と Method のどちらも Description オブジェクトになることが出来ます。属性、メソッド、そしてインターフェース オブジェクトは以下のインターフェースを実装しています:

  • getName() – オブジェクトの名前を返します。
  • getDoc() – オブジェクトのドキュメントを返します。

メソッドオブジェクトは Python メソッドについてのより詳細なメタデータ を記述する仕組みを提供しており、以下のメソッドを持っています:

  • getSignatureInfo() – メソッドの引数についての定義を格納した辞書 を返します。
  • getSignatureString() – メソッドのシグネチャを人間が読める文字列で 返します。

例:

>>> m = User.namesAndDescriptions()[0][1]
>>> m
<Interface.Method.Method object at 80f38f0>
>>> m.getSignatureString()
'(fullName=1)'
>>> m.getSignatureInfo()
{'varargs': None, 'kwargs': None, 'optional': {'fullName': 1},
'required': (), 'positional': ('fullName',)}

getSignatureInfo を使うことでメソッドの各引数の名前や型について 知ることが出来ます。

3.7. 実装の確認

クラスやインスタンスが、あるインターフェースを実装しているかどうかを、 インターフェースに問い合わせることが出来ます。 例えば、 HelloCmponent クラスが ‘IHello’ を実装しているかどうかは 以下のように確認します:

IHello.implementedBy(HelloComponent)

これは真を返します。同様に HelloComponent のインスタンスについても、 インターフェースに対して、インスタンスがそのインターフェースを実装 しているかを以下のように確認します:

IHello.implementedBy(my_hello_instance)

これにも、 my_hello_instanceHelloComponent のインスタンスならば 真を返します。他に IHello インターフェースを実装しているクラスであれば 真を返すでしょう。

3.8. まとめ

インターフェースは、 Python オブジェクトの定義を記述するシンプルな 方法を提供します。インターフェースを使って、オブジェクトが出来る事 を記載してください。 Zope は今後もっとコンポーネント指向になっていき、 そのとき、あなたのオブジェクトも、よりフィットしてくるでしょう。 コンポーネントとインターフェースの技術が進み、ドキュメントと 検証の機能が今日ではとても使えるものになっています。

(Translated by Shimizukawa, r104989, original-site)