🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C#

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

Q&A

解決済

2回答

1897閲覧

[C#]文字列による処理の振り分けをswitchでなくスマートに書きたい

hinatahinata

総合スコア29

C#

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

0グッド

0クリップ

投稿2019/12/02 07:04

編集2019/12/02 08:22

文字列によって処理を振り分けたいのですが、switch分で書くとコードが長くなってしまいます。
コードをスマートにするにはどのようにすれば良いでしょうか。

実装したい機能

キャラクターの動作をシミュレートするアプリケーション。(のようなもの)

データベースで以下のようなテーブルがあり、それらを計算しキャラクターの挙動を変化させる。

キー(string)値(float)
WalkSpeed+10
WalkSpeed+20
WalkSpeed-10
AttackSpeed+5
AttackSpeed+10
IsPoison1

DataUnit

1// テーブルの各行 2public class DataUnit { 3 string Key { get; set; } 4 float Value { get; set; } 5 6 public DataUnit(string key, float value) { 7 Key = key; 8 Value = value; 9 } 10}

Chara

1public class Chara { 2 float walkSpeed = 0f; 3 float attackSpeed = 0f; 4 bool isPoison = false; 5 6 void TestFunc() { 7 List<DataUnit> dataUnits = new List<DataUnit>(); 8 9 // データベースからテーブルデータをdataUnitsへ保存(下記はデバッグ用にデータを生成) 10 dataUnits.Add(new DataUnit("WalkSpeed", 10.0f)); 11 dataUnits.Add(new DataUnit("AttackSpeed", -20.0f)); 12 dataUnits.Add(new DataUnit("IsPoison", 1f)); 13 dataUnits.Add(new DataUnit("WalkSpeed", 12f)); 14 dataUnits.Add(new DataUnit("AttackSpeed", 5f)); 15 16 foreach(var unit in dataUnits) { 17 // Key毎にTestクラスの別々の変数をいじりたい 18 swicth (unit.Key) { 19 case "walkSpeed" 20 walkSpeed += unit.Value; 21 break; 22 23 case "attackSpeed" 24 attackSpeed += unit.Value; 25 break; 26 27 case "isPoison" 28 isPoison |= unit.Value > 0f ? true : false; 29 break; 30 31 // フィールドは増える予定 32 } 33 } 34 } 35 36 void Update() { 37 // キャラクターの動作に反映 38 } 39}

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

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

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

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

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

BluOxy

2019/12/02 07:06

fieldAとfieldBは同じ処理をしていますが、何か共通点はあるのでしょうか。 それとも、処理がたまたま同じなだけでしょうか。
BluOxy

2019/12/02 08:17 編集

また、fieldA,fieldB,fieldCを初期化するのにdataUnitsは一見不必要に思います。
hinatahinata

2019/12/02 07:12

質問ありがとうございます。 キー"fieldA"の場合は変数fieldAをいじる、 キー"fieldB"の場合は変数fieldBをいじる、 というように処理対象が変わります。 また、処理自体もキー毎に変わります。 質問を編集しました。
BluOxy

2019/12/02 07:20

fieldA,fieldB,fieldCという変数名を用いていますが、命名が抽象的すぎて、何に使われるか分からないことがスマートさを欠いている原因と思います。 完全に使い道が別物なら、それぞれをオブジェクトとして表現するのが良さそうです。
BeatStar

2019/12/02 07:29

swichのエリアを簡略化したいっていうことですよね? 無いと思いますよ。 どうしてもクラスを組むかメソッドを組むか、そのままべた書きかっていう違いだけなので。
guest

回答2

0

ベストアンサー

fieldがどういう値を持つのかを明確にするため、下記ケースを想定してサンプルを作りました。

  • アクションゲームを作ることを想定
  • 攻撃を受けたときの振る舞いを各オブジェクトに持たせたい

まずは同一の振る舞いを持ったオブジェクトを複数定義するためにインターフェースを定義します。

C#

1public interface IHitable{ 2 void ReceiveDamage(int damage); 3}

次に、その振る舞いを持つオブジェクトを定義します。
イメージでは、Bair.HitPointが元fieldADog.HitPointが元fieldBOldMan.IsDeadが元fieldCに相当します。

C#

1public class Bair : IHitable{ 2 private int HitPoint = 300; 3 public void ReceiveDamage(int damage){ 4 HitPoint -= damage / 2; 5 } 6} 7 8public class Dog : IHitable{ 9 private int HitPoint = 150; 10 public void ReceiveDamage(int damage){ 11 HitPoint -= damage; 12 } 13} 14 15public class Oldman : IHitable{ 16 private bool IsDead; 17 public void ReceiveDamage(int damage){ 18 IsDead = damage > 5; 19 } 20}

最後に、IHitableなオブジェクトをリストに保持するようにし、ポリモーフィズムを利用して各要素が持つReceiveDamageメソッドを呼び出してください。
これで、各オブジェクトのメンバーが保持している値を変更できるようになります。

ちなみに、hitablesからDataUnitのキーに対応する要素を探すためにリフレクションという機能を使っています。恐らく、内容と工夫次第でリフレクションを使う必要もなくなりそうです。

C#

1public class Test{ 2 List<IHitable> hitables = new List<IHitable>(); 3 4 void TestFunc(){ 5 hitables.Add(new Bair()); 6 hitables.Add(new Dog()); 7 hitables.Add(new OldMan()); 8 9 List<DataUnit> dataUnits = new List<DataUnit>(); 10 dataUnits.Add(new DataUnit("Bair", 20)); 11 dataUnits.Add(new DataUnit("Dog", 1)); 12 dataUnits.Add(new DataUnit("Oldman", 10)); 13 14 foreach(var unit in dataUnits){ 15 var hitable = hitables.First(x => x.GetType().Name == unit.Key); 16 hitable.ReceiveDamage(unit.Value); 17 } 18 } 19}

また、掲示されたコードでは、そもそもDataUnitsを使う必要性が分かりませんでした。
下記のような書き方でも良さそうに思います。

C#

1public class Test{ 2 List<IHitable> hitables = new List<IHitable>(); 3 4 void TestFunc(){ 5 var bair = new Bair(); 6 bair.ReceiveDamage(20); 7 var dog = new Dog(); 8 dog.ReceiveDamage(1); 9 var oldMan = new OldMan(); 10 oldMan.ReceiveDamage(10); 11 12 hitables.Add(new Bair()); 13 hitables.Add(new Dog()); 14 hitables.Add(new OldMan()); 15 } 16}

スマートさを出すために検討すべきこと

fieldA,fieldB,fieldCの違いを明確にしてください。
おそらく配列やコレクションで持つ方が望ましいかもしれません。

また、DataUnitsの役割を明確にしてください。
もし定義する必要性がない、もしくはDataUnitsを使うことで却って煩雑になるようなら別の実装方法を検討したほうが良いかもしれません。

パターンを増やす場合

  1. IHitableを実装した新しいクラスを定義
  2. そのクラス内のReceiveDamageメソッドに独自の処理を追加
  3. hitablesに新しく追加したクラスのインスタンスをAddする

これで、hitablesを列挙したときに新しく追加したインスタンスのメソッドが呼ばれるようになります。


掲示されたコードだとこうなりそうです。
Updateという名前が抽象的ですが、これ以上の具体的な命名は難しそうです。

C#

1public interface IAbility{ 2 void Update(float value); 3} 4 5public class Walk : IAbility{ 6 private float speed = 0f; 7 public void Update(float value){ 8 speed += value; 9 } 10} 11 12public class Attack : IAbility{ 13 private float speed = 0f; 14 public void Update(float value){ 15 speed += value; 16 } 17} 18 19public class Health : IAbility{ 20 private Condition condition = Condition.None; 21 public void Update(float value){ 22 condition = value > 0f ? Condition.Poison : Condition.None; 23 } 24} 25 26public enum Condition{ 27 None, 28 Poison, 29 Burning, 30 Palsy 31} 32 33public class Chara { 34 List<IAbility> abilities = new List<IAbility>(); 35 36 public Chara() 37 { 38 abilities.Add(new Walk()); 39 abilities.Add(new Attack()); 40 abilities.Add(new Health()); 41 } 42 43 void TestFunc() { 44 List<DataUnit> dataUnits = new List<DataUnit>(); 45 46 // データベースからテーブルデータをdataUnitsへ保存(下記はデバッグ用にデータを生成) 47 dataUnits.Add(new DataUnit("Walk", 10.0f)); 48 dataUnits.Add(new DataUnit("Attack", -20.0f)); 49 dataUnits.Add(new DataUnit("Health", 1f)); 50 dataUnits.Add(new DataUnit("Walk", 12f)); 51 dataUnits.Add(new DataUnit("Attack", 5f)); 52 53 foreach(var unit in dataUnits) { 54 var ability = abilities.First(x => x.GetType().Name == unit.Key); 55 ability.Update(unit.Value); 56 } 57 } 58}

投稿2019/12/02 07:49

編集2019/12/02 08:59
BluOxy

総合スコア2663

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

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

hinatahinata

2019/12/02 08:55

回答ありがとうございます。 実装したい機能がわかりづらく申し訳ありませんでした。 ポリモーフィズムを利用しての実装、勉強になりました。
guest

0

DataUnit側に

C#

1public class DataUnit 2{ 3 public DoSomethingTo( Test tgt ) 4 { 5 tgtに何かする 6 } 7}

みたいなメソッドを書いてやるような方向性はどうでしょうか.

↑では,わかりやすいように DoSomethingTo()の引数の型を Test としましたが,そこらへんは何か適当なinterfaceにでもしておけば良いかも.

投稿2019/12/02 07:12

fana

総合スコア11990

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

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

hinatahinata

2019/12/02 08:52

回答ありがとうございます。 Keyによって処理内容も変更したいので別のかたをベストアンサーとさせていただきました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問