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

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

新規登録して質問してみよう
ただいま回答率
85.50%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

5回答

12587閲覧

staticクラスでの共通処理

terateraterater

総合スコア12

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

1クリップ

投稿2018/12/22 13:55

前提・実現したいこと

unityでc#を使ってゲーム製作しているのですが、共通の処理をしたい時にStaticクラスを作成してそこに共通処理を書いて処理しているのですが、ネットで調べているとユーティリティクラスはオブジェクト指向に反しているからダメとの記述を見かけるのですが、staticクラスを使わずに共通処理を行う場合どのような方法がありますか?
あと、拡張メソッドもstaticクラスを作成してstaticメソッドを定義するので結局はユーティリティクラスと同じなのですか?

例えば、ダメージを受ける処理を

c#

1//敵やプレイヤーなどダメージを受けるものが持つLifeクラス 2public class Life 3{ 4 public int lifepoint; 5 public int defensepower = 5; 6 7 //ダメージを受けた時 実際はもっと複雑な内容 8 void Damage(int attackpower) 9 { 10 //攻撃力が防御力を下回っていたら終了 11 if (attackpower <= defensepower) return; 12 13 int damage = attackpower - defensepower; 14 15 lifepoint -= damage; 16 } 17}

こう書くか

c#

1//敵やプレイヤーなどが持っているLifeクラス 2public class Life 3{ 4 public int lifepoint; 5 public int defensepower = 5; 6 7 //ダメージを受けた時 8 void Damage(int attackpower) 9 { 10 lifepoint -= DamageKeisan.resault(attackpower, defensepower); 11 } 12}

c#

1//計算するクラス 2public static class DamageKeisan 3{ 4 //ダメージの計算 実際はもっと複雑な内容 5 public static int resault(int attackpower, int defensepower) 6 { 7 //攻撃力が防御力を下回っていたら終了 8 if (attackpower <= defensepower) return 0; 9 10 return attackpower - defensepower; 11 } 12} 13

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

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

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

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

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

guest

回答5

0

ベストアンサー

こんにちは。

ネットで調べているとユーティリティクラスはオブジェクト指向に反しているからダメ

staticクラスがオブジェクト指向に反していることはその通りと思います。そのケースでは単なるグローバル関数に過ぎませんから。
しかし、オブジェクト指向以外のプログラミング手法がダメというわけではないです。
オブジェクト指向プログラミングは非常に強力な手法ですが万能ではありません。オブジェクト指向よりも適した手法が存在するケースもそこそこあります。そのような時にオブジェクト指向に反するというだけの理由でその適した手法を採用しないのはあまり好ましくないように感じます。(例えば、デバッグ用のログ・システムはグローバルに呼び出せるようにしたいので、staticクラスやstatic関数を使うことになるでしょう。)

staticクラスを使わずに共通処理を行う場合どのような方法がありますか?

基底クラスで実装する方法があります。
protectedなら派生先クラス全てから呼び出せる「共通処理」として定義できます。
例として上げられている Damege()関数ならば、恐らくダメージを受けるオブジェクトの LifePoint を操作する関数でしょうから、基底クラスに LifePoint と Damage()関数を実装すると好ましいように感じます。
更に、Damage()関数をpublicにすると基底クラスへの参照経由でDamage()関数を呼び出せます。
更に更に、Damage()関数を仮想関数としておけば、派生クラスでオーバーライドすることも可能です。これにより、基底クラスへの参照経由で呼び出しているにも関わらずオブジェクトの種類によってダメージの受け方をスマートに変えることができるようになります。

拡張メソッドもstaticクラスを作成してstaticメソッドを定義するので結局はユーティリティクラスと同じなのですか?

同じですね。構文が異なるだけで構造的には拡張先オブジェクトを引数にとるstatic関数ですから。
そもそもメンバ関数は構造的にはthisを隠れ引数として取るstatic関数です。拡張メソッドも拡張先のオブジェクトを隠れ引数として持つstatic関数です。どちらもメンバ関数の構文で呼び出せるようにされたthisを隠れ引数とするちょっと特殊なstatic関数に過ぎません。

投稿2018/12/22 15:00

編集2018/12/22 15:02
Chironian

