質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

デザインパターン

デザインパターンは、ソフトウェアのデザインでよく起きる問題に対して、解決策をノウハウとして蓄積し再利用出来るようにした設計パターンを指します。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

881閲覧

多重のif文が大量にあるコードをオブジェクト指向で書く(クラス設計)

lukkio

総合スコア4

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

デザインパターン

デザインパターンは、ソフトウェアのデザインでよく起きる問題に対して、解決策をノウハウとして蓄積し再利用出来るようにした設計パターンを指します。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

4クリップ

投稿2022/11/22 00:27

編集2022/12/18 04:46

前提

プログラミングの初心者です。
多重のif文があるコードを、オブジェクト指向で書き直すには、
どういった書き方をすれば良いでしょうか?

コード例では3Dと2Dのそれぞれの球形、直方体をオブジェクトとして持ちたいです。
現段階では図形に依存するshape.print_shape()の命令を、オブジェクトが自身の図形を知っているのでポリモーフィズムの実現によってメソッドを切り替えられますが、次元は変数として持っていなければならず、次元を増やしたときにif文の改定が必要になるのでメンテナンス性に欠けます。(特に大量にクラスがあった場合すべてのif文に修正が必要)

->if文を使わずに次元の判断をメンテナンス性を維持した状態で付け加えたいです。
またはオブジェクト指向の考え方そのものや、構造がおかしいのであれば指摘していただきたいです。

実現したいこと

”もし3Dデータであり、球体であるなら~”
”もし2Dデータであり、直方体であるなら~”
をif文を使わずにオブジェクト指向で表現したい。

該当のソースコード

python

1from abc import ABCMeta, abstractmethod 2 3class Shape(metaclass=ABCMeta): # 図形のインターフェイス 4 @abstractmethod 5 def print_shape(self): 6 pass 7 8 def print_dimension(self): 9 pass 10 11 12class Globe(Shape): 13 def __init__(self, dimension): 14 self.dimension = dimension 15 16 def print_shape(self): 17 print('This is a Globe') 18 19 def print_dimension(self): 20 if self.dimension == 3: 21 print('This is a 3D object') 22 elif self.dimension == 2: 23 print('This is a 2D object') 24 25class Cube(Shape): 26 def __init__(self, dimension): 27 self.dimension = dimension 28 29 def print_shape(self): 30 print('This is a Cube') 31 32 def print_dimension(self): 33 if self.dimension == 3: 34 print('This is a 3D object') 35 elif self.dimension == 2: 36 print('This is a 2D object') 37 38 39if __name__ == '__main__': 40 data = Cube(2) 41 data.print_shape() 42 data.print_dimension() 43 44 data = Globe(3) 45 data.print_shape() 46 data.print_dimension() 47 48# これをif文を使わずに同じ結果を得たい

修正コード

回答で提案していただいた、委譲を使って書き直しました。
これが正しい使い方なのか、もっとうまく書く方法があるかなど、ご指摘いただければと思います。

python

1from abc import ABCMeta, abstractmethod 2 3 4class Dimension(metaclass=ABCMeta): # 次元のインターフェイス 5 @abstractmethod 6 def print_dimension(self): 7 pass 8 9class Shape(metaclass=ABCMeta): # 図形のインターフェイス 10 @abstractmethod 11 def print_shape(self): 12 pass 13 @abstractmethod 14 def print_dimension(self): 15 pass 16 17 18class Dimension2D(Dimension): # 2Dの次元 19 def print_dimension(self): 20 print('This is a 2D object') 21 22class Dimension3D(Dimension): # 3Dの次元 23 def print_dimension(self): 24 print('This is a 3D object') 25 26 27 28class Globe(Shape): # 球 29 def __init__(self, dimension): 30 self.dimension = dimension 31 32 def print_shape(self): 33 print('This is a Globe') 34 35 def print_dimension(self): 36 self.dimension.print_dimension() 37 38 39class Cube(Shape): # 立方体 40 def __init__(self, dimension): 41 self.dimension = dimension 42 43 def print_shape(self): 44 print('This is a Cube') 45 46 def print_dimension(self): 47 self.dimension.print_dimension() 48 49 50if __name__ == '__main__': 51 data = Cube(Dimension2D()) 52 data.print_shape() 53 data.print_dimension() 54 55 data = Globe(Dimension3D()) 56 data.print_shape() 57 data.print_dimension()

補足情報(FW/ツールのバージョンなど)

python 3.8.10
IED: Spyder IDE 5.4.0

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2022/11/22 00:29

違う種類のオブジェクトには違うクラスを作ってしかるべきだと思うのですが、現状では何が問題なのでしょうか?
int32_t

