メソッドでビヘイビアーを追加する
システムの最終的な目標は、役に立つ出力を生成することです。 そのためには、入力を処理する必要があります。 処理を行うときは、さまざまなメソッドとデータの助けが必要になる場合があります。 オブジェクト指向プログラミング (OOP) では、メソッドとデータはオブジェクトに配置されます。 入力を処理して結果を生成するには、OOP ではメソッドが必要です。
OOP のメソッド
どのようなパラダイムを使用する場合でも、メソッドはアクションを実行できます。 そのアクションは、入力のみに依存する計算のこともあれば、変数の値を変更することもできます。
OOP でのオブジェクトのメソッドには、次の 2 つのフレーバーがあります。
- 外部メソッドは、他のオブジェクトから呼び出すことができます。
- 内部メソッドは、他のオブジェクトから到達できません。 また、内部メソッドは、外部メソッドへの呼び出しによって開始されるタスクを実行するのに役立ちます。
メソッドの種類にかかわらず、オブジェクトの属性の値 (つまり状態) を変更することができます。
状態の概念と、それを "誰" および "何" が変更できるかということは、重要なテーマです。 これは、クラスとオブジェクトの設計において重要な部分です。 これらの質問は、次のセクションである "カプセル化" につながります。
カプセル化: データを保護する
カプセル化の一般的な概念として、あるオブジェクトのデータとは、そのオブジェクトだけに関係のある "内部的" な何かです。 データはオブジェクトとメソッドがそれらのジョブを行うために必要であり、それはタスクを実行することです。 データが内部的である場合、それは外部からの他の操作、さらに厳密に言えば制御されていない外からの操作から、保護される必要があることを意味します。 なぜでしょう。
必要である理由
データが別のオブジェクトによって直接操作されてはならない理由について説明します。 次に例をいくつか示します。
内部を知る必要はない。 車を運転するときは、ペダルを踏んで、クラッチを制御したり、加速したり、ブレーキをかけたりします。 車の運転は高いレベルで行うので、表面下では何が行われているのか、車がどのようにして動くのかといったことは気にする必要がありません。 コードでも同じことが言えます。 ほとんどの場合、必要な処理を行うために呼び出すことができるメソッドがある限り、オブジェクトで何かが行われる方法を知る必要はありません。
内部を知ってはならない。 ペダルを使用して車を操作するのではなく、ドライバーやはんだ付けキットを使用して加速しようとすることを想像してください。 恐ろしくありませんか。 なぜならそのとおりだからです。 もっと具体的な例で説明しましょう。次のようなコードの square (正方形) クラスがあるとします。
class Square: def __init__(self): self.height = 2 self.width = 2 def set_side(self, new_side): self.height = new_side self.width = new_side square = Square() square.height = 3 # not a square anymore
square の例では、
height
変数を設定することによって、正方形の概念が壊れます。 square をコーディングするには、square が正常に機能するためのset_side()
メソッドを呼び出す必要があります。 オブジェクトにそのデータを処理させる方が、安全であると考えられます。 ほぼすべての場合で、メソッドを使用して操作するか、データを明示的に設定するかを、選択する必要があります。
アクセス レベル
望ましくないデータの操作からクラスとオブジェクトを保護するにはどうすればよいでしょうか。 答えは "アクセス レベル" です。 データと関数を特定のキーワードでマークすることにより、外界つまり他のオブジェクトからデータを隠すことができます。 これらのキーワードは、アクセス修飾子と呼ばれます。
Python でデータの隠ぺいを実現する方法は、属性名にプレフィックスを追加することです。 先頭の 1 つのアンダースコア _
は、このデータにはおそらく触れてはいけないという外界へのメッセージです。 square クラスを変更すると、最終的に次のようなコードになります。
class Square:
def __init__(self):
self._height = 2
self._width = 2
def set_side(self, new_side):
self._height = new_side
self._width = new_side
square = Square()
square._height = 3 # not a square anymore
先頭にアンダースコアが 1 つあっても、データを変更することはできます。Python ではそれを "保護されている" と呼びます。 これを改善することはできるでしょうか。 できます。そのためには先頭のアンダースコアを 2 つ __
にします。これは "プライベート" と呼ばれます。 これで square クラスはこのコードのようになるはずです。
class Square:
def __init__(self):
self.__height = 2
self.__width = 2
def set_side(self, new_side):
self.__height = new_side
self.__width = new_side
square = Square()
square.__height = 3 # raises AttributeError
これで安全になりました。 データは保護されているでしょうか。 完全ではありません。 Python では、基になるデータの名前が変更されるだけです。 このコードを入力すると、やはりその値を変更できます。
square = Square()
square._Square__height = 3 # is allowed
データ保護が実装されている他の多くの言語では、この問題が異なる方法で解決されています。 Python は、データ保護が厳密に実装されているのではなく、提案に近いレベルである点が、他とは異なります。
ゲッターとセッターとは
ここまで、一般に外部からデータに触れてはならないと言ってきました。 データにはオブジェクトが関与します。 すべてのルールや強力な推奨事項と同様に、例外があります。 データを変更する必要がある場合、つまり大量のコードを追加するよりも変更した方が簡単である場合があります。
ゲッターとセッターは、"アクセサー" および "ミューテーター" とも呼ばれ、データの読み取りまたは変更のためだけのメソッドです。 ゲッターは、内部データを外部から読み取ることができるようにする処理の一部を担います。よさそうな話ですね。 セッターは、データを直接変更できるメソッドです。 セッターの役割の概念は、"悪い" 値を設定できないように保護することです。 ここで、square クラスをもう一度使用して、ゲッターとセッターの動作について説明します。
class Square:
def __init__(self):
self.__height = 2
self.__width = 2
def set_side(self, new_side):
self.__height = new_side
self.__width = new_side
def get_height(self):
return self.__height
def set_height(self, h):
if h >= 0:
self.__height = h
else:
raise Exception("value needs to be 0 or larger")
square = Square()
square.__height = 3 # raises AttributeError
set_height()
メソッドは、負の値が設定されないように保護します。 実行すると、例外が発生します。
ゲッターとセッターにデコレーターを使用する
デコレーターは Python の重要なテーマです。 それは、"メタ プログラミング" と呼ばれる、より大きなテーマの一部です。 デコレーターは、関数を入力として受け取る関数です。 考え方は、再利用可能な機能を "デコレーター関数" としてエンコードし、それを使用して他の関数を "装飾する" というものです。 その目的は、前になかった機能を関数に付与することです。 デコレーターを使用すると、たとえば、オブジェクトにフィールドを追加したり、関数の呼び出しにかかる時間を測定したり、その他多くのことができます。
OOP とゲッターおよびセッターのコンテキストでは、ゲッターとセッターを追加するときに一部の定型コードを削除するのに、特定のデコレーター @property
が役に立ちます。 @property
デコレーターによって次の処理を行います。
- バッキング フィールドを作成する:
@property
デコレーターで関数を装飾すると、バッキング プライベート フィールドが作成されます。 必要に応じてこのビヘイビアーをオーバーライドできますが、既定のビヘイビアーを使用すると便利です。 - セッターを識別する: セッター メソッドでバッキング フィールドを変更できます。
- ゲッターを識別する: この関数からは、バッキング フィールドを返す必要があります。
- 削除関数を識別する: この関数で、フィールドを削除できます。
このデコレーターの動作を見てみましょう。
class Square:
def __init__(self, w, h):
self.__height = h
self.__width = w
def set_side(self, new_side):
self.__height = new_side
self.__width = new_side
@property
def height(self):
return self.__height
@height.setter
def height(self, new_value):
if new_value >= 0:
self.__height = new_value
else:
raise Exception("Value must be larger than 0")
前のコードで、関数 height()
はデコレーター @property
によって修飾されています。 この "装飾" アクションにより、プライベート フィールド __height
が作成されます。 __height
フィールドは、コンストラクター __init__()
では定義されていません。これは、デコレーターで既に行われているためです。 @height.setter
というもう 1 つの装飾も行われています。 この装飾では、"セッター" と似た見た目の height()
メソッドが指定されています。 新しい height メソッドは、別のパラメーター value
を 2 番目のパラメーターとして受け取ります。
幅とは別に高さを操作できるようにすると、やはり問題が発生します。 ゲッターやセッターを使用すると、リスクが発生するので、それを検討する前に、クラスの動作を理解する必要があります。