言い訳とはじめに

Tくんアドカレの順番変わってくれてありがとう.最近C++が親友な黒岩です.

僕がPythonのDIライブラリを使ってみたかっただけなので,多分わからないです.

ブラウザバックを推奨します.いや,もはや読むな.まあアメブロってプログラム書く場所でもないっすね.キレました.結構段階的にコード書いたりしたんですけどね.載せられないんで,この辺に興味を持つきっかけになればいいです.詳しくは聞きにきたら何時間でも付き合います.いやあ,Qiitaに書くべきなんでしょうね.............ここまで読んでしまいましたね.始めます.

 

人は,何かに依存して生活しています.依存は必ずありますが,過度に依存することは良くありません.依存とはうまく付き合う必要があります.プログラムも同じです.

本題

依存性逆転の法則とはwikiより,以下の通りです.

  1. 上位モジュールはいかなるものも下位モジュールから持ち込んではならない。双方とも抽象(例としてインターフェース)に依存するべきである。
    "High-level modules should not import anything from low-level modules. Both should depend on abstractions (e.g., interfaces)."[3]
  2. 抽象は詳細に依存してはならない。詳細(具象的な実装内容)が抽象に依存するべきである。
    "Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions."[3]

何を言ってんだという感じでしょう.まあ大切なのは,抽象に依存しましょうってことです.

 

キーボードをPCに接続する時を考えてみましょう.

下位モジュールは特定のキーボード,上位モジュールは特定のPCです.抽象はUSBですね.依存性逆転の法則に違反すると言うのは,特定の PCに向けてキーボードを作ってしまうのです.つまり,USBの規格から逸脱する.(なんか,新しいポートが必要そうな気がしますね.)そのキーボードは,そのPCでしか使えないといったことが起きます.

 

僕たちは宇宙に関する団体なので,宇宙ぽいことを題材に依存性について書いてみたいと思います.発電量計算のために.衛星パネルと太陽光のなす角の余弦を求めるコードを書いてみました.

このプログラムはcalculatorから軌道クラスorbitと姿勢クラスattitudeを用いて,1パネルのなす角を求めるプログラムです.

 

最終的にこんなプログラムを書きました.

calculator.py - メイン 軌道と姿勢のクラスを使って計算する

bindings.py -  DIライブラリの設定

IOrbit.py - 軌道クラスのインターフェイス

Orbit.py - β角を用いた軌道クラス (https://ameblo.jp/sssrc/entry-12716333438.html)

SkyfieldOrbit.py - skyfieldを用いたTLEから計算した軌道のクラス(時間は合わせてない)

IAttitude.py - 姿勢クラスのインターフェイス

Attitude.py - 姿勢クラス

 

 

何も考えずにプログラムを書くとすると,依存関係はこのようになるでしょう.(もちろん,こういった場合は規模がかなり小さいので,これでいいです.)

 

この時の問題点を考えてみましょう.

- クラスのどれかに変更があったときに,その影響が他にも伝播する.

- テストができない

- 密です

 

では,抽象に依存させてみましょう.抽象はインターフェイスを作ります.インターフェイスとは,クラスが持つべきメソッドを規定するものです.USBも規格がありますね.ちなみにPythonはABCという抽象規定クラスを定義できるやつがありますが,僕はあんまり好きじゃないので,必要なメソッドを定義してraise Not Implemented Errorとしてます.こうする人も結構いると思います.監修的にInterfaceの頭文字のIをとって名付けがちです.OrbitとAttitudeのインターフェイスをIOrbitとIAttitudeとしてしまいましょう.さあ,依存関係はどのようになるでしょうか...水色がインターフェイスになります.

 


 

濃い青色同士が直接つながらないのが確認できるでしょう.こうすることで,プログラム同士が疎結合になります.
 

 

では,AttitudeクラスがIOrbitクラスに依存していますが,プログラムではどのようになっているのでしょうか.簡単に思いつくのは,OrbitクラスをAttitudeクラスで生成するとかですが,それではいけません.依存性逆転の法則に反してますね.では,コンストラクタで与えるのはどうでしょう.今回は.こんな感じで描いてみました.コンストラクタで,orbitを受け取りインスタンス変数に保持しています.コンストラクタの型ヒントをIOrbitとすることで,エディタも抽象(IOrbit)に対してのメソッドを表示してくれます.ちなみに,pythonで型ヒントを使ってないやつはヤンキーです.更生しましょう.僕に聞きに来てください.

こうしていると,IOrbitクラスを継承したクラスならこのAttitudeクラスをうまく繋がることがわかります.実際にOrbitクラスと新しく作ったSkyfieldOrbitクラスを比べてみましょう.
 
インターフェイスで定義したIOrbitをオーバーライドする形で同じメソッドが定義されていますね.こんなの実装中にミスっちゃうよー!って言う.そこのあなたはインターフェイスの魔力にまだ気づいていません.IOrbitのインターフェイスを見てみましょう.
依存性逆転の法則に違反せずに書けば,他のモジュールがIOrbitだと思ってメソッドを呼びます.同じ名前の関数でオーバライドされないとNotImplementedErrorが起きます.動かないコードをできたと思う人間はいないですからね.(ただし,JavaやC#ほどの強制力がないのは事実です.mypyを使ってエラーの表示などを工夫して初めて効果が出ます.やっぱりmypy使ってないとダメです.更生しましょう.)こういった形で,クラスを書いているとテストがしやすくなります.最近はcopilotがテストを生成してくれますね.github coplilotは学生なら使いましょう.

 


さあ最後です.Calculatorでは,IOrbitとIAttitudeに対応するクラスを用いる必要があります.これが二つとかだといいんですが,複数増えてきて,IAttitude -> IOrbitだけでなくISolarPanel -> IAttitude , IOrbit,IBattery -> ISolarPanel, IEquipmentsなどと依存関係が増えてきたらどうでしょう??(まだまだ少ない.もっと増えます.)ちょっとやってられないですね.そこで活躍するのがDIライブラリです.今回は,以下のものを使ってみました.
 
では,実際に設定しているところと生成している場所を見てみましょう.
インターフェイスとクラスの結びつきをここで設定します.scopeは,そのクラスのライフタイムとインスタンスを管理するために必要です.今回設定しているのは,アプリケーションのライフタイム中に一度だけ生成されます.もし何個も生成されると,姿勢が依存する軌道と,太陽電池が依存する軌道が違うなんてことに!!!!!!!!!!!!!
 
このような設定をすると,以下のようにmain側でクラスを簡単に生成できます.(@injectは他のクラスにもつける必要はある.)
 
 
この一覧の流れで,DIおよびDIライブラリを用いたソフトウェアが作成できます.このCalculatorは結構テキトーに書きました.

こういう話をするとコードが書きにくくなっちゃう人へ
世間では,クソコードと呼ばれるものがあります.僕もなんだこれ?ってものに会いましたし,書きました.クソコードはストーリーがあります.期限や納期が差し迫っていた.技術がなかったなどなど.そのストーリーを考えてみると,微笑ましいものです.また,そのストーリの中で最適出力がそのコードです.その時の全力で書いたそれなりに動くコードです.クソコードなんて言わずに,可愛がりましょう.あとは俺に任せなという気持ちで取り組んでやりましょう.それでは本当のクソコードでお別れしましょう.これだ!!!!!!!!
 
import numpy as math
import math as np