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

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

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

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

Python

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

Q&A

解決済

3回答

6101閲覧

オブジェクト指向でカードゲームのクラスを作るときの設計方法がわからない

Dozi0116

総合スコア13

オブジェクト指向

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

Python

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

1グッド

2クリップ

投稿2019/01/04 17:05

編集2019/01/05 00:20

概要

オブジェクト指向でカードゲームを作っています。
そのカードゲームでは大きく分けて、

    1. コスト・攻撃力・体力を持つカード
    1. コスト・効果を持つカード(攻撃力・体力は持っていない)

のような2種類があります。

このようなカードゲームを作ろうと思ったのですが、クラス設計で悩んでいます。

現在の状態

現在、2つの実装方法を考えました。
(あまりオブジェクト指向では使わないpythonですが、自分が使えてサクッと書ける言語がpythonしかなかったため、ご容赦ください。)

下で出てくるdataというのは辞書を想定しています。

※2019-01-05 09:18 コード修正しました。

1つ目

python

1# 共通部分を定義するクラス 2class Card(): 3 def__init__(self, data): 4 self.cost = data['cost'] 5 6# Cardを継承した例1のクラス 7class Type1(Card): 8 def __init__(self, data): 9 super().__init__(data) 10 self.atk = data['atk'] 11 self.hp = data['hp'] 12 13# Cardを継承した例2のクラス 14class Type2(Card): 15 def __init__(self, data): 16 super().__init__(data) 17 self.effect = data['effect'] 18 19# カードがどちらのタイプに属するかを判別し、適切なインスタンスを返す関数 20def classify_card(data): 21 if data.type == 1: 22 return Type1(data) 23 elif data.type == 2: 24 return Type2(data) 25 else: 26 print('error') 27 28if __name__ == '__main__': 29 # dataは事前でもらっているものとする 30 card = classify_card(data)

1つ目は、classify_card関数を作成して、事前に与えられているtypeから適切なインスタンスを返す方法です。

考えられるメリットは「管理が楽」で、共通部分、タイプ1、タイプ2とキレイに分かれているため、見た目でどのタイプがどのパラメータを持っているかがわかりやすいです。
また、dataの中身も必要な情報のみを格納すればいいため、スッキリすると考えています。

デメリットが「処理が大変」です。
例えば、全部のカードの攻撃力を参照するといった処理を考えるとき、Type2のカードが1枚でもあったらエラーを起こしてしまいます。


2つ目

python

1# 例1も2もこのクラスで作成 2class Card(): 3 def __init__(self, data): 4 self.cost = data['cost'] 5 self.atk = data['atk'] 6 self.hp = data['hp'] 7 self.effect = data['effect'] 8 9if __name__ == '__main__': 10 # dataは事前にもらっているものとする 11 card = Card(data)

2つ目は、関数などを作成せずに、すべてのパラメータを1つのクラスで管理する方法です。

考えられるメリットは、「処理が楽」で、1つ目のプログラムのように、いちいちtypeを参照して処理を変更するといったことなく、すべてのカードに同じ処理を適用できると考えています。

デメリットは、「複雑性の向上」です。
1つのインスタンスの中に必要のないパラメータが複数種類存在してしまうことが多くなり、不必要な部分で不必要な参照が行われてしまう可能性が考えられます。

お聞きしたいこと

このような状況下では、どちらの設計がいいでしょうか。
また、今後にパラメータが増える予定があるとしたら、どちらの設計がいいでしょうか。
どちらも良くない・そもそもオブジェクト指向を使うのがよくないなどありましたら、代替案を提案していただけると勉強になります。

yukkuri👍を押しています

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

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

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

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

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

guest

回答3

0

ベストアンサー

前者、つまりクラスを分ける方に一票。

デメリットが「処理が大変」です。

例えば、全部のカードの攻撃力を参照するといった処理を考えるとき、Type2のカードが1枚でもあったらエラーを起こしてしまいます。

二種混ざった状態で一方に依存した処理をするとき、エラーが起きるのはむしろ妥当だと思います。
前以て必要なカードだけ選り分ける処理を噛ませるべきです。

後者は、実質的にはdataをそのまま使う方法からほとんど改善しないはずです。

投稿2019/01/04 17:44

編集2019/01/04 17:49
LouiS0616

総合スコア35660

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

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

Dozi0116

2019/01/05 00:35 編集

回答ありがとうございます。 基は同じでも特徴が違うものは別々に作って、必要な場合に選りすぐりをする、というのが根底の考え方にあるのですね。 参考にさせていただきます。 各処理ごとにエラー処理を書くとのことですが、これはtry-exceptでkeyErrorが起ったら何もしない程度の処理でいいのでしょうか?
KSwordOfHaste

