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

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

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

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

インターフェース

インターフェイスという用語はハードウェア・ソフトウェアの両方に使うことができます。 一般的に、インターフェイスは内部処理の詳細を見せないように設定されます。オブジェクト指向プログラミングにおいて、インターフェイスはabstractクラスとして定義されます。

Q&A

解決済

2回答

2177閲覧

C#の依存性の注入ができているかわからない

tom_honmono

総合スコア22

C#

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

インターフェース

インターフェイスという用語はハードウェア・ソフトウェアの両方に使うことができます。 一般的に、インターフェイスは内部処理の詳細を見せないように設定されます。オブジェクト指向プログラミングにおいて、インターフェイスはabstractクラスとして定義されます。

0グッド

0クリップ

投稿2021/07/25 09:56

このコードですが、インターフェースを使って依存性逆転の法則を守ろうと(?)しています。
このコードのMain()内で、プロパティに名前や攻撃力を代入してしまっているのですが、ここでも new の後にインターフェース等(?)を使わないと依存性の注入ができていないということでしょうか?それともこの時点で注入は十分にされてると思いますか?
ご意見をください。
ちなみに私はインタフェースや依存性などを全然理解できていなく勉強中なので、トンチンカンな質問でしたらすみません。

C#

1interface IEnemies{ 2 string Name{ get; set; } 3 int HP { get; set; } 4 } 5 interface IBullets{ 6 string Name { get; set; } 7 int AttackPoint { get; set; } 8 } 9 interface IHIT{ 10 int HIT(string Name, int HP, int AttackPoint); 11 } 12 public class Player{ 13 public class Bullet : IBullets{ 14 public string Name { get; set; } 15 public int AttackPoint { get; set; } 16 public Bullet(string name, int attackpoint){ 17 this.Name = name; 18 this.AttackPoint = attackpoint; 19 } 20 public class BulletHit : IHIT{ 21 public int HIT(string Name,int HP,int AttackPoint){ 22 HP -= AttackPoint; 23 Console.WriteLine("プレイヤーの弾が{0}に当たりました",Name); 24 return HP; 25 } 26 } 27 }//Bullet 28 }//Player 29 public class Enemy : IEnemies{ 30 public string Name{ get; set; } 31 public int HP { get; set; } 32 33 public Enemy(string name,int hp){ 34 this.Name = name; 35 this.HP = hp; 36 } 37 38 } 39 public class Boss : IEnemies{ 40 public string Name{ get; set; } 41 public int HP { get; set; } 42 43 public Boss(string name, int hp){ 44 this.Name = name; 45 this.HP = hp; 46 } 47 } 48 public class Test{ 49 public static void Main(){ 50 IBullets B1 = new Player.Bullet("普通の弾",25); 51 IEnemies E1 = new Enemy("エネミー1",200); 52 IEnemies BOS1 = new Boss("1面のボス", 500); 53 IHIT IHIT = new Player.Bullet.BulletHit(); 54 55 Console.WriteLine("{0}のHPは{1}, 弾の攻撃力は{2}",E1.Name,E1.HP,B1.AttackPoint); 56 E1.HP = IHIT.HIT(E1.Name,E1.HP,B1.AttackPoint); 57 Console.WriteLine("{0}の残りHPは{1}",E1.Name ,E1.HP); 58 59 Console.WriteLine("{0}のHPは{1}, 弾の攻撃力は{2}",BOS1.Name,BOS1.HP,B1.AttackPoint); 60 BOS1.HP = IHIT.HIT(BOS1.Name,BOS1.HP,B1.AttackPoint); 61 Console.WriteLine("{0}の残りHPは{1}",BOS1.Name ,BOS1.HP); 62 BOS1.HP = IHIT.HIT(BOS1.Name,BOS1.HP,B1.AttackPoint); 63 Console.WriteLine("{0}の残りHPは{1}",BOS1.Name ,BOS1.HP); 64 } 65 }

また、例えばクラスの内部にクラスを作るのはやめた方がいいよ!とか、クラスの上下関係など、アドバイス等あると助かります。
抽象的な質問ですみません。よろしくお願いします。

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

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

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

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

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

Zuishin

2021/07/25 10:00

> ちなみに私はインタフェースや依存性などを全然理解できていなく勉強中なので、トンチンカンな質問でしたらすみません。 それを理解するのが先でしょう。 でなければ説明されてもわからないのでは?
tom_honmono

2021/07/25 10:02

すみません。少しは理解したつもりなので、聞きました。とんちんかんな質問だったらすみませんという意味の文です。
Zuishin

2021/07/25 10:09 編集

このコードのどの辺が依存性の注入になっているのか全くわかりません。 まったくできていないと思います。 依存性の注入をどのように理解しているのかを質問を編集して説明すれば、間違っているところを正してもらえるかもしれません。
tom_honmono

2021/07/25 10:09

わかりました。ご指摘ありがとうございます。
guest

回答2

0

すでに解決済みですが、回答を途中まで書いていたので残します。

インターフェースを使って依存性逆転の法則を守ったり依存性の注入をしたりすることと、内部クラスを持つことは切り離して考えましょう。(そもそも内部クラスが必要になる場面は多くありません)

そもそも、インターフェースを使ったり、依存性逆転の法則、依存性の注入をしたりすることの目的は整理できているでしょうか。1つは、機能追加・修正をしやすいコードを書くためです。

命名を見直す

機能追加・修正をしやすいコードを書くためには、まずはクラスやプロパティ・メソッドに適切な名前をつけることです。

IEnemies,IBulletsという名前は、単数形ではなく複数形です。ですが、それ等が持つプロパティは明らかに単数形です。この時点で名前が名前の役割を果たしていないので、機能追加・修正をしにくいコードになっています。

C#

1interface IEnemy{ 2 string Name{ get; } 3 int HitPoint { get; set; } 4} 5interface IBullet{ 6 int AttackPoint { get; } 7}

IHITもインタフェースで使う名前としては不適切です。なぜならHitとは物ではなく振る舞いを示すものだからです。

そしてHitとは、プレイヤーが敵にダメージを与えることを指しているのか、プレイヤーが敵からのダメージを受けることを指しているのかが名前からわかりません。

Hitとは、受動態であってもなくとも英語のスペルはHitのままなので、私ならその動詞は使わないと思います。
もしHitが「プレイヤーが敵にダメージを与えること」を指しているのであれば、プレイヤーにHit相当のメソッドをもたせると思います。もしくはIShootableのようなインタフェースを作り、Playerクラスに実装するかもしれません。

依存性の注入

依存性の注入を使うメリットは外部で作成したインスタンスを取得し、利用できることです。
PlayerクラスがIBullet型のプロパティを持つことにより、特定のBulletクラスに依存しないコードを書けます。

PlayerのコンストラクタにIBulletを含めないと下記のように内部でインスタンスを抱える必要があります。

C#

1public class Player 2{ 3 public IBullet Bullet { get; } = new LeadBullet(); 4 5 6 public void ShootBullet(IEnemy target) 7 { 8 target.GetShotBy(Bullet); 9 } 10}

しかし、このコードの問題点は、PlayerクラスがSilverBulletなど別の弾丸を持ちたいときに問題があります。

この場合はPlayerのコンストラクタを作成し、IBullet型のインスタンスを引数に含め、Bulletプロパティを初期化することでLeadBulletに依存しなくなります。

適応例

下記はIHITを削除し、インタフェースに定義するものを整理し、依存性の注入を使ったコードです。

C#

1 2interface IEnemy 3{ 4 string Name{ get; } 5 int HitPoint { get; set; } 6 void GetShotBy(IBullet bullet); 7} 8 9interface IBullet 10{ 11 int AttackPoint { get; } 12} 13 14public class Player 15{ 16 public IBullet Bullet { get; } 17 18 // これが依存性の注入(DI) 19 public Player(IBullet bullet) 20 { 21 Bullet = bullet; 22 } 23 24 public void ShootBullet(IEnemy target) 25 { 26 target.GetShotBy(Bullet); 27 } 28} 29 30public class Gang : IEnemy 31{ 32 public string Name { get; } = "ギャング"; 33 public int HitPoint { get; set; } = 8; 34 public void GetShotBy(IBullet bullet) 35 { 36 HitPoint -= bullet.AttackPoint; 37 } 38} 39 40public class Dracula : IEnemy 41{ 42 public string Name { get; } = "ドラキュラ"; 43 public int HitPoint { get; set; } = 16; 44 public void GetShotBy(IBullet bullet) 45 { 46 if(bullet is SilverBullet) 47 { 48 HitPoint -= bullet.AttackPoint * 10; 49 } 50 } 51} 52 53public class LeadBullet : IBullet 54{ 55 public int AttackPoint { get; } = 6; 56} 57 58public class SilverBullet : IBullet 59{ 60 public int AttackPoint { get; } = 4; 61} 62 63public class DependencyInjectionTest{ 64 public static void Main(){ 65 var gang = new Gang(); 66 var dracula = new Dracula(); 67 68 var leadBullet = new LeadBullet(); 69 var agent = new Player(leadBullet); 70 71 //ギャングは鉛玉2発で倒せるが 72 agent.ShootBullet(gang); 73 agent.ShootBullet(gang); 74 DebugEnemyIsDead(gang); 75 76 //ドラキュラは倒せない 77 agent.ShootBullet(dracula); 78 agent.ShootBullet(dracula); 79 DebugEnemyIsDead(dracula); 80 81 var silverBullet = new SilverBullet(); 82 var draculaHunter = new Player(silverBullet); 83 84 //銀の弾丸なら1発でドラキュラを倒せる 85 draculaHunter.ShootBullet(dracula); 86 DebugEnemyIsDead(dracula); 87 } 88 89 public static void DebugEnemyIsDead(IEnemy target) => 90 Console.WriteLine($"{target.Name}は{(target.HitPoint > 0 ? "死なない" : "死ぬ" )}"); 91 92}

内部クラスはまったく使っていないことを確認してください。クラスの上下関係はこの場合インスタンスを作成しているDependencyInjectionTestクラスが上位です。他は等しく下位です。

依存性逆転の法則はそもそもレイヤーを分けて開発をするときに意識するものです。レイヤーを意識した設計を行ってから考えましょう。そのため依存性逆転の法則に関しての説明は省きます。

投稿2021/07/26 11:36

編集2021/07/26 11:50
BluOxy

総合スコア2663

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

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

tom_honmono

2021/07/26 11:44

ありがとうございます。自分の作りたかったものの答えの例をいただけたことで、学習が捗りそうです。ありがとうございました。
BluOxy

2021/07/26 12:02

コードはあくまでDIや命名の整理方法の参考程度にお願いします。 というのも、このコードが絶対に正しいものではないということをお伝えしたいからです。 例えば、このコードの場合はPlayerはインタフェースを継承しておらず、差し替え不可です。 例えば、このコードの場合は各Bulletの攻撃力、各Enemyクラスの名称・体力は直接保持しているため、データベースなどからそれ等の値を持っていき初期化することはできません。 コードを抽象化するにも、適切な抽象化とそうではない抽象化(ただ実装が複雑になる抽象化)があるので、上手にインターフェースや依存性の注入は使いましょう。
guest

0

ベストアンサー

.NET Core アプリでは Microsoft の Microsoft.Extensions.DependencyInjection 名前空間にあるクラス類を使って Dependency Injection (DI) 機能を実装できるので、自分でやってみてはいかがですか?

コンソールアプリでも以下のようにしてできます。

.NET Core での Dependency Injection
http://surferonwww.info/BlogEngine/post/2021/01/01/dependency-injection-for-dotnet-core-application.aspx

基本的には、あるクラスが依存するオブジェクトを、そのクラスを初期化する際に、オブジェクトを生成してそのクラスのコンストラクターの引数に渡してやるという感じです。

コンストラクタの引数にインターフェイスを使うのは DI に必須という訳ではなく、オブジェクトを差し替えることができるなどの便宜を図るためと思います。

投稿2021/07/25 10:28

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

tom_honmono

2021/07/26 11:04

丁寧にありがとうございます。そこから勉強してみます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問