web-dev-qa-db-ja.com

内在および外在状態の構文特性

Gang of Four’s Flyweight設計パターンはintrinsicおよびextrinsic statesの概念を導入します:

ここでの重要な概念は、intrinsicextrinsic状態の違いです。固有の状態はフライウェイトに保存されます。フライウェイトのコンテキストに依存しない情報で構成されているため、共有可能です。外因性の状態は、フライウェイトのコンテキストに依存して変化するため、共有することはできません。クライアントオブジェクトは、必要なときに外部状態をフライウェイトに渡す責任があります。

言い換えると、オブジェクトの状態は、固有の状態および外部の状態として分解できますオブジェクトのグループに関してここで、固有の状態はintersection ofグループのすべてのオブジェクトの状態と外部状態は、オブジェクトの状態と組み込み状態のdifferenceです。組み込み状態はグループの各オブジェクトで複製されるため、オブジェクトのグループを単一の組み込み状態を格納する単一のflyweightオブジェクトで置き換えることにより、スペースを節約できます。ただし、flyweightオブジェクトはグループのオブジェクトの複数の外部状態を格納できないため、外部状態は外部に格納され、クライアントオブジェクトからの各リクエストでflyweightオブジェクトに渡されます。このような最適化された通信プロトコルは、ステートレスプロトコルと呼ばれます。これは、フライウェイトオブジェクトが外部状態を格納しないためです。ステートレスプロトコルの例には、IPおよびHTTPが含まれます(より一般的には、すべてのRESTプロトコル。ここで、組み込み状態はリソース状態と呼ばれ、外部状態はアプリケーション状態と呼ばれます。)。

たとえば、それぞれのクライアントに3つのオブジェクトがあるとします。

o1←c1
o2←c2
o3←c3

3つのオブジェクトに関して、各オブジェクトの状態を分解できます。

状態1 = 固有の状態外部の状態1
state 2 = intrinsic stateextrinsic state 2
state = intrinsic stateextrinsic state

どこ:

固有の状態 = 状態1状態2状態
外的状態1 = 状態1\内的状態
外的状態2 = 状態2\内的状態
外的状態 = 状態\内的状態

ここでは、固有の状態が複製されています。したがって、それを単一のflyweightオブジェクトに格納する(および外部状態をクライアントに移動する)と、スペースを節約できます。

o←c1、c2、c3

ここまでは順調ですね。今、私は、フライウェイトクラスを派生させるために、属性を非共有クラスのdefinitionから組み込み状態または外部状態に分類できるかどうか疑問に思っています。

非共有クラスの組み込み状態と外部状態の次の構文特性は正しいですか?

  • 固有の状態=不変のインスタンス変数∪クラス変数;
  • 外部状態=可変インスタンス変数

たとえば、次の非共有クラスを定義してみましょう。

class Unshared:
    __z = 0

    def __init__(self):
        self.__x = 0
        self.__y = 0

    def f(self):
        return self.__x + self.__y

    def g(self):
        self.__y += 1

    @classmethod
    def h(cls):
        cls.__z += 1

提案された特徴付けは、

  • 固有の状態= {__x__z};
  • 外因性状態= {__y}。

正しいようです。その非共有クラスは、スペースを節約するためにflyweightクラスに変換できます。

class Flyweight:
    __z = 0

    def __init__(self):
        self.__x = 0

    def f(self, y):
        return self.__x + y

    @classmethod
    def h(cls):
        cls.__z += 1

編集する

@DocBrownが指摘したように、インスタンス化またはクラス固有の変更可能なインスタンス変数を組み込み状態から除外することにより、変更可能なフライウェイトオブジェクトを防止したため、私の特性評価は不完全でした。また、組み込み状態でインスタンス固有である不変のインスタンス変数も誤って含まれていました。

次の用語を定義してみましょう。

  • groupは、単一のflyweightオブジェクトが派生する非共有オブジェクトのセットです。
  • インスタンススコープインスタンス変数は、個々のオブジェクトに固有のインスタンス変数です。
  • lesser-group-scopeインスタンス変数は、検討中のグループよりも小さい非共有オブジェクトのグループに共通のインスタンス変数です。
  • group-scope instance variableは、検討中の非共有オブジェクトのグループに共通のインスタンス変数です。
  • greater-group-scopeインスタンス変数は、検討中のグループよりも大きな非共有オブジェクトのグループに共通のインスタンス変数です。
  • クラススコープインスタンス変数は、すべてのオブジェクトに共通のインスタンス変数です。

インスタンススコープのインスタンス変数とより小さいグループスコープのインスタンス変数は、検討中の非共有オブジェクトのグループによって共有できませんが、グループスコープのインスタンス変数、より大きいグループスコープのインスタンス変数、クラススコープのインスタンス変数とクラス変数は、検討中の非共有オブジェクトのグループによって共有可能です。