2019/01/05 03:52 編集

> try-exceptでkeyErrorが起ったら何もしない程度でよいか そのあたり「全く違うクラスにたまたま名前が同じで意味が違う属性があったときそれを明確に区別すべきかどうか」は設計によると思います。 攻撃用カードの攻撃力の属性名を「attack_of_attack_card」なんて名前にするのは普通オブジェクト指向ではしないと思います。単に「attack」とするでしょう。 さて効果カードの攻撃力への加算の属性名を「attack_bonus」とするか「attack」とするかは設計次第と思います。もし「attack」にしたなら... Pythonのような動的型付け言語では「変数には型がない」ので変数にどういう型の値が入っているかはプログラマーがあり得る可能性を把握した上で論理を作る必要がありますので、attackが複数の型の属性にありえるなら try-exeptでkeyErrorがでるかどうかで判断するというのは大雑把すぎるということになるでしょうし、「着目している型にそのような属性名の重複がないように設計する」ならKeyErrorを頼りにするという方針もあり得るかも知れません。 自分は、どちらかというと「KeyErrorを頼りにする」ために「属性名に重複がないような設計にする」方に神経を使うぐらいなら素直に「今見ている値の型はなんなのか」を事前にチェックする方が安心だと思います。 --- と、書きましたが・・・少なくともattackのような基本的な属性については「少なくとも全てのカードでこういう意味にする」みたいな方針で考えた方がよいかも知れません。「今見ている値の型はなんなのかを事前にチェック」は静的型付け言語よりすぎる場合もあるかも知れません。
LouiS0616

2019/01/05 05:00

@KSwordOfHaste さん 非常に有用なコメントありがとうございます。概ね同意です。 --- > 基本的な属性については...みたいな方針で考えた方がよいかも知れません。 attackが0のType1カードが存在する場合は考慮しなくていいのかな、という点だけ少し気になりました。 プロトタイプ段階での制約というか要請としては影響が大き過ぎるような。 型ヒントの導入も進んでおり、静的型付け言語に寄せた方が結局作りやすいという合意がコミュニティ内でもあったんじゃないかと想像します。
KSwordOfHaste

2019/01/05 05:16 編集

> 型ヒントの導入も進んでおり、静的型付け言語に寄せた方が結局作りやすいという合意 自分もそう感じます。充分に小さく全体の見通しがよいモジュールを作る際にはボイラープレート的な構文がうるさく感じ、それをはしょれる動的型付け言語が生きるけど、ある程度大きなものを作る記述能力をLLが備えてくると「動的型付け」「静的型付け」の両方のいいとこどりをしたいということなのだろうと思います。 自分は「静的型付けで設計しよう!」とまで思わなくても、型ヒントを入れとくとIDEが有用なコード補完をしてくれるため静的型付け的な利点を享受したい動機でちょっとだけ使うなんてことがよくあります。
Dozi0116

2019/01/05 22:59

@KSwordOfHaste さん 新たなコメントありがとうございます。 > 「KeyErrorを頼りにする」ために「属性名に重複がないような設計にする」方に神経を使うぐらいなら素直に「今見ている値の型はなんなのか」を事前にチェックする方が安心だと思います。 今まで属性名がかぶってしまうというほど大規模な開発をしたことがないので、考えたこともありませんでした。 このような考え方は、今後プログラムを書く上での新たな考え方として吸収できました。ありがとうございます。 --- @LouiS0616 さん >attackが0のType1カードが存在する場合は考慮しなくていいのかな、という点だけ少し気になりました。 プロトタイプ段階での制約というか要請としては影響が大き過ぎるような。 現時点ではattackが0のType1カードも作成する予定です。 この場合、どんな影響が出ることが考えられるのでしょうか? 重ね重ねの質問、申し訳ありません。
LouiS0616

2019/01/06 07:16

> 現時点ではattackが0のType1カードも作成する予定です。 この場合、どんな影響が出ることが考えられるのでしょうか? 例えば『全Type1の攻撃力を100up』みたいな効果を実装するときに面倒です。 事前にタイプを判定しなければType2のattackも100になってしまいますし、それにどうせ判定するならば最初から区別が自明についている方が楽です。
guest

0

dataってなんですか。とりあえずJavaScriptの連想配列風の記法になっている辞書だとみなすことにしますけど。


