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

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

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

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

オブジェクト指向

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

ドメイン駆動設計

ドメイン駆動設計(Domain-driven design, DDD)とは、ソフトウェアの設計手法、および設計思想や哲学のことです。ドメインモデル構築の際に、設計上の判断を決定する枠組みとドメイン設計に関して議論するボキャブラリを提供するものです。

Q&A

解決済

2回答

2893閲覧

オブジェクト指向のクラス設計について

Hottopia

総合スコア16

C#

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

オブジェクト指向

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

ドメイン駆動設計

ドメイン駆動設計(Domain-driven design, DDD)とは、ソフトウェアの設計手法、および設計思想や哲学のことです。ドメインモデル構築の際に、設計上の判断を決定する枠組みとドメイン設計に関して議論するボキャブラリを提供するものです。

0グッド

3クリップ

投稿2023/01/18 00:14

編集2023/01/18 00:48

前提

C#(WPF)にてクイズゲームのようなアプリを開発しています。
オブジェクト指向についての理解が浅く、どのようにクラスの設計したらよいか迷っています。

実現したいこと

クイズのコンテンツ(png,mp3等)をローカルに配置し、それらを参照して画面に表示させたり、音声を再生させるつもりです。
そこで1問のクイズを表すQuizクラスを作成しました。機能としてはローカルの読み取りを行い、適切な形に成形してプロパティにセットするといった内容です。(コンストラクタの引数で取得したいコンテンツ郡を指定しています。)
作っている中で、コンテンツを読み取ることはQuizクラスの責務ではないような気がしていて、クラスを分離したほうが良いのではないかと感じています。しかし具体的にどのようにクラスを設計したら良いかがよく分かりません。
この場合、どのように設計すると良いのでしょうか?

ソースコード

Quizクラス

C#