総合スコア23272

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

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

terateraterater

2018/12/22 23:50

詳しく教えていただきありがとうございます。とても勉強になります!
guest

0

ユーティリティクラスが駄目なのであってstaticが駄目ってわけじゃないと思いますよ

ユーティリティクラスだと、ようは雑居ビル状態になるので目的も対象も何も関係なくぶち込んでしまうからダメって話だと思います
例えばMathはstaticだけど数学的な計算をまとめたクラスです
これだと使う分には引数で渡した数値だけを元に計算を行うライブラリのようなものに仕上がっていますが、関係ないもメソッドは含まれていません
つまりはさっきのビルでいうところの商業ビルなりオフィスビルなり一つの目的で集まっているので使う分にはわかりやすいので問題ありません
ところがユーティリティクラスだとこういった計算式もあればDBへの接続管理も含んでいたり日付を自プロジェクトで表示するための書式指定をした文字列に変換したりと目的も対象もばらばらのただひたすらに膨大なメソッドをつぎ込んだものが出来上がってしまいます
オブジェクト指向は色々ややこしいのでこういった表現をすると間違ってると指摘する人も出てくるかもしれませんが、超大雑把に言うとなんかしら意味のまとまった塊で切り分けることで作りやすく・使いやすくするので、その他でまとめたったーは違うよそれってことなんだと思います
もっとも、質問者さんが見た文章を書いた人がどう考えたのかまではわかりませんので、違う観点から出した結論かもしれませんので断言できるものではありませんが

オブジェクト指向に関しては解説をする人によって解釈が違う部分もあるしよくそれは違うだとかあってるだとかの議論が起きるくらいに誰でも一目でわかる何かがあるわけではないので自分・チーム内で何らかの納得できる理由とともに規約を決めると良いと思います。今回のような場合、駄目らしいからやめとこーで思考を停止するのではなく、なんでだろう?ならばどうすればいいのだろう?いやこれは別にいいだろう?等考えるのが大事だと思います

追記:2018/12/23 03:10頃
ユーティリティクラスに持たせたいメソッドがどんなのかにもよりますが、例えば敵クラスと味方クラスで共通の処理を実装したいというのであれば、キャラクタークラスを定義してキャラクタークラスにそのメソッドを実装して敵クラスと味方クラスはキャラクタークラスを継承したクラスとして定義することができます
また、全然違うクラスだけど特定の同じメソッドを実装したいのであればインターフェースとして定義してやり、そのインターフェースへ拡張メソッドで追加するというのも手ですし、引数にどのクラスから読んだのかという情報を持たせて内部で切り分けを行うような場合なら、インターフェースで定義だけしておいて実装はそれぞれのクラスでそのクラスの場合に通るルートだけを実装して切り分けフラグを排除してしまえばよくなります
といった感じでどのような処理をどのようなクラスで使いたいかなどの条件によっては解決法も変わってきます

投稿2018/12/22 15:29

編集2018/12/22 18:17
len_souko

総合スコア1337

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

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

terateraterater

2018/12/22 23:57

回答ありがとうございます。 なんでもかんでもユーティリティクラスにぶち込むのがダメなのですね。
guest

0

例えば攻撃手段として斬撃・刺突・殴打などの種類があり、プレイヤーおよび敵はそれらに対し個々に熟練度や耐性を持っていて、同じ力で斬撃しても攻撃側の熟練度と防御側の耐性を考慮してダメージ計算するとします。

その場合、ダメージ計算に必要なパラメータは攻撃の種類・攻撃側の熟練度・防御側の耐性となるので、一度の攻撃にそれらのパラメータを渡して計算することは可能です。その場合、static であろうがなかろうが違いはほとんどありません。

しかし、キャラクターは他にも多くのパラメータを持っている可能性があります。そのような場合は、パラメータをグループ分けして階層化すると見通しが良くなります。

Character クラスから Player クラスと Enemy クラスが派生し、それらは Attack オブジェクトと Defence オブジェクトを保持し、攻撃する際には player.Defence.CalcDamage(enemy.Attack) のようにできるとパラメータをたくさん渡さなくてもどのキャラクタでも同じコードで計算ができます。