質問文に示された範囲だと、メソッドを定義しないでコンテナもどきとして使っているだけなので、それなら要らないです。クラスにするなら、方針はどんなメソッドを書くのか次第です。

どっちでも良いので作り始めてしまった方があるべき姿に早く気付ける気もします。

投稿2019/01/04 17:34

hayataka2049

総合スコア30933

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

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

Dozi0116

2019/01/05 00:33

回答ありがとうございます。 dataは辞書型を想定していました。 そのため、質問文と一部ソースを変更いたしました。ご指摘ありがとうございます。 現時点では、showメソッドを作成して、各Typeごとに見た目を変化させることを考えております。その他のメソッド追加は未定です。 一度作ってしまって問題にぶつかったときにまた考え直すというのも手なのですね。 他の回答者の意見とも合わせて、今回は前者のパターンで実装していこうと思います。
hayataka2049

2019/01/05 04:52

>現時点では、showメソッドを作成して、各Typeごとに見た目を変化させることを考えております。その他のメソッド追加は未定です。 質問に示されている範囲のコードはあくまでも内部ロジックの表現だと思いますが、内部ロジックは内部ロジックで作っておいて、表示周りは別のレイヤで作るほうが綺麗な設計にしやすいとは思います。 内部ロジックの面では、まあ質問文の要件でも「攻撃する」「攻撃された」「効果発動」「効果を食らった」みたいなメソッドは少なくとも必要になりそうな気がしますから(ゲームのルール次第ですが)、それらをどう定義するかによるんじゃないでしょうか。
Dozi0116

2019/01/05 22:27

内部は内部で完結させ、表示部分はまた別で作るのが綺麗なのですね。勉強になります。 返信遅くなってしまいすいません。大変参考になりました。ありがとうございます。
Dozi0116

2019/01/05 22:27

内部は内部で完結させ、表示部分はまた別で作るのが綺麗なのですね。勉強になります。 返信遅くなってしまいすいません。大変参考になりました。ありがとうございます。
guest

0

クラス定義する方法で作業を進めてみてはいかがでしょう。
全部のカードの攻撃力を参照する例を3つ書いてみました。

この例では classify_card 関数は廃止しています。
代わりに type メソッドか、attrs() メソッドを使って 攻撃属性の有無を判定しています。

3つめの方法は、 card クラスに get_atk メソッド (0 を返す) を定義し、 Type1 では get_atk() をオーバーライドします。
get_atk() はすべてのクラスが持っているので、カード種類を区別せず get_atk() を使って合計計算できます。

cards.py

python3

1# 共通部分を定義するクラス 2class Card(): 3 def __init__(self, data): 4 self.cost = data['cost'] 5 def attrs(self): 6 return ['cost'] 7 def get_atk(self): 8 return 0 9 10# Cardを継承した例1のクラス 11class Type1(Card): 12 def __init__(self, data): 13 super().__init__(data) 14 self.atk = data['atk'] 15 self.hp = data['hp'] 16 def attrs(self): 17 return ['cost', 'atk', 'hp'] 18 def get_atk(self): 19 return self.atk 20 21# Cardを継承した例2のクラス 22class Type2(Card): 23 def __init__(self, data): 24 super().__init__(data) 25 self.effect = data['effect'] 26 def attrs(self): 27 return ['cost', 'effect'] 28 29if __name__ == '__main__': 30 cards = [ 31 Type1({'cost': 50, 'atk': 10, 'hp': 100}), 32 Type2({'cost': 60, 'effect': 10}), 33 Type1({'cost': 100, 'atk': 20, 'hp': 100}) 34 ] 35 36 # cardの種類を判定しながら計算 37 sum = 0 38 for card in cards: 39 if type(card) == Type1: 40 sum += card.atk 41 print(sum) 42 43 # cardが持つ属性を判定しながら計算 44 sum = 0 45 for card in cards: 46 if 'atk' in card.attrs(): 47 sum += card.atk 48 print(sum) 49 50 # 全てのクラスがもつ get_atk() を使って計算 51 sum = 0 52 for card in cards: 53 sum += card.get_atk() 54 print(sum)

実行例
イメージ説明

投稿2019/01/05 04:41

katoy

総合スコア22324

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

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

Dozi0116

2019/01/05 23:06

回答ありがとうございます。 3種類の方法と例示、とてもわかり易かったです。 今回の設計方針では、card.attrs()メソッドを実装するのが一番うまく行きそうな雰囲気がするので、一度その方法をお借りして実装してみようと思います。 他の例も綺麗な書き方なので、今後の別プロジェクトでの設計に活かせそうです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問