1using System; 2using System.Collections.Generic; 3using System.Collections.ObjectModel; 4using System.IO; 5using System.Linq; 6 7namespace QuizSample 8{ 9 public record Quiz 10 { 11 private readonly string _contentsPath = @"resources\quizset"; 12 13 /// <summary> 14 /// 難易度 15 /// </summary> 16 public string Level { get; init; } 17 18 /// <summary> 19 /// 問題文画像 20 /// </summary> 21 public Question Question { get; init; } 22 23 /// <summary> 24 /// 回答ボタン画像 25 /// </summary> 26 public ReadOnlyCollection<Answer> Answers { get; } 27 28 /// <summary> 29 /// </summary> 30 /// <param name="level">問題の難易度</param> 31 /// <param name="number">問題フォルダの番号</param> 32 public Quiz(string level, int number) 33 { 34 //対象ファイルを取得 35 var quizFolder = _contentsPath + @"\" + level + @"\" + number; 36 string[] quizElements = Directory.GetFiles(quizFolder); 37 38 //Questionを生成 39 var questions = new List<Question>(); 40 var questionImgPath = quizElements.Where(o => o.Contains("question.png")); 41 var questionVoicePath = quizElements.Where(o => o.Contains("question.mp3")); 42 var questionPaths = questionImgPath.Zip(questionVoicePath, (iPath, vPath) => 43 new { ImgPath = iPath, VoicePath = vPath }); 44 foreach (var path in questionPaths) 45 { 46 var question = new Question(path.ImgPath, path.VoicePath); 47 questions.Add(question); 48 } 49 Question = questions[0]; 50 51 //Answersを生成 52 var answers = new List<Answer>(); 53 var answerPath = quizElements.Where(o => o.Contains("answer", StringComparison.CurrentCulture)); 54 foreach (var path in answerPath) 55 { 56 var answer = new Answer(path); 57 answers.Add(answer); 58 } 59 Answers = answers.AsReadOnly(); 60 } 61 } 62} 63
resources └─quizset ├─easy │ └─1 │ answer1-correct.png │ answer2.png │ answer3.png │ question.mp3 │ question.png │ ├─hard │ └─1 answer1-correct.png │ answer2.png │ answer3.png │ question.mp3 │ question.png │ └─normal └─1 answer1-correct.png answer2.png answer3.png question.mp3 question.png

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

Visual Studio 2022
C#10.0
WPF

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

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

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

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

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

Zuishin

2023/01/18 00:47

クイズはファイルからのみ読み取るようになっていますが、将来データベースやネットから読み取る可能性も考えると、読み取り部分は確かに分離した方が良さそうです。 ファイルやネットからデータを読み取る Stream クラスを参考にしてみては? データを mp3 や png ではなく今は未対応のファイル形式や複数のファイルになることもあると仮定すると、JSON や XML などを利用してデータの種類や数や読み取り方法を指定するのも良いかもしれません。 クラスを設計するのであれば、どのようなクラスを作れば拡張しやすいか、そしてクラス自体を交換しやすいかを考えるのが良いでしょう。 これを開放/閉鎖原則と言います。 https://ja.wikipedia.org/wiki/%E9%96%8B%E6%94%BE/%E9%96%89%E9%8E%96%E5%8E%9F%E5%89%87
Zuishin

2023/01/18 01:01

過去の質問を見ると、どうも自分の中に正解があって回答者がそれを答えられるかどうかをテストしてるみたいだな。
Hottopia

2023/01/18 01:27

そんなことはないです。。。
Zuishin

2023/01/18 01:27

この質問も自分の中で答えが出てるようだけど?
Hottopia

2023/01/18 01:29

なんとなく、こうしたほうがいいんじゃないかみたいなものはありますが、正解なのかがわからないので、質問しました。
Zuishin

2023/01/18 01:29

動けば正解。
Hottopia

2023/01/18 01:33

動けば正解はそうかも知れませんが、Zuishin様がコメントおっしゃったように、拡張性だったり修正の容易性を意識したいのですが、どういう設計にすればよいかがわからないため今回の質問に至りました。
Zuishin

2023/01/18 01:44 編集

プログラミングは、無いものを作る作業であって、学力テストではないので、決められた正解を目指すのではなく、自分でできるやり方で動くものを作ればいいだけ。 最初に作るものはプロトタイプと割り切り、動くようになってからリファクタリングしてもいい。 良いものを作るには経験が必要なので、最初から完璧にできなくていいし、人に設計してもらったのではその経験すら積めない。 良い設計だったかどうかは改修時にわかる。
Hottopia

2023/01/18 02:01

>>良いものを作るには経験が必要なので、最初から完璧にできなくていいし、人に設計してもらったのではその経験すら積めない。 おっしゃる通りですね。精進します。 ご丁寧にありがとうございました!
guest

回答2

0

ベストアンサー

Quizクラスが持つべき役割を整理すると「質問と答えのセットを提供すること」でしょうから、「クイズ用のアイテムを読み込む」のは2つ目の役割といえるかもしれません。クラスに担わせる役割は一つだけのほうがいいでしょうから、2つでも十分多いと言えます。

こういう時はQuizManagerなどとQuizを管理する側のクラスを別途用意して読み込み処理を担わせる、といった形で処理の分担を考えるとQuizクラス自体をシンプルに保てます。


(以下、だいぶ長めの回答になります。)

QuizManagerを作ろうという時に、追加でもう一点「読み込み処理そのものの抽象化」をしておくと便利です。

どういうことかといいますと↓

Quizを取得するだけの役割を持った interface が以下のように表現できます。

cs:IQuizLoader.cs

1public interface IQuizLoader 2{ 3 Task<QuizMode[]> GetAllQuizModeAsync(); 4 Task<Quiz> GetQuizAsync(QuizMode quizMode); 5} 6 7public record QuizMode(string Level, int Number);

このようなIQuizLoaderインターフェイスを用意するとクイズ読み込みの「手段だけ」が提示されることになります。

具体的な読み込み方法、ストレージの存在などはこのIQuizLoaderから伺い知ることは出来ないため、このIQuizLoaderを使う人に対して「Quizの取得手段だけを知っている」という状態を実現できます。

一方でQuizを使いたい側(QuizManager、または何らかのViewModel)はIQuizLoader.GetQuizAsync(quizMode)を呼び出して使います。そのためにはまずQuizを使いたい側のコンストラクタでIQuizLoaderを受け取れるようにします。

IQuizLoaderの実装クラスのインスタンスを渡す方法に関しては、DI Container(DI=Dependency Injection=依存性の注入)で実現するのがベターです。

DI Containerがわからないようなら、後で「MVVM toolkit DI」で調べてみてください。今はわからなくても読み進めて大丈夫です。回答最後のところに参考リンクが貼ってあります。

次に、IQuizLoader の実装は例として以下のように書けます。

cs:ResourceBasedQuizLoader.cs

1public class ResourceBasedQuizLoader : IQuizLoader 2{ 3 public Task<QuizMode[]> GetAllQuizModeAsync() 4 { 5 // TODO: クイズの種類を取得して返す 6 } 7 public Task<Quiz> GetQuizAsync(QuizMode quizMode) 8 { 9 // TODO: クイズの種類からクイズを読み込んで返す 10 } 11}

リソースファイルベースで読み込む場合は上記のように ResourceBasedQuizLoader を実装したり、あるいは別途サーバーを立ててWebから取得する場合には WebBasedQuizLoader を実装したりすることで、IQuizLoaderの実装を切り替えることで多様なソースからのデータ取得の切り分けが表現できるようになります。

実装が出来たら QuizManagerないしViewModelのコンストラクタにnew QuizManager(new ResourceBasedQuizLoader())といった形でIQuizLoaderを実装したクラスのインスタンスを提供します。

cs:IQuizLoader.cs

1public class QuizManager 2{ 3 private readonly IQuizLoader _quizLoader; 4 public QuizManager(IQuizLoader quizLoader) 5 { 6 _quizLoader = quizLoader; 7 } 8 9 // TODO: QuizModeやQuizの取得を実装する 10}

cs

1var quizManager = new QuizManager(new ResourceBasedQuizLoader()); 2 3// DI Containerを使う場合は、IQuizLoaderを解決する場合の実装クラスを指定して 4Container.Register<IQuizLoader, ResourceBasedQuizLoader>(); 5// QuizManagerのインスタンスを生成する場合にコンストラクタで求められる IQuizLoader が 6// Containerから提供され(この場合はRegisterしたResourceBasedQuizLoaderが選択され) 7// QuizManagerクラスのインスタンスがResolve(解決)されます。 8var quizManager = Container.Resolve<QuizManager>();

最終的に以下のような形でクイズ取得を呼び出して使います。

cs

1var quizManager = new QuizManager(new ResourceBasedQuizLoader()); 2var quizModes = await quizManager.GetAllQuizModeAsync(); 3var quiz = await quizManager.GetQuizAsync(quizModes.Where(x => x.Level == "hard").First());

以上です。

ちなみに、ここまで提示したのはいわゆる SOLID原則 を踏まえた設計の一つになろうかと思います。「オブジェクト指向」だとまだふわっとしてるので SOLID原則 など具体的な設計指針(デザインパターン)を参考にしながら考えていけるといいのかなと思います。

参考

イラストで理解するSOLID原則
https://qiita.com/baby-degu/items/d058a62f145235a0f007

DI Containerによるコンストラクタ インジェクションの説明
https://learn.microsoft.com/ja-jp/dotnet/communitytoolkit/mvvm/ioc#constructor-injection

オススメ書籍

  • .NETのクラスライブラリ設計 改訂新版
  • 良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方

投稿2023/01/18 12:08

tor4kichi

総合スコア763

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

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

Hottopia

2023/01/20 00:42

ありがとうございます。上手く行きそうです。
Kodzura

2023/01/25 00:43

何を「どう」したいかまで決めてから質問するよりも「何をしたいか」段階で聞いた方が「答えられる」。 新しい世代のオブジェクト指向プログラミング技術の「基本設計構築」のひとつの特長になっています。 基本設計への強い制約や矛盾が避けられなくなるまで「作り込んで」から質問するより、ずっと良いかと。 →そうした技術がなかった頃の質問との大きな違い
guest

0

オブジェクト指向で開発するならば、コードを先におこすのでなく、
オブジェクト指向分析や設計を先に行った方がよいかと思います。

今回の場合、Quiz, Question, Answer などの単語がプログラムに記載されていますが、
それらがどういう関係なのかを先に整理した方がよいのではないでしょうか。

なんとなくプログラムをみている限り下記のような構造が見えてきました

イメージ説明

投稿2023/01/23 23:56

DanDan244

総合スコア57

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

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

Hottopia

2023/01/24 02:56

回答ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問