見出し画像

初デザインパターン学習はPythonよりもJava, C# のほうがいいかもしれない

とりあえずHead First本をJavaからC# ではなくてPythonに変換して始めからやり直してみることにした。結論から言えばPythonでもObject-Oriented Programming (OOP) ができた。できたんだが……。

(約 3,500文字の記事です。)



ChatGPTでJavaをPythonに変換してみた

Head First本の第1章の、Strategyパターン。Ducksの完成コード。C# の記法のままになっている部分として、

  • メソッド名が大文字スタート(C# が採用しているアッパーキャメルケースがレア?Java, Pythonはロワーキャメルケース。こっちが主流かも?)

  • Interfaceクラスの頭に「大文字のアイ(I)」を付加済み。これはC# の慣習的な記法らしい。

  • フィールド変数の頭にアンダースコアを付加済み。これもC# の慣習的なことで必須ではないらしい。個人的にはぱっと見で「クラス内のフィールド変数なのか、クラス外部から受け取った引数なのか」が一瞬で分かるので好き。C# ではフィールド名の頭に_を付ければthis. を省略可能。(Pythonでのself. は省略不可)

いずれもエラーにはならない。言語ごとの記法の違い。

from abc import ABC, abstractmethod


# Interface for FlyBehavior
class IFlyBehavior(ABC):
    @abstractmethod
    def Fly(self):
        pass


# Interface for QuackBehavior
class IQuackBehavior(ABC):
    @abstractmethod
    def Quack(self):
        pass


# Duck abstract class
class Duck(ABC):
    def __init__(self):
        self._flyBehavior = None
        self._quackBehavior = None

    def SetFlyBehavior(self, flyBehavior: IFlyBehavior):
        self._flyBehavior = flyBehavior

    def SetQuackBehavior(self, quackBehavior: IQuackBehavior):
        self._quackBehavior = quackBehavior

    @abstractmethod
    def Display(self):
        pass

    def PerformFly(self):
        self._flyBehavior.Fly()

    def PerformQuack(self):
        self._quackBehavior.Quack()

    def Swim(self):
        print("All ducks float, even decoys!")


# Concrete Fly Behaviors
class FlyWithWings(IFlyBehavior):
    def Fly(self):
        print("I'm flying!!")


class FlyNoWay(IFlyBehavior):
    def Fly(self):
        print("I can't fly")


class FlyRocketPowered(IFlyBehavior):
    def Fly(self):
        print("I'm flying with a rocket")


# Concrete Quack Behaviors
class Quack(IQuackBehavior):
    def Quack(self):
        print("Quack")


class MuteQuack(IQuackBehavior):
    def Quack(self):
        print("<< Silence >>")


class Squeak(IQuackBehavior):
    def Quack(self):
        print("Squeak")


# Concrete Ducks
class MallardDuck(Duck):
    def __init__(self):
        super().__init__()
        self._quackBehavior = Quack()
        self._flyBehavior = FlyWithWings()

    def Display(self):
        print("I'm a real Mallard duck")


class ModelDuck(Duck):
    def __init__(self):
        super().__init__()
        self._flyBehavior = FlyNoWay()
        self._quackBehavior = Quack()

    def Display(self):
        print("I'm a model duck")


class RubberDuck(Duck):
    def __init__(self, flyBehavior=None, quackBehavior=None):
        super().__init__()
        self._flyBehavior = flyBehavior if flyBehavior else FlyNoWay()
        self._quackBehavior = quackBehavior if quackBehavior else Squeak()

    def Display(self):
        print("I'm a rubber duckie")


class DecoyDuck(Duck):
    def __init__(self):
        super().__init__()
        self._flyBehavior = FlyNoWay()
        self._quackBehavior = MuteQuack()

    def Display(self):
        print("I'm a duck decoy")


class RedHeadDuck(Duck):
    def __init__(self):
        super().__init__()
        self._flyBehavior = FlyWithWings()
        self._quackBehavior = Quack()

    def Display(self):
        print("I'm a real Red Headed duck")


# MiniDuckSimulator
if __name__ == "__main__":
    mallard = MallardDuck()
    rubberDuckie = RubberDuck()
    decoy = DecoyDuck()
    model = ModelDuck()

    mallard.PerformQuack()
    rubberDuckie.PerformQuack()
    decoy.PerformQuack()

    model.PerformFly()
    model.SetFlyBehavior(FlyRocketPowered())
    model.PerformFly()
    rubberDuckie.SetFlyBehavior(FlyRocketPowered())

(Mainの中で自分で少しテストしたコードも入っています。)

PythonとC# のOOPの違い

いきなり色々違って面食らった。

  1. クラスにAbstract, Interfaceの宣言がない
    ChatGPT曰く「ABCを継承して使え」とのこと。

  2. Interfaceの概念はないらしい。え😱???
    デザインパターンを実装する上ではかなり致命的では……?(困惑)

  3. self. は省略できない。必須とのこと。

  4. 事前にフィールド変数の宣言なしに、突然いきなり使い出せる(困惑)

  5. new演算子がない。
    インスタンス化のタイミングはかなり重要だが🤔

  6. PythonではC# のようにprivate, protected, internal に相当するアクセス修飾子がない。

    • privateではダブルアンダースコアによる名前マングリング。

    • protectedではシングルアンダースコアによる注意喚起的な仕組み。

    • internal相当はないらしい。Copilot調べ。

  7. メソッドの書き出しがdefだけなので逆に面食らう。public? private? メソッドのoverride, virtualはどうなるのか謎。逆に可読性が悪い気が。

とりあえず細かい記法の違いを除けば、上記コードで動いたのでOOPを実践できることは確認できた。

だがしかし……。

Pythonの悪いところも若干分かった気がする。


PythonとC# の違いに戸惑う

初心者向けに色々簡略化されているのがPythonの特徴。{}ブロックもなくなってインデントでブロックを意味する仕組みだとか、メソッドの宣言で文字数が少なくて済む仕組みとか。ローカル変数も事前の宣言なしにいきなり使える点も、C# のカッチリとした「全部事前に(コンストラクタの前に)準備しておく」ことに慣れていると、コードを読んでいて突然、謎の変数が出てくるとビックリする😖

Pythonのシンプルさは、OOPを実践する上では逆にシンプル過ぎて可読性が低くなっているような気がした。JavaもC# もコードがかなり似ているので紙面を見てもVisual Studioのコードを見ても、だいたい一緒。だがPythonになるといきなり色々違うので目が点になる。OOPではカプセル化が重要なので、メソッドや変数がpublic, privateはとても重要。なのでアンダースコア表示よりも普通に文字で表記の方が読みやすいし分かりやすい。

あと賛否両論あると思うが、私は個人的にはメソッド名はUpperCamelCaseのほうが読みやすい。C# 採用の記法ね。頭から読んで大文字なら「変数ではない=ほぼほぼメソッド=関数なはず」ということが一瞬で分かるので。後ろに()がくればメソッドだと確定するが、コードを読んでいるときに大文字1つでメソッドだと先読みできるのは嬉しい。

だがJava, PythonではlowerCamelCaseなので、小文字で読み始めると単語を読み進めないと変数か関数か分からない点がちょっと嫌かな。

あとメソッドとクラスの違いは大文字・小文字の違いよりも記載位置でだいたい分かるのでつらいと思ったことはないなぁ。


【現時点での感想】Pythonで初デザインパターン学習は初心者向けではないかも?

素直にそう思った。(OOP学習ではなくて)デザインパターン学習の本はなぜかJavaが多い。理由は不明。そして私の場合はJavaよりもC# がいいのだが、たまたまJavaもC# もかなりコードが似ている。なのでC# でもデザインパターン学習に支障はない。

だがPythonとなるとJavaとは結構違うので、頭に「思考切り替えの負荷」が結構かかる。これは意外だった。あとファイルの管理方法もPythonの方が緩いゆえに、初心者は学習中に「ファイルをどうやって分割・管理すればいいの?」にぶつかると思う。C# ,Javaは基本的には1クラスが1ファイル(クラス名=ファイル名.cs またはファイル名.java が基本らしい)。

あとエディタの特性。PyCharmでクラスやメソッドの定義や実装位置への往復が、慣れていないせいかストレスを感じる。頻繁に切替えるのでここをサクサクできないとイライラする😅

こういう細かい違いがことあるごとに表出すると、学習ストレス以外で脳に負荷がかかるのが、ちょっと嫌だなと思った。なのでPythonでのHead First本学習作戦はいったんお蔵入り😭ちょっとつらい。

なのでデザインパターン学習は再び慣れたC# で進めることにする。

PyCharm Proのクラス図がバグで矢印が逆

これ、一度は過去に直ったらしいが今また元に戻っている。継承の矢印が逆。というか全部の矢印が逆😭

https://youtrack.jetbrains.com/issue/PY-57722

(ブログカードが表示されないのでリンクのみ)

あとクラス名表記もなぜかmain. というプレフィックスが……。要するに、ちょっと普通のUML図と違う。コレジャナイ。色々惜しい。

Csharp to PlantUMLの出力はこちら。
A. abstract class, I. interface class, C. concrete class.



【作戦変更】そもそもBlenderアドオンをOOPで作るのか?という素朴な疑問

Blenderアドオンは結局はBlender APIを使わざるを得ないという縛りがあるので、実はOOPを使うような大規模な仕組みにならないのでは?とふと思った。ならばプロシージャル+可読性UPのためのモジュラープログラミングで十分だとすれば、「今、私にとっての茨道のPython+デザインパターン学習」をあえて選ぶ必要はないような気がした。

OOP学習は効率を考えてC# でコツコツ進めて、Blenderアドオン開発はこれまで通りのプロシージャルなモジュラーコードでアドオンを開発して動けばそれでよし!なんじゃないか?と🤔

ならば何でもかんでも1つのプログラミング言語に絞る必要はないな、と。OOP学習はC# で、BlenderアドオンはPythonで。使い分けようと思い直したわけです。

それに個人的な直感として、プロシージャルコードはもうChatGPTと協業して作り出せることが分かっているので。実際の体験からそう判断した。であればPythonでのOOP実装する日は私にとっては多分来ないと思うので、わざわざPythonでのOOP学習するのは私の場合は悪手だと思った。

自分にとってはC# での学習進行の方が合っていると思ったわけです😊


まとめ

とりあえず今日の成果のまとめ。

  • PythonでもOOPは実現可能

  • だがデザインパターン学習はJava主流の教本などと比べて不利かも。

  • Python特有の実装がデザインパターン学習の初心者キラーかも?

  • JavaもC# もかなり似ているので学習の障害にはならない

  • PyCharm Proのダイアグラムはバグで矢印が逆

  • BlenderアドオンをOOPで作るつもりがないならばあえてPythonでOOP学習をするメリットが私にはない(あとは各自の状況で判断してね)

  • プロシージャルコード作成にはChatGPTと協業できることを実証済み

なので1周回ってOOP学習は再びC# に戻ってきました。😊

色々試せば結論は変わるものです。

ソフトウェアにおいて絶対に変わらない真実、それは「変化する」ということ。その変化に柔軟に対応するための術の一つがOOP。Head First本に書いてある。なので色々検討した結果、結論が変わるのは自然なこと

頑なに変えないことは、ときには悪手となる。

というわけでC# でOOP学習を継続することにしました~🎉


今回の創作活動は約1時間15分(累積 約3,927時間)
(1,169回目のnote更新)

筆者はAmazonアソシエイト・プログラムに参加しています。(AmazonアソシエイトとはAmazon.co.jpの商品を宣伝し所定の条件を満たすことで紹介料をAmazon様から頂けるという大変ありがたい仕組みのこと。)
以下のリンクを経由してAmazonでお買物をするとその購入額の1~3%ほどのお小遣いが私に寄付されます。誰が何を買ったという情報は私には通知されませんのでご安心下さい😊 以下のリンクを経由して頂ければ紹介商品以外のご購入でもOKですよ~。


いいなと思ったら応援しよう!

大和 司
読んでくれてありがとう。気長にマイペースに書いてます。この出会いに感謝😊