static を使う場合には、計算を中央で集中管理する手続き志向的な手法になりますし、各オブジェクトに計算を任せるならオブジェクト指向的になります。
これはどちらが良いかということではなく、要件によって使い分けるべきものです。

シューティングゲームならそれほど複雑な計算にならないので static を使うのが良いと思いますし、複雑な攻撃手段と多彩なキャラクタが登場する RPG ならオブジェクトによる計算が向いているかもしれません。

投稿2018/12/22 15:25

Zuishin

総合スコア28656

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

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

terateraterater

2018/12/22 16:42

オブジェクトによる計算というのは、例えば Characterクラスを継承していてAttackオブジェクトと Defenceオブジェクトを保持したEnemyオブジェクトが100体いたとして いずれかのEnemyに攻撃した場合、攻撃されたEnemyのCalcDamage()を呼び出すということですか? 100体のEnemyのCalcDamage()関数に計算処理を書くのと、1つのCalcDamageクラスを作ってダメージ計算はCalcDamageクラス内のメソッドで行うのはどちらのが良いのでしょうか?
Zuishin

2018/12/22 21:21

クラスは継承できますから、同じ処理なら 100 回書く必要はありません。 Unity から始める人はクラスの使い方を誤解している人が多いので、Unity に関係ない C# の本を読んでみるのがいいと思います。
guest

0

あなたがオブジェクト指向じゃないとダメ、という考え方なら、static関数は論外となるでしょうけど、

ふつーは(オレが組む場合は)共通処理はstatic使って、共通変数はグローバル使って、と、ふつーに使ってますねー

#そこらへん、これは使ったらダメ、という場合の理由は説明できるでしょうか

投稿2018/12/22 14:05

y_waiwai

総合スコア87719

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

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

terateraterater

2018/12/22 14:18 編集

特に自分がオブジェクト指向じゃないとダメという事はないのですが、というかオブジェクト指向の意味がよくわかってません。 なので、なぜstatic使うななのかがわからないです。なぜダメと書かれていたりするのでしょうか?
y_waiwai

2018/12/22 14:22

そりゃ > オブジェクト指向に反しているからダメ だからでしょうね それ言ってる人に聞いてみましょう
terateraterater

2018/12/22 14:26

staticクラスを使わずに共通処理を行う場合はどのような方法がありますか?
y_waiwai

2018/12/22 22:27

インスタンス作ってそれを公開、あらゆるところから呼び出せるようにするって話になりますが、 それが果たしてオブジェクト指向にかなっているのかどうかはべつのおはなし
terateraterater

2018/12/22 23:48

回答ありがとうございました。 なるほど。もっと勉強します!
guest

0

ネットで調べているとユーティリティクラスはオブジェクト指向に反しているからダメとの記述を見かけるのですが、

そのようなネット記事はゴミ記事なので無視しても良いです。もしそうなら、C#のMathクラスなどもオブジェクト指向に反してダメということになりますが、そんな評価は聞いたことがありません。

Mathクラスの関数群のように、処理(計算)が主体で状態を持たない(メモリ上に何らかのデータを生成してそれを保持しておく必要がない)のであれば、staticクラス(メソッド)で実装すれば良いです。C#がなぜマルチパラダイムの言語なのかというと、「必要に応じて最適な手段を選択」することで生産性を向上させるためです。「オブジェクト指向に反する」という理由でその選択肢を狭めてしまうのは生産的とは言えません。
「オブジェクト指向」を追求する余り何をやっているのかすぐには理解不能なコードを時折見かけますが、それは目的と手段を取り違えているというものです。

ゲームにおけるダメージ計算などは、設計時に決めたらそれで終わりということはなく、テストしながら、場合によってはリリース後も細かい調整が繰り返されたりするものです。私なら、そういうものは判りやすくまとめた「ユーティリティクラス」に書きますね。そうしておけば、それだけを取り出して「ダメージ計算確認調整ツール」なるものを作って確認・調整作業を効率化する、なんてこともできますから。

投稿2018/12/22 16:32

catsforepaw

総合スコア5938

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

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

terateraterater

2018/12/23 00:01

回答ありがとうございます。 オブジェクト指向に反しているから使わないではなく、一番効率よくできるやり方を考えて進めていきます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問