2022/11/22 00:34

「多重のif文が大量にあるコード」を見せてください。
退会済みユーザー

退会済みユーザー

2022/11/22 02:50 編集

そもそも該当のソースに問題があるのでは 例示用かつサンプルとして図形毎に出力する文字列を替えたいって物なのかもしれないけど そんなので学習しても正直納得できる学習にはならないかと 2Dなら面積と辺の長さを取得する関数があり、円・楕円、多角形・正方形・長方形・三角形・台形などで求め方が異なる 同様に3Dなら表面積と体積を求める関数があって、それぞれ計算の仕方が異なる だから図形の種類毎にクラスを作成する 2Dと3Dはそもそも得られる物が違うし、各クラスで実装すべきロジックも異なる そう言った例題じゃないとオブジェクト指向の学習に繋がらないかと もしこれが学校の教材なら文句言っていいレベル
lukkio

2022/11/22 18:07

皆様 ご回答ありがとうございます。まとめて返信させていただきます。 まず多重の”if文が大量にあるというのは質問に誤りがありました。慎重にわかりやすく質問したつもりでしたが、練れていなかったようで申し訳ありません。 質問を書き直してみますと、 2Dと3Dのデータで違う処理(例えば2Dと3Dで違うフィルター処理)をしたい時、張り付けたコードだと、filter メソッドを切り替える為にどこかに if dimension == 3 .....のように次元の違いによるif文を書かなければならず、 逆に2Dクラス 3Dクラスと作ってしまうと この例でいうとprint_dataメソッドにif side == 4.....などのように辺の数によるif文を書かなければならず、どうすればよいのかわかりませんでした。 そもそもクラス設計やオブジェクト指向の考え方を間違っているのでしょうか? これで質問の意図が伝わらないのであれば、もう少し練ってからまた質問し直したいと思います。
maisumakun

2022/11/23 01:22

> 2Dと3Dのデータで違う処理(例えば2Dと3Dで違うフィルター処理)をしたい時 それをまとめて行おうとするのが、そもそも構造として間違っているのではないでしょうか?
fana

2022/11/23 02:10

どんなものを作っているのか次第で「2D と 3D を区別する必要すらない」と考えることもできるかもしれないし,「2D と 3D は全く別のものである」として扱う必要があるのかもしれない.そのへんは事情を知らない他者には一概には言えないと思う. それら(2Dの四角形だとか3Dの球だとか?)のデータを取り扱う側(そいつらに対して何か処理をしようとする側)が各データに関して「これは2Dだ」とか「これは球だ」とかを知る(=区別する)必要があるのかないのか? とかを考えてみてはどうか. それらを実際にクラスとして実装するのかどうかはわからないけども,とにかくそれらの実装の内側ではなく外側においてどうあっても2Dと3Dの区別が要るのであれば,2Dと3Dは同じものとはみなせない(まとめること自体に無理がある)のかもしれない.
fana

2022/11/23 02:19

> 質問の意図が伝わらないのであれば に関して… そもそもの話として「何故 if を書くことを問題視しているの?」というのが多分伝わってない感. 実際問題として if で十分なのであればそこは素直に if で書いていれば良いのであり,そこにわざわざ良くわからん構造を導入して書き換えることに利があるのか?っていう. 「if だと実際にこういうことが困る→から,それを解決したいのだ」みたいな,書き直しの目的みたいなのを明確にした方が良いのでは.
lukkio

2022/11/23 16:34

>それをまとめて行おうとするのが、そもそも構造として間違っているのではないでしょうか? >これは2Dだ」とか「これは球だ」とかを知る(=区別する)必要があるのかないのか? とかを考えてみてはどうか. たしかに今までクラス設計とモデリングというものを同じものだと考えていましたが、全体の構造自体を見直すべきかもしれません。 >そもそもの話として「何故 if を書くことを問題視しているの?」というのが多分伝わってない感. 確かに今まで、オブジェクト指向でif文やフラグは可能な限り少なくしなければならないという思考にとらわれていました。(理想と実際の現場での違いということでしょうか?) 手続き型プログラミングで多重のif文で表されているものをオブジェクト指向で書き直すということはよくあることかと思い、なにか典型的な解決方法があるのかと考え質問しました。 >「if だと実際にこういうことが困る→から,それを解決したいのだ」 現段階では図形に依存するshape.print_data()の命令を、オブジェクトが自身の図形を知っているのでポリモーフィズムの実現によってメソッドを切り替えられるが、次元は変数として持っていなければならず、次元を増やしたときにif文の改定が必要になるのでメンテナンス性に欠ける。(特に大量にクラスがあった場合すべてのif文に修正が必要) ->これをshape.filter()のような命令を、オブジェクト自身に次元を判断させメンテナンス性を維持したい。 しかし、shape.print_data()とspape.filter()は判断材料が異なり(次元と図形)同じクラスで両立できないので、この競合を解決したい。 質問を書き直してみましたが、これはそもそもデザインパターンやクラスの切り分けみたいなものを駆使して解決するものではなく、お二方の指摘のようにモデリング自体の見直しが必要なのかと思い始めています。
guest