非共有クラスの組み込み状態と外部状態の完全な特性は次のとおりであると結論付けることができます:

  • 固有の状態=グループスコープのインスタンス変数∪Greaterグループスコープのインスタンス変数∪クラススコープのインスタンス変数∪クラス変数;
  • 外部状態=インスタンススコープインスタンス変数∪小グループスコープインスタンス変数

ただし、@ DocBrownが正しく指摘しているように、この特徴付けは純粋に構文的なものではありません。クラス変数、クラススコープのインスタンス変数、および一部の変更可能なインスタンススコープのインスタンス変数は、非共有クラス定義を調べるだけで特定できます構文的に。ただし、より大きいグループスコープのインスタンス変数、グループスコープのインスタンス変数、より小さいグループスコープのインスタンス変数、不変のインスタンススコープのインスタンス変数、および可変のインスタンススコープのインスタンス変数は、セマンティックでのみ識別できます。

非共有クラスからflyweightクラスを派生させると、そのインスタンススコープのインスタンス変数とlesser-group-scopeインスタンス変数はflyweightクラスの外に移動され、そのグループスコープのインスタンス変数はflyweightクラスのインスタンススコープのインスタンス変数になります。 great-group-scopeインスタンス変数はflyweightクラスの外に移動され、そのクラススコープインスタンス変数はflyweightクラスのクラススコープインスタンス変数のままで、そのクラス変数はflyweightクラスのクラス変数のままです。

たとえば、次の非共有クラスを定義してみましょう。

import collections

class Unshared:
    __L = 0  # immutable class variable
    __instances = collections.defaultdict(list)  # mutable class variable

    def __init__(self, GROUP, I, i, j, k):
        self.__instances[GROUP].append(self)
        self.__GROUP = GROUP  # immutable group-scope instance variable
        self.__I = I  # immutable instance-scope instance variable
        self.__i = i  # mutable instance-scope instance variable
        for instance in self.__instances[GROUP]:
            instance.__j = j  # mutable group-scope instance variable
        self.__K = 0  # immutable class-scope instance variable
        for instances in self.__instances.values():
            for instance in instances:
                instance.__k = k  # mutable class-scope instance variable

    def query(self):
        return {
            "instance": [self.__I, self.__i],
            "group": [self.__GROUP, self.__j],
            "class": [self.__K, self.__k, self.__L, Tuple(self.__instances)]
        }

    def manipulate(self):
        # Update the mutable instance-scope variables.
        self.__i = None
        # Update the mutable group-scope variables.
        for instance in self.__instances[self.__GROUP]:
            instance.__j = None
        # Update the mutable class-scope variables.
        for instances in self.__instances.values():
            for instance in instances:
                instance.__k = None
        self.__instances[None] = []

新しい特性化により、以下が得られます。

  • 固有の状態= {__GROUP__j__K__k__L__instances};
  • 外因性状態= {__I__i}。

その非共有クラスは、スペースを節約するためにflyweightクラスに変換できます。

class Flyweight:
    __L = 0
    __instances = {}

    def __init__(self, GROUP, j, k):
        self.__instances[GROUP] = self
        self.__GROUP = GROUP
        self.__j = j
        self.__K = 0
        for instance in self.__instances.values():
            instance.__k = k

    def query(self, I, i):
        return {
            "instance": [I, i],
            "group": [self.__GROUP, self.__j],
            "class": [self.__K, self.__k, self.__L, Tuple(self.__instances)]
        }

    def manipulate(self):
        # Update the mutable group-scope variables.
        self.__j = None
        # Update the mutable class-scope variables.
        for instance in self.__instances.values():
            instance.__k = None
        self.__instances[None] = []

非共有クラスの使用:

ra1 = Unshared("a", 1, 1, 1, 0)  # unshared instance of group "a"
ra2 = Unshared("a", 2, 2, 1, 0)  # unshared instance of group "a"
rb1 = Unshared("b", 1, 1, 2, 0)  # unshared instance of group "b"
rb2 = Unshared("b", 2, 2, 2, 0)  # unshared instance of group "b"

print(ra1.query())  # {"instance": [1, 1], "group": ["a", 1], "class": [0, 0, 0, ("a", "b")]}
print(ra2.query())  # {"instance": [2, 2], "group": ["a", 1], "class": [0, 0, 0, ("a", "b")]}
print(rb1.query())  # {"instance": [1, 1], "group": ["b", 2], "class": [0, 0, 0, ("a", "b")]}
print(rb2.query())  # {"instance": [2, 2], "group": ["b", 2], "class": [0, 0, 0, ("a", "b")]}
ra1.manipulate()
print(ra1.query())  # {"instance": [1, None], "group": ["a", None], "class": [0, None, 0, ("a", "b", None)]}
print(ra2.query())  # {"instance": [2, 2], "group": ["a", None], "class": [0, None, 0, ("a", "b", None)]}
print(rb1.query())  # {"instance": [1, 1], "group": ["b", 2], "class": [0, None, 0, ("a", "b", None)]}
print(rb2.query())  # {"instance": [2, 2], "group": ["b", 2], "class": [0, None, 0, ("a", "b", None)]}

