2020年6月21日日曜日

5: Pythonサイクリックインポートを、構造を歪めることなく解消する

<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>

サイクリックな依存は極めて自然で問題ありません、ドメインモデル的には。Pythonがそれに対処できないからといって、その構造を歪めるようにあなたがアドバイスされるべきではありません。

話題


About: Pythonプログラミング言語

この記事の目次


開始コンテキスト


  • 読者は、Pythonの基本的知識を持っている。

ターゲットコンテキスト



  • 読者は、Pythonnにおいて、サイクリックインポートを、コード構造を歪めることなく解消する方法を知る。

本体


1: サイクリックインポートに遭遇する


Hypothesizer 7
以下のコードは、「ImportError: cannot import name 'ConstructorAOriginal' from partially initialized module 'theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorAOriginal' (most likely due to a circular import)」というエラーを起こす、実行時に。

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/ConstructorAOriginal.py

@Python ソースコード
import sys
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyAOriginal import PowerCompanyAOriginal

class ConstructorAOriginal:
	def __init__ (a_this: "ConstructorAOriginal") -> None:
		a_this.i_powerCompany: "PowerCompanyAOriginal"
	
	def initialize (a_this: "ConstructorAOriginal", a_powerCompany: "PowerCompanyAOriginal") -> None:
		a_this.i_powerCompany = a_powerCompany
	
	def selfMaintain (a_this: "ConstructorAOriginal") -> bool:
		a_this.i_powerCompany.supplyPower ()
		return True
	
	def constructBuilding (a_this: "ConstructorAOriginal") -> bool:
		sys.stdout.write ("### Performing the service of ConstructorAOriginal.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/PowerCompanyAOriginal.py

@Python ソースコード
import sys
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorAOriginal import ConstructorAOriginal

class PowerCompanyAOriginal:
	def __init__ (a_this: "PowerCompanyAOriginal") -> None:
		a_this.i_constructor: "ConstructorAOriginal"
	
	def initialize (a_this: "PowerCompanyAOriginal", a_constructor: "ConstructorAOriginal") -> None:
		a_this.i_constructor = a_constructor
	
	def selfMaintain (a_this: "PowerCompanyAOriginal") -> bool:
		a_this.i_constructor.constructBuilding ()
		return True
	
	def supplyPower (a_this: "PowerCompanyAOriginal") -> bool:
		sys.stdout.write ("### Performing the service of PowerCompanyAOriginal.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/Test1Test.py

@Python ソースコード
from typing import List
from typing import Type
import sys
# the original test Start
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorAOriginal import ConstructorAOriginal
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyAOriginal import PowerCompanyAOriginal
# the original test End

class Test1Test:
	@staticmethod
	def main (a_arguments: List [str]) -> None:
		Test1Test.test ()
	
	@staticmethod
	def test () -> None:
		# the original test Start
		l_constructorAOriginal: "ConstructorAOriginal" = ConstructorAOriginal ()
		l_powerCompanyAOriginal: "PowerCompanyAOriginal" = PowerCompanyAOriginal ()
		l_constructorAOriginal.initialize (l_powerCompanyAOriginal)
		l_powerCompanyAOriginal.initialize (l_constructorAOriginal)
		
		l_constructorAOriginal.constructBuilding ()
		l_powerCompanyAOriginal.supplyPower ()
		# the original test End

if __name__ == "__main__":
	Test1Test.main (sys.argv)

...うーん、何が起きたかは知っている: 'Test1Test'は'ConstructorAOriginal'をインポートし始め、'ConstructorAOriginal'は'PowerCompanyAOriginal'をインポートし始め、'PowerCompanyAOriginal'は'ConstructorAOriginal'をインポートするふりをしたが、実際にはそうしなかった、なぜなら、そのモジュールは既にインポートされている最中だから、そして、'ConstructorAOriginal'という変数を'ConstructorAOriginal'クラスという値にセットしようとしたが、できなかった、なぜなら、そのクラスはまだ定義されていないから、なぜなら、'ConstructorAOriginal'のその'import'行しかまだ実行されていないから: 典型的なサイクリックインポート。

しかし、その知識は、私は特にハッピーにはしない: Pythonがただ拙劣なだけじゃないのか?: Javaはサイクリックインポートを許す、C++はサイクリックインクルードを許す(事前宣言を求められるとはいえ)、C#はサイクリック使用を許す、Pythonはただ許さない。...Pythonは動的型付けプログラミング言語だから仕方がないのだと一部の人々が弁解することを私は知っているが、私はこう言うだけだ、「動的型付けであってくれなどと私は頼んだことはない、その弁解は、動的型付けプログラミング言語など最低であることをただ証明しているだけだ。」。

以下が推奨されていることは知っている。

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/ConstructorANoFrom.py

@Python ソースコード
import sys
import theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyANoFrom

class ConstructorANoFrom:
	def __init__ (a_this: "ConstructorANoFrom") -> None:
		a_this.i_powerCompany: "theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyANoFrom.PowerCompanyANoFrom"
	
	def initialize (a_this: "ConstructorANoFrom", a_powerCompany: "theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyANoFrom.PowerCompanyANoFrom") -> None:
		a_this.i_powerCompany = a_powerCompany
	
	def selfMaintain (a_this: "ConstructorANoFrom") -> bool:
		a_this.i_powerCompany.supplyPower ()
		return True
	
	def constructBuilding (a_this: "ConstructorANoFrom") -> bool:
		sys.stdout.write ("### Performing the service of ConstructorANoFrom.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/PowerCompanyANoFrom.py

@Python ソースコード
import sys
import theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorANoFrom

class PowerCompanyANoFrom:
	def __init__ (a_this: "PowerCompanyANoFrom") -> None:
		a_this.i_constructor: "theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorANoFrom.ConstructorANoFrom"
	
	def initialize (a_this: "PowerCompanyANoFrom", a_constructor: "theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorANoFrom.ConstructorANoFrom") -> None:
		a_this.i_constructor = a_constructor
	
	def selfMaintain (a_this: "PowerCompanyANoFrom") -> bool:
		a_this.i_constructor.constructBuilding ()
		return True
	
	def supplyPower (a_this: "PowerCompanyANoFrom") -> bool:
		sys.stdout.write ("### Performing the service of PowerCompanyANoFrom.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/Test1Test.py

@Python ソースコード
from typing import List
from typing import Type
import sys
# the no-from test Start
import theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorANoFrom
import theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyANoFrom
# the no-from test End

class Test1Test:
	@staticmethod
	def main (a_arguments: List [str]) -> None:
		Test1Test.test ()
	
	@staticmethod
	def test () -> None:
		# the no-from test Start
		l_constructorANoFrom: "theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorANoFrom.ConstructorANoFrom" = theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorANoFrom.ConstructorANoFrom ()
		l_powerCompanyANoFrom: "theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyANoFrom.PowerCompanyANoFrom" = theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyANoFrom.PowerCompanyANoFrom ()
		l_constructorANoFrom.initialize (l_powerCompanyANoFrom)
		l_powerCompanyANoFrom.initialize (l_constructorANoFrom)
		
		l_constructorANoFrom.constructBuilding ()
		l_powerCompanyANoFrom.supplyPower ()
		# the no-from test End

if __name__ == "__main__":
	Test1Test.main (sys.argv)

確かに、それはうまくいく、しかしなぜなのか?...つまり、'PowerCompanyANoFrom'をインポートしているとき、'__init__'定義内のその「theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorANoFrom」はまだ定義されていないのだが、それはなぜエラー判定されないのか?

その理由は、純粋なPython的には、第1に、データタイプ指定はただのアノテーションであり、無視できるコメントにすぎないこと、第2に、ファンクション定義における変数は、そのファンクションが実際に実行される直前に定義されてさえいればオーケーであることだ。

'静的タイプチェック'的には(私はmypyを使う)、それがオーケーなのは、mypyは、拙劣なPythonインポートメカニズムに統治されていないから。

えーと、問題解決?...実のところ、そのアプローチには、私は強い嫌忌を持っている。

なぜか?そんなの馬鹿げていると思う: 私は常にフルネームを指定しなければならないのか?...私は、どのクラスもモジュール名修飾なしでユニークであるように命名するように努めている、クラスを同定するのにそんな修飾が必要ないように。例えば、誰が自分のクラスを'str'と命名するだろうか、自分のクラスはモジュール名によって標準'str'から区別できるからオーケーなのだとうそぶいて?...私は、ネームスペースメカニズムは、名称が重複しないことを保証するために必要であることに同意するし、作成物を整理する手段としてとても有益であることにも同意するが、そのメカニズムが、全ての作成物をコード内で常にフルネーム指定させるためのものだというのには同意しない。

'java.util.ArrayList'、'java.util.LinkedHashMap'、等の全てのクラス名が常にフルネームで現れるようなJavaコードは、私には馬鹿げているように思われる。誰が、コードをそんな風に書くのか?そんな書き方が、なぜPythonにおいてだけ馬鹿げていないのか?

'as'を使って修飾名を短くすればよいだけだろうと言う人がいるかもしれないが、そういう問題ではない: 短くしようがしまいが、修飾が求めれていて、それがコンセプト上馬鹿げているのだ。...それに、私は短縮名なるもの全般が嫌いだ。なぜか?なぜなら、1つのものに複数の名前を持つということが、私には煩わしいから。実際、もしも、ある短縮名が良いものであるならば、なぜ、それを私はそもそも正式名にしなかったのだろうか。もしも、その短縮名が良いものでないのであれば、そんな良くない名前を私は欲しくない。...実際面から言えば、名前短縮を首尾一貫して行うことは極めて難しい: 同じ正式名が様々な箇所で違って短縮されてしまい、コードをあちこち汚してしまう。例えば、'theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorAOriginal'はいかに短縮されるべきか?'tccC'?しかし、'theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorASecondary'が重複を起こしてしまう...

Javaコードに'ju.List'、'u.List'、'utl.List'などと様々に短縮された修飾付き名が散らばっていれば、それを私は汚いと思う。なぜ、Pythonにおいてだけ、そのようなコードが汚くないのか?


2: 純Python(静的タイプチェックの無い)の世界における解決策、その世界に私は住んでいないが


Hypothesizer 7
実は、静的タイプチェックを私が用いないとすれば、以下でよいのだ。

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/ConstructorAWithoutStaticTypesChecking.py

@ ソースコード
import sys

class ConstructorAWithoutStaticTypesChecking:
	def __init__ (a_this):
		a_this.i_powerCompany = None
	
	def initialize (a_this, a_powerCompany):
		a_this.i_powerCompany = a_powerCompany
	
	def selfMaintain (a_this):
		a_this.i_powerCompany.supplyPower ()
		return True
	
	def constructBuilding (a_this):
		sys.stdout.write ("### Performing the service of ConstructorAWithoutStaticTypesChecking.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/PowerCompanyAWithoutStaticTypesChecking.py

@ ソースコード
import sys

class PowerCompanyAWithoutStaticTypesChecking:
	def __init__ (a_this):
		a_this.i_constructor = None
	
	def initialize (a_this, a_constructor):
		a_this.i_constructor = a_constructor
	
	def selfMaintain (a_this):
		a_this.i_constructor.constructBuilding ()
		return True
	
	def supplyPower (a_this):
		sys.stdout.write ("### Performing the service of PowerCompanyAWithoutStaticTypesChecking.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/Test1Test.py

@ ソースコード
from typing import List
from typing import Type
import sys
# the without static types checking test Start
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorAWithoutStaticTypesChecking import ConstructorAWithoutStaticTypesChecking
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyAWithoutStaticTypesChecking import PowerCompanyAWithoutStaticTypesChecking
# the without static types checking test End

class Test1Test:
	@staticmethod
	def main (a_arguments):
		Test1Test.test ()
	
	@staticmethod
	def test ():
		# the without static types checking test Start
		l_constructorAWithoutStaticTypesChecking = ConstructorAWithoutStaticTypesChecking ()
		l_powerCompanyAWithoutStaticTypesChecking = PowerCompanyAWithoutStaticTypesChecking ()
		l_constructorAWithoutStaticTypesChecking.initialize (l_powerCompanyAWithoutStaticTypesChecking)
		l_powerCompanyAWithoutStaticTypesChecking.initialize (l_constructorAWithoutStaticTypesChecking)
		
		l_constructorAWithoutStaticTypesChecking.constructBuilding ()
		l_powerCompanyAWithoutStaticTypesChecking.supplyPower ()

if __name__ == "__main__":
	Test1Test.main (sys.argv)

純Python的には、'ConstructorAWithoutStaticTypesChecking'が'PowerCompanyAWithoutStaticTypesChecking'をインポートする必要も、'PowerCompanyAWithoutStaticTypesChecking'が'ConstructorAWithoutStaticTypesChecking'をインポートする必要もない。

しかしながら、私はその世界の住民ではない


3: サイクリックな依存は極めて自然である、ドメインモデル的には、もしくは、当面そう言っておく


Hypothesizer 7
サイクリックインポートをどのように解消するべきかについてのアドバイスを求めた人に対するよくある断定は、「お前のデザインが悪いに違いない!」だ...

えーと、その断定は、完全に間違っているというわけではないとしても、とても誤解を招きやすいものだ。

私は言明するが、ドメインモデル的には、サイクリックな依存は極めて自然である。

上記の例において、その建設会社は、その電力会社からサービスを受ける(なぜ、いけないのか?その建設会社は(またはほとんどどんな会社も)極めて自然なことに、いくらか電力を使い、その電力は普通、ある電力会社から取られるが、その電力会社はなぜその特定の電力会社であってならないのか?)。その電力会社は、その建設会社からサービスを受ける(なぜ、いけないのか?その電力会社は(またはほとんどどんな工業主体も)極めて自然なことに、いくつかの設備を持たなければならず、その設備は普通、ある建設会社によって建てられるが、その建設会社はなぜその特定の建設会社であってならないのか?)。

相互寄与は、むしろ標準であり、問題ではない。

上記例は、クラス間の関係についてのものであるが(実際には、その例では、各クラスはそれ自身のモジュール内にあるということになっている)、事情は、モジュール間の関係においても違わない: クラスかモジュールかは、単に粒度の問題にすぎない: クラスレベルにおける実体間の相互寄与が標準であるのに、モジュールレベルにおける実体間の相互寄与が、なぜ突然、邪悪だということになるのか?なぜ、コレクションを操作するモジュールとストリームを操作するモジュールが相互に寄与すべきではないのか?

注目すべきだが、あるモジュールが他のモジュールから使われていないということは、そのモジュールは、他のモジュールにとって無用のものだということだ。したがって、使われるのは良いことなのだ。...想像してほしいのだが、あなたが、他の誰かによるある役立つモジュールをを用いて役立つモジュールを作成したとして、なぜ、その誰かは、あなたの役立つモジュールをその人のモジュールで使用していけないのだろうか?サイクリックな依存は邪悪だから?


4: サイクリックな依存は本当に悪いのか、コード的に?


Hypothesizer 7
それでは、サイクリックな依存は、コード的にのみ、悪いのか?

私の意見では、ドメインモデル構造を忠実にコードに実現させるのが理想であって、もしも、あるプログラミング言語がその構造では問題だというのであれば、そのプログラミング言語が残念なのだ。

サイクリックな依存はタイトカップリングを意味するという主張を多く見たが、適切な相互寄与が、強い一方的依存よりも、なぜ、よりタイトなカップリングであるのかを私は理解しない。それに、たとえ、それが、よりタイトなカップリングだとして、だから何なのだ?ドメインモデルがその相互寄与を求めているのに、なぜ、私は、その関係を歪めるべきなのか?...実のところ、サイクリックな依存を防ぐ最強の構造は、巨大な単一モノリシッククラスだが(だって、そのクラス以外にクラスなどないのだから、依存するべき先が全然ない)、それが...何と比べて良いというのか?

結論として、コードにおけるサイクリックな依存が必然的に悪いとは私は思わないが、当該プログラミング言語がサイクリックな依存に対処できないというのであれば、それは残念なことであり、サイクリックな依存を取り除く以外にオプションがないということになる。


5: 「モジュールを合体させろ」、「ものをモジュール間で移動させろ」といったアドバイスについて


Hypothesizer 7
優勢なアドバイスは、「2つ(またはより多く)のモジュールを合体させればいい」というものだ...

本当に?それが、「良いデザイン」とお呼びになっているものですか?...それは、確かに、危急のエラーを黙らせるかもしれないが、それでは、ドメインモデルから継承したコード構造を歪めてしまう。...ある残念なプログラミング言語がサイクリックインポートに対処できないからっといって、ただ、ものをごた混ぜにするというのには、私は同意しない。

それに、モジュールを合体させることは、別のサイクリックインポートを引き起こしがちだ。例えば、'ClassA' -> 'ClassB' -> 'ClassC'は、'ClassA'、'ClassB'、'ClassC'がそれぞれ'moduleA'、'moduleB'、'modueC'にあるときは問題なかったが、合体された'mnoduleAAndC'に'ClassA'および'ClassC'がある今は問題になった。...それで、今度はどうします?勿論、'moduleB'と'moduleAAndC'も合体させるべきだと? ...えーと、押し詰めると、「全てを単一モジュールにごた混ぜにしてしまえ!」になる。...それが、「良いデザイン」とお呼びになっているものですか?

「ものをモジュール間で移動させればいい」といったアドバイスも頻繁に見る。

それも、コード構造を歪めてしまうだろう。

そうしたアドバイスには、あまり良心というものを検知することができない。


6: なぜサイクリックな依存が現れたり現れなかったりするのかを要約し、正しい方針を選択する


Hypothesizer 7
あらゆるものがごた混ぜになっている最悪なコードは、サイクリックな依存を引き起こし得ない。

ドメインモデルにしたがってものを分割してコードを改善することによって、サイクリックな依存が現われるようになる。

それでは、どうするか、もしも、それらのサイクリックな依存が問題であるならば?

一部の人々は、コードを再び悪く(または最悪にさえ)する、再びものをごた混ぜにしたり、ものをたらい回しにすることによって(それが、一部の優勢なアドバイスが勧めていることだ)。

別のオプションもある: コードをより良くする、ドメインモデルを洗練させ、それにともなって、コード構造も洗練させる。

はあ?私は気狂いなのか?

ドメインモデルにおけるサイクリックな依存は自然である、と私は言ったが、実際には、サイクリックな依存は、あるテクニックによって取り除くことができる(少なくとも、大抵は)。

それが、正しい方針だろう。

「サイクリックな依存が現われるのは、デザインが悪いからだ」と誰かが言うとき、もしも、その人が言っているのが「デザインが、サイクリックな依存を無くすまで十分洗練されていない」ということであるのであれば、多分、私はその人と同意見だが、もしも、その人の引き続くアドバイスが「モジュールを合体させろ」や「ものをモジュール間で移動させろ」であるならば、その人は 別のことを言っているものと私は結論せざるを得ないだろう。


7: モジュール毎に1クラスという指針が以後仮定されているが、...


Hypothesizer 7
私は複数のクラス(インナークラスを除き)1つのモジュールに入れない、なぜなら、モジュール毎に1クラスという方針が、ものを整理するのに最も簡単であり(少なくとも、私にとっては)、私が使う他のプログラミング言語(特に、Java)のやり方にも整合しており、サイクリックインポートを避けるのにも役立つ: 'ClassA' -> 'ClassB' -> 'ClassC'は、'ClassA'と'ClassC'を1つのモジュールに入れるからこそサイクリックインポートになる。

そこで、以降では、1つのモジュールにはたった1つのクラス(インナークラスを除き)が入れられると仮定される。

しかしながら、それは、以降で紹介されるテクニックがその方針のみのためであることを意味しない。

以降のテクニックは、クラス間のサイクリックな依存を解消するためのものであるが、クラス間のサイクリックな依存が解消されれば、サイクリックインポートは、そうしたクラスをモジュールに適切に整理することで解消できる。


8: サイクリックな依存を、抽象化とインジェクションを用いることで解消する


Hypothesizer 7
大抵、サイクリックな依存は、抽象化を用いることで解消できる。

上掲の建設会社-電力会社の例を考えてみよう。

具象である'ConstructorAOriginal'および'PowerCompanyAOriginal'は、確かに、お互いに依存しているが、'ConstructorAOriginal'が'PowerCompanyAOriginal'のサービスを受けるとき、'PowerCompanyAOriginal'がセルフメンテナンスのために何をするかはどうでもよい: その電力会社が電力を良好に供給してくれればそれで結構だ(「良好に」という中には、料金等が含まれている)。実のところ、その電力会社は、'PowerCompanyAOriginal'である必要はない、もしも、別の会社のサービスがより良いのであれば。

そこで、1つの戦略は、任意の電力会社一般を考えることであり、それが、抽象化だ。...それが、当該のサイクリックな依存とどう関係するのか?抽象的な電力会社は、具体的な電力会社が抱える複雑な事情なしに(特に、ある建設会社に依存するということなしに)、ただ電力を供給するだけである。したがって、もしも、'ConstructorAOriginal'がその抽象的な電力会社にのみ依存すれば、サイクリックな依存は消え去るだろう。

同様の抽象化を建設会社にて対しても行なうと、改善後のコードは以下のようになる、'~Original'の代わりに'~Modified'を使って。

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/Constructor.py

@Python ソースコード
from abc import ABCMeta
from abc import abstractmethod

class Constructor (metaclass=ABCMeta):
	@abstractmethod
	def constructBuilding (a_this: "Constructor") -> bool:
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/PowerCompany.py

@Python ソースコード
from abc import ABCMeta
from abc import abstractmethod

class PowerCompany (metaclass=ABCMeta):
	@abstractmethod
	def supplyPower (a_this: "PowerCompany") -> bool:
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/ConstructorAModified.py

@Python ソースコード
import sys
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.Constructor import Constructor
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompany import PowerCompany

class ConstructorAModified (Constructor):
	def __init__ (a_this: "ConstructorAModified") -> None:
		a_this.i_powerCompany: "PowerCompany"
	
	def initialize (a_this: "ConstructorAModified", a_powerCompany: "PowerCompany") -> None:
		a_this.i_powerCompany = a_powerCompany
	
	def selfMaintain (a_this: "ConstructorAModified") -> bool:
		a_this.i_powerCompany.supplyPower ()
		return True
	
	def constructBuilding (a_this: "ConstructorAModified") -> bool:
		sys.stdout.write ("### Performing the service of ConstructorAModified.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/PowerCompanyAModified.py

@Python ソースコード
import sys
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.Constructor import Constructor
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompany import PowerCompany

class PowerCompanyAModified (PowerCompany):
	def __init__ (a_this: "PowerCompanyAModified") -> None:
		a_this.i_constructor: "Constructor"
	
	def initialize (a_this: "PowerCompanyAModified", a_constructor: "Constructor") -> None:
		a_this.i_constructor = a_constructor
	
	def selfMaintain (a_this: "PowerCompanyAModified") -> bool:
		a_this.i_constructor.constructBuilding ()
		return True
	
	def supplyPower (a_this: "PowerCompanyAModified") -> bool:
		sys.stdout.write ("### Performing the service of PowerCompanyAModified.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/Test1Test.py

@Python ソースコード
from typing import List
from typing import Type
import sys
# the modified test Start
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorAModified import ConstructorAModified
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyAModified import PowerCompanyAModified
# the modified test End

class Test1Test:
	@staticmethod
	def main (a_arguments: List [str]) -> None:
		Test1Test.test ()
	
	@staticmethod
	def test () -> None:
		# The modified test Start
		l_constructorAModified: "ConstructorAModified" = ConstructorAModified ()
		l_powerCompanyAModified: "PowerCompanyAModified" = PowerCompanyAModified ()
		l_constructorAModified.initialize (l_powerCompanyAModified)
		l_powerCompanyAModified.initialize (l_constructorAModified)
		
		l_constructorAModified.constructBuilding ()
		l_powerCompanyAModified.supplyPower ()
		# The modified test End

if __name__ == "__main__":
	Test1Test.main (sys.argv)

'ConstructorAModified'は、抽象的な'PowerCompany' に関知するのであって、具象の'PowerCompanyAModified'には関知しない。'PowerCompanyAModified'は、抽象的な'Constructor'に関知するのであって、具象の'ConstructorAModified'には関知しない。したがって、問題のサイクリックな依存は消え去った!

その方法では、ドメインモデルは、全然歪められることはなく、順応性が高まった、他の建設会社や電力会社を首尾一貫したやり方で導入できるようにして。

注目すべきは、具象の電力会社インスタンスは'ConstructorAModified'にインジェクトされているということだ: もしも。'ConstructorAModified'が具象電力会社をインスタンス化しようとしていたら、それは、具象クラスを使用せざるを得なかっただろう(ファクトリーを用いても、問題を解決しないだろうう、なぜなら、そのファクトリーがその具象クラスを結局使うだろうから、'ConstructorAModified'はその具象クラスを使うことになるだろう、間接的にではあっても)。...確かに、'ConstructorAModified'インスタンスは'PowerCompanyAModified'インスタンスを使うが、クラス的には、'ConstructorAModified'は、具象の電力クラスには全然関知しない。

実は、上記例に対して、別の抽象化方法がある、以下のように。

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/Company.py

@Python ソースコード
from abc import ABCMeta
from abc import abstractmethod

class Company (metaclass=ABCMeta):
	@abstractmethod
	def performService (a_this: "Company") -> bool:
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/ConstructorAModifiedAgain.py

@Python ソースコード
import sys
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.Company import Company

class ConstructorAModifiedAgain (Company):
	def __init__ (a_this: "ConstructorAModifiedAgain") -> None:
		a_this.i_powerCompany: "Company"
	
	def initialize (a_this: "ConstructorAModifiedAgain", a_powerCompany: "Company") -> None:
		a_this.i_powerCompany = a_powerCompany
	
	def selfMaintain (a_this: "ConstructorAModifiedAgain") -> bool:
		a_this.i_powerCompany.performService ()
		return True
	
	def performService (a_this: "ConstructorAModifiedAgain") -> bool:
		sys.stdout.write ("### Performing the service of ConstructorAModifiedAgain.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/PowerCompanyAModifiedAgain.py

@Python ソースコード
import sys
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.Company import Company

class PowerCompanyAModifiedAgain (Company):
	def __init__ (a_this: "PowerCompanyAModifiedAgain") -> None:
		a_this.i_constructor: "Company"
	
	def initialize (a_this: "PowerCompanyAModifiedAgain", a_constructor: "Company") -> None:
		a_this.i_constructor = a_constructor
	
	def selfMaintain (a_this: "PowerCompanyAModifiedAgain") -> bool:
		a_this.i_constructor.performService ()
		return True
	
	def performService (a_this: "PowerCompanyAModifiedAgain") -> bool:
		sys.stdout.write ("### Performing the service of PowerCompanyAModifiedAgain.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/Test1Test.py

@Python ソースコード
from typing import List
from typing import Type
import sys
# the modified-again test Start
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorAModifiedAgain import ConstructorAModifiedAgain
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyAModifiedAgain import PowerCompanyAModifiedAgain
# the modified-again test End

class Test1Test:
	@staticmethod
	def main (a_arguments: List [str]) -> None:
		Test1Test.test ()
	
	@staticmethod
	def test () -> None:
		# The modified-again test Start
		l_constructorAModifiedAgain: "ConstructorAModifiedAgain" = ConstructorAModifiedAgain ()
		l_powerCompanyAModifiedAgain: "PowerCompanyAModifiedAgain" = PowerCompanyAModifiedAgain ()
		l_constructorAModifiedAgain.initialize (l_powerCompanyAModifiedAgain)
		l_powerCompanyAModifiedAgain.initialize (l_constructorAModifiedAgain)
		
		l_constructorAModifiedAgain.performService ()
		l_powerCompanyAModifiedAgain.performService ()
		# The modified-again test End

if __name__ == "__main__":
	Test1Test.main (sys.argv)

'ConstructorAModifiedAgain'と'PowerCompanyAModifiedAgain'の両方が、一種のサービスを提供するただの会社として抽象化されている。

本ケースにおいては、後者の方法のほうが、前者の方法よりも良いように思われる、しかし、私が前者の方法を紹介したのは、後者の方法は、本ケースに独特な性質がゆえに可能になっているにすぎないからだ。

より長いコードを書くことを強いられるので、'この瞬間のために生きる'人々はこの正しい方法を避けるであろうことを、私は知っているが、ソースコードの長さは、おおむね、感情的な問題だ: プログラミングの主要部分は、考えることであって、タイピングすることではない。したがって、タイピング量を減らすのは、本当はそれほど重要でない。良く構造化されたコードを書くことが、長い目で見ればより重要なのだ、...しかし、"「ダックタイピング」が素敵だと考える人々のほとんどは、ただより短いコードを書ければそれでハッピーなのであろうことを私は知っている。


9: 結びとその先


Hypothesizer 7
サイクリックな依存は、極めて自然であり、邪悪ではない、ドメインモデル的に言って。

しかし、ドメインモデルにおけるサイクリックな依存は、抽象化を行ない、ドメインモデルの順応性を高めることによって、大抵、消し去ることができる。

それが、サイクリックな依存を解消するための正しい方法であろう、ものをごた混ぜにしたりたらい回しにすることではなく。

その解決策は、Pythonに限定されるものではなく、どの典型的なプログラミング言語にも適用できる、それを、サイクリックな依存をうまく取り扱うことができないPythonのコンテキストにて論じたが。

私は、Pythonにいくつか長所があることを認めるが、気の利かない点がいくつかあることも認める(オーバーロードできないこと、あらゆるインスタンスメンバーの出現を「self」で修飾しなければならないこと、グローバルインタープリターロック(これはただ気が利かないというよりは、致命的に近い)が即座に思い浮かば)。私は、Pythonの宣伝マンではないので、そのような気の利かなさを正当化したりせず、それについて率直に話すだろう、将来の記事にて。


参考資料


<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>