回答1

0

ベストアンサー

いまの質問文の print_dimension() ですと「print(f'This is a {self.dimension}D object') でいいでしょ」となります。

こういう self.dimension による分岐があちこちにあってそれを減らしたいということであれば、クラスを分ける(Globe2DGlobe3D など)か、クラスを分けずに委譲するなどでしょうか。

投稿2022/11/23 22:18

int32_t

総合スコア20670

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

lukkio

2022/11/24 18:27

ご回答ありがとうございます。 最初の方法ですと、実際の実装は複雑な計算なので、代入していく方法は難しそうです。 Globe2DやGlobe3Dに分けてしまうとやはりメンテナンス性がなくなってしまうようなので、うまくいきませんでした。 最後の委譲による方法を検討してみました。これはつまり、2D、3Dを型(クラス)として定義しそこに代入していくということで合っていますでしょうか? 確かにこの方法なら実現できそうです。Dimensionというインターフェイスクラスを作り、2D,3DのインスタンスをShapeクラスに保持させて、 図形による切り替えはdata.print_shape()というメッセージを送り、次元による切り替えはdata.dimension.print_dimension()をいうメッセージを送ることで、 元のコードを修正せずにいくらでも種類を増やせそうです。 まだ委譲の本質を理解しきっていないので、2、3日委譲の勉強をして理解する時間をいただきたいと思います。そのご実際にやりたいことが実現できそうなら回答を締め切らせていただきます。 ありがとうございました。 修正したコード from abc import ABCMeta, abstractmethod class Dimension(metaclass=ABCMeta): @abstractmethod def print_dimension(self): # 次元のインターフェイス pass class Shape(metaclass=ABCMeta): # 図形のインターフェイス @abstractmethod def print_shape(self): pass class Dimension2D(Dimension): # 2Dの次元 def print_dimension(self): print('This is a 2D object') class Dimension3D(Dimension): # 3Dの次元 def print_dimension(self): print('This is a 3D object') class Globe(Shape): # 球 def __init__(self, dimension): self.dimension = dimension def print_shape(self): print('This is a Globe') class Cube(Shape): # 立方体 def __init__(self, dimension): self.dimension = dimension def print_shape(self): print('This is a Cube') if __name__ == '__main__': data = Cube(Dimension2D()) data.print_shape() data.dimension.print_dimension() data = Globe(Dimension3D()) data.print_shape() data.dimension.print_dimension()
int32_t

2022/11/24 23:36 編集

> 図形による切り替えはdata.print_shape()というメッセージを送り、次元による切り替えは > data.dimension.print_dimension()をいうメッセージを送ることで、 Shape にも print_dimension() を足して、その実装は self.dimension.print_dimension() を呼ぶだけにして、 data = Globe(Dimension3D()) data.print_dimension() とできるようにしたほうが委譲っぽいです。
lukkio

2022/11/25 15:56

なるほど、委譲元のクラスに委譲先のメソッドを追加することで、呼び出し元からは、 data.print_dimension() data.print_shape() ど同列に扱うことができるわけですね。 勉強になります。 修正コードに反映しました。
lukkio

2022/11/26 14:38

ここで得られた知識を実際のコードに反映してうまく行きましたので、回答を締め切らせていただきます。 「委譲」もなんとなくしか理解していなかったのですが、おかげさまですっきり理解できました。 ありがとうございました。 質問してくださった方々も、質問のより具体化、モデルの見直しなど為になりました。 ありがとうございました。
lukkio

2022/12/13 00:25

今更ですが、これがDependency Injection (依存性の注入)なんですね。 ようやく気が付きました・・・。
int32_t

2022/12/13 00:47

DIと形は似ていますが、別物だと思います。
lukkio

2022/12/17 19:46

返信を見逃していました。コメントをありがとうございます。 DIはインスタンスの作成にかかわることであり、 if __name__ == '__main__': data = Cube(Dimension2D()) の部分をInjectorクラスやDIコンテナクラスでひとまとめにすることでDIパターンになる。 であってますか?
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問