Flyweightクラスの使用:

fa = Flyweight("a", 1, 0)  # shared instance of group "a"
fb = Flyweight("b", 2, 0)  # shared instance of group "b"

print(fa.query(1, 1))  # {"instance": [1, 1], "group": ["a", 1], "class": [0, 0, 0, ("a", "b")]}
print(fa.query(2, 2))  # {"instance": [2, 2], "group": ["a", 1], "class": [0, 0, 0, ("a", "b")]}
print(fb.query(1, 1))  # {"instance": [1, 1], "group": ["b", 2], "class": [0, 0, 0, ("a", "b")]}
print(fb.query(2, 2))  # {"instance": [2, 2], "group": ["b", 2], "class": [0, 0, 0, ("a", "b")]}
fa.manipulate()
print(fa.query(1, None))  # {"instance": [1, None], "group": ["a", None], "class": [0, None, 0, ("a", "b", None)]}
print(fa.query(2, 2))     # {"instance": [2, 2], "group": ["a", None], "class": [0, None, 0, ("a", "b", None)]}
print(fb.query(1, 1))     # {"instance": [1, 1], "group": ["b", 2], "class": [0, None, 0, ("a", "b", None)]}
print(fb.query(2, 2))     # {"instance": [2, 2], "group": ["b", 2], "class": [0, None, 0, ("a", "b", None)]}
3
Maggyero

あなたの質問が

インターフェースから更新可能なインスタンス変数

外的国家になる候補者である一方、

クラス変数+インターフェイスから更新できないインスタンス変数

は、フライウェイトオブジェクトの固有の状態になるための候補です。

  • インターフェイスから更新できないインスタンス変数から新しいオブジェクトを作成すると、不変のflyweightオブジェクトになります。これらのオブジェクトは異なるクライアント間で共有されるため、これは実際には不要な副作用を回避するための良い方法です。

  • クラス変数areはすでに共有されているため、それらはすでに組み込みの状態を提供します。

ただし、これは難しいルールではありません。すべてのクライアントに対して意図的な副作用を伴うフライウェイトを作成することが理にかなう要件がある場合もあります。たとえば、 WikipediaのPythonの例 は不変のチーズブランドに関するもので、各ブランドには固定コストがあります。しかし、すべてのブランドのコストを後で変更する必要がある場合、フライウェイトオブジェクトを変更可能にすることは、可能な解決策になる可能性があります。

さらに、フライウェイトのアイデアは、かなりの量のメモリ空間を節約することであることを忘れないでください。組み込み状態を共有しても、関連する方法でオブジェクトの数を減らすことができない場合、または外部状態の組み合わせを減らすことができない場合は、インスタンス変数の種類を見ただけでは役に立ちません。

また、問題となっているクラスは、フライウェイトを抽出する前に内部で再構築する必要がある場合があります。これにより、クラスインターフェースを介して現在存在するインスタンス変数またはその使用法だけが無意味になります。

最後に、クラス名Regularとメソッド名fghだけを指定しても、このような設計上の質問にはほとんど答えられないと言います。いくつかのコンテキストが必要であり、クラスの特定のユースケースを理解する必要があります。その後、セマンティックで何をするかを決定できます。純粋に構文の観点からのみこのような問題にアプローチしようとしても、通常はうまく機能しません。

2
Doc Brown

あなたの理解は基本的に正しいです。 Flyweightモデルの適切な使用例は、Java Swing's JTableおよびセルレンダラー( example )を使用した場合です。)有限数のセルレンダラーテーブルからの情報がレンダラーに渡され、そこで何が行われるべきかが計算されます。

この例では、すべてのextrinsic状態がコールバックメソッドに渡されます。

Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
    // .... Can set color based on row, whether the cell has focus, etc.
    // .... all of that is extrinsic state
}

この例では、フライウェイトがintrinsic状態になることはほとんどありません。そして、それはこのパターンで理解することが重要だと思います。ただし、固有の状態の例には次のものがあります。

  • 外的状態に応じて切り替えるアイコンのセット
  • 現在の行に応じて切り替えるフォントのセット

フライウェイトの背後にある全体的な概念は、フライウェイトのインスタンスを最小化し、再利用性を最大化することです。


Gang of Fourパターンの本では、それらの例はページ上のグリフでした。ドキュメント内の各文字に独自のグリフオブジェクトがある場合、個々のグリフのレンダリングはメモリを大量に消費します。彼らの例では、彼らは責任をこのように分けています:

  • 各グリフオブジェクトには、グリフグラフィックのintrinsic値がありました
  • extrinsic値は、グリフがレンダリングされる画面(または紙)上の場所でした

多くの点で、それが画面上でのフォントのレンダリング方法です。フォントには文字セット内の各グリフの定義があり、画面は必要に応じてそれらのグリフをレンダリングします。

0
Berin Loritsch