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

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

新規登録して質問してみよう
ただいま回答率
85.37%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

意見交換

8回答

1491閲覧

Java 採点求む。 データとUIの分け方

shuoga

総合スコア2

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

1グッド

4クリップ

投稿2024/10/29 12:19

テーマ、知りたいこと

タイトル通りです。qiitaを見ていると、どうやらクラスの分割について、基本はデータとUIにわけると書いてあるのをみました。
https://qiita.com/o65nakashima/items/182bba3bf49d166348f9
こちらのコメント欄です。
なので、見様見真似で簡単な四則演算機構をつくり、それをデータとUIにわけてみました。なので、採点の方よろしくお願いします。

Java

1import java.util.Scanner; 2 3class Calc{ 4 private double A,B; 5 private double sum; 6 private int operation; 7 8 public void updateNum(double A, double B,int operation){ 9 this.A = A; 10 this.B = B; 11 this.operation = operation; 12 } 13 public double calc (){ 14 switch (operation){ 15 case 1: sum = A + B;break; 16 case 2: sum = A - B;break; 17 case 3: sum = A * B;break; 18 case 4: 19 if (B == 0)sum = 0; 20 else sum = A / B; 21 break; 22 } 23 return sum; 24 } 25} 26 27public class Main { 28 public static void main(String[] args) { 29 Calc calc = new Calc(); 30 31 input(calc); 32 33 System.out.println("the answer is : " + calc.calc()); 34 35 } 36 37 public static void input(Calc calc){ 38 Scanner scanner = new Scanner(System.in); 39 40 System.out.println("1:+ , 2:- , 3:* , 4:/"); 41 int operation = scanner.nextInt(); 42 43 System.out.println("type number A"); 44 float variable_A = scanner.nextFloat(); 45 46 System.out.println("type number B"); 47 float variable_B = scanner.nextFloat(); 48 49 calc.updateNum(variable_A,variable_B,operation); 50 } 51}
Y.H.👍を押しています

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

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

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

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

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

回答8

#1

hiroki-o

総合スコア1057

投稿2024/10/29 13:24

(動かしてないし、リンク先も見ていません)

これはMainの中にUIが入っています。そして、ロジックのみをCalcクラスに分離しています。

厳密にクラス分けするなら、

  • input関数を独立させてUIクラスを作る
  • variable_A,variable_B,operationを持つためのデータクラスを作る
  • Mainは、UIクラスから返ってきたデータクラスを、ロジック(Calc)クラスに渡す

ようにしたいです。

でも、これくらいの長さなら、これでよいと思います。きれいに書けてるし。

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

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

#2

Manabu

総合スコア60

投稿2024/10/29 13:52

編集2024/10/29 13:54

四則演算程度なら質問中のコードで及第点だと思います
これは個々の思想の問題になり、正解や最適解が存在する議題ではないため、あくまで個人の見解です

まず、課題を挙げるなら、それはCalcの破壊的操作の扱いです
関数は基本的にデータに干渉すべきでないとされます
この観点に立つと、今の四則演算はCalcの参照で回っているので、干渉を許容します

干渉を極力回避するには、その役割ごとにクラスを分割します
例えばCalcは四則演算のロジックを提供することにのみ専念し、他の一切には関わらないようにする選択です

ここでは操作対象のデータをデータクラスとして再定義し、フィールドのみを包みます

Java

1class Numbers{ 2 public final double A,B; 3 public final int operation; 4 5 public Numbers(double a,double b,int operation){ 6 //Your code 7 } 8}

CalcではこのNumberを受け取り、計算を担当します

Java

1class Calc{ 2 public static double calc (Numbers nums){ 3 double sum=0; 4 5 switch (nums.operation){ 6 case 1: sum = nums.A + nums.B;break; 7 case 2: sum = nums.A - nums.B;break; 8 case 3: sum = nums.A * nums.B;break; 9 case 4: 10 if (nums.B == 0)sum = 0; 11 else sum = nums.A / nums.B; 12 break; 13 } 14 15 return sum; 16 } 17}

この利点は複数のNumbersのインスタンスを使い分けることで、一つのClacで柔軟に演算を変化させられる点です
Calcのデータの状態を気にしなくていいので、Numbersでデータを安全に管理できます

他にもUIの利便性を考えるならoperationenumにした方が視覚的に理解しやすいなど様々意見は可能ですが、シンプルなコードには相応しくない仰々しさになるので、ひとまずはこの具合です

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

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

#3

jimbe

総合スコア13168

投稿2024/10/29 16:28

編集2024/10/31 14:01

分割するものだと言われたから分割してみたというのでは、個人的には違和感を感じるコードです。
分割内容は対象の状況に因って異なります。参考にされたものと質問のコードは構造に全く同じ所が無いようですので、当てはめた所でうまくは行き難いでしょう。
四則演算器ではこの (Main と Calc という2つの)クラス分けで、どのような利点が発生するのでしょうか。

Calc クラスは何故 updateNum と calc な2つのメソッドを持っているのでしょう。 calc メソッドが A, B, operation を引数に取れば、 updateNum メソッドも Calc クラスのフィールドも不要ですよね?

Calc の operation と UI の選択番号とが正しく対応することをプログラミングレベルで確認できるようにしたくはなりますね。

私なら 2値と演算子を纏めてクラスにするのでは無く(今の所,値を保存する必要が見当たりませんので) 演算子部分だけを enum にします。
これは GUI で電卓を作る時にも良くやります。

java

1import java.util.*; 2 3enum Calc { 4 ADD("+") { 5 double apply(double a, double b) { return a + b; } 6 }, 7 SUB("-") { 8 double apply(double a, double b) { return a - b; } 9 }, 10 MUL("*") { 11 double apply(double a, double b) { return a * b; } 12 }, 13 DIV("/") { 14 double apply(double a, double b) { return b == 0 ? 0 : a / b; } 15 }; 16 17 final String text; 18 Calc(String text) { 19 this.text = text; 20 } 21 22 abstract double apply(double a, double b); 23} 24 25public class Main { 26 public static void main(String[] args) { 27 Scanner scanner = new Scanner(System.in); 28 29 System.out.println(getCalcSelectText()); 30 Calc op = Calc.values()[scanner.nextInt() - 1]; 31 32 System.out.println("type number A"); 33 float a = scanner.nextFloat(); 34 35 System.out.println("type number B"); 36 float b = scanner.nextFloat(); 37 38 System.out.println("the answer is : " + op.apply(a, b)); 39 } 40 41 private static String getCalcSelectText() { 42 Calc[] ops = Calc.values(); 43 StringJoiner sj = new StringJoiner(" , "); 44 for(int i=0; i<ops.length; i++) sj.add((i+1) + ":" + ops[i].text); 45 return sj.toString(); 46 } 47}

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

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

#4

pecmm

総合スコア647

投稿2024/10/30 14:34

基本はデータとUIにわける

ちょっと違います。
最も基本となるのは「(プログラムが大きくなるに従い)機能や層ごとに分割」です。
各機能ごとに必要があればデータクラスとして分割もします。

またその際はデータだけではなく、「一連のデータ+関連する処理」をひとまとめにします(カプセル化とかいわれるやつです)
データ+ロジックをモデルとか呼ぶこともあります。

UIというとユーザ操作(入力)・画面などへの表示(出力)だけに見えてしまいますが
普通は IO全般からモデルを分離 します(IO=対ユーザに限らないあらゆる入出力……ファイル・ネットワーク・DB等々)。
IOから分離した純粋なモデルは自動テストしやすい・再利用しやすいなどのメリットがあります。

特にUIについていえば、例えば今回のようなCUIアプリであれば、それらの入出力は「クラスに分割する」というよりは「モデルを抽出して分離し、mainメソッドに近い所に残しておく」ようなアプローチが自然なこともあります。
こういうUIの入出力はあまりクラスに抽出して再利用するようなものではなく、必要であればメソッド抽出で十分な場合がほとんどです。
もちろん規模が大きくなれば画面単位(≒機能単位)でクラス分割することも多々あります。


ちなみに参考URL記事のコメントで言われている「ビュー・コントローラ」ですが
これらは一般的なクラス分割での基本的な考え方、といったものではありません。
クラス分割の大きな基準の一つに「使用しているフレームワークの要請に従う」というのもありますが
ビュー・コントローラもそういったフレームワークの考え方の範疇です。

またそちらの記事のPersonクラスですが
正直この程度のサンプルプログラムですと、クラス抽出は不要でメソッドだけで十分です。
この規模のサンプルはどう工夫しても、クラス化の強い需要ややり方によるメリット/デメリットの違いが見えてこず、参考教材にはしづらいです。

このサンプルの方向性で頑張るとして……
例えば大量のPersonデータがあり、DBの入出力があるとか
ExcelのようにPersonの一覧を表示させてisHealthyの可否でフィルタしたいとか
そういう需要を考えたときに初めてデータの可搬性を考えてクラス抽出の必要性を検討します。
データっぽいものがあったらとりあえずクラス化する、とかはやめた方がよいです。

記事のコメントの提案コードでHealthなるクラスが示されていますが
これは作りたいプログラムの要求次第では、特に必要のない過剰な分割となる可能性があります。
例えば、前述のとおり単にPersonを表示したいだけであれば全く不要になりますが
別の例として
Personに直接属するnamebirthdayといったデータは固定で不変だが Healthに属するheight, weight とそこから導出される bmi, isHealthy は健康診断ごとに変わる可能性があるので、これらを Person:Health=1:n として複数件を履歴で保持しPersonごとに一覧できるようにする(※ageは不変ではないのでbirthdayに変更)
……といった要求を考えると、Healthを独立クラスにする必要性が見えてくると思います。

このように、サンプルレベルではなくある程度大きな規模のサンプルを作ってみるとか実用的なアプリを検討するとかでないと
そもそも妥当なクラス分割について検討すらできないと思いますので
一度チャレンジしてみるのをオススメします。


上記の記事と同様に、質問本題の四則演算でも正直にいうとクラス化の必然性はさほどないと思います。
データとして何も保持せずともメソッド化だけで十分テストしやすい独立機能にできます。

いろいろ細かいところ(命名規則とか異常値の扱いとか)が気になったので、自分ならどのようなコードを書くかを示してみます。
敢えてクラス抽出の必要性を増すために、勝手に機能を追加(toExpressionStringのところ)しています。

java

1import java.util.Scanner; 2 3enum OperationKind { 4 ADD, SUB, MUL, DIV; 5 6 public static OperationKind from(int input) { 7 return switch (input) { 8 case 1 -> ADD; 9 case 2 -> SUB; 10 case 3 -> MUL; 11 case 4 -> DIV; 12 default -> throw new IllegalArgumentException("illegal operation:" + input); 13 }; 14 } 15} 16 17record Formula(OperationKind operation, double leftOperand, double rightOperand) { 18 /** 式の値を計算する */ 19 public double calc() { 20 return switch (operation) { 21 case ADD -> leftOperand + rightOperand; 22 case SUB -> leftOperand - rightOperand; 23 case MUL -> leftOperand * rightOperand; 24 case DIV -> leftOperand / rightOperand; 25 }; 26 } 27 28 /** 式を文字列化する */ 29 public String toExpressionString() { 30 return switch (operation) { 31 case ADD -> leftOperand + " + " + rightOperand; 32 case SUB -> leftOperand + " - " + rightOperand; 33 case MUL -> leftOperand + " * " + rightOperand; 34 case DIV -> leftOperand + " / " + rightOperand; 35 }; 36 } 37} 38 39public class Main { 40 public static void main(String[] args) { 41 Scanner scanner = new Scanner(System.in); 42 43 System.out.println("1:+ , 2:- , 3:* , 4:/"); 44 int operation = scanner.nextInt(); 45 46 System.out.println("type left number"); 47 float leftOperand = scanner.nextFloat(); 48 49 System.out.println("type right number"); 50 float rightOperand = scanner.nextFloat(); 51 52 var f = new Formula(OperationKind.from(operation), leftOperand, rightOperand); 53 System.out.println(f.toExpressionString() + " = " + f.calc()); 54 } 55}

java18 でpaizaにて動作確認しています。

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

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

#5

Mirko_Mug_Cup

総合スコア56

投稿2024/11/06 11:58

私はスマホ開発をしており、GUIのプログラミングを日常的にしています。
クラスの分割に興味を持たれているようですので、質問者さんの参考程度に書いてみようと思いました。
※これは質問者さんのソースの採点ではありませんので、悪しからず。

GUIのプログラミングでも、もちろん画面クラスとロジックのクラスを分けることは重要です。
アーキテクチャ・パターンというものが複数存在し、大抵のGUIプログラミングは、それらのいずれかを採用しています。MVP, MVVM etc...

MVVMはよく利用されているパターンです。簡単に言うと、変数の値の変化をトリガーに画面の更新を行うパターンです。ただ、自前でこれを実現するのは大変なので、大抵の場合、既存のフレームワークを利用することになります。

今回はMVPで、計算処理のプログラムを書いてみました。なお、Calcクラスについては、様々な意見が既に出ているように、改善の余地はありますが、今回はMVPの仕組みを理解してもらうことに注力するためスルーです。また、以下のプログラムはコンパイルしていないので、うまく動作しないかもです… ゴメンナサイ。

まずは、画面クラスの呼び出しです。

Java

1public class Startup 2{ 3 public static void main(String[] args) 4 { 5 CalcView view = new CalcView(); 6 view.begin(); 7 } 8}

そして画面クラスです。本来はGUI部品を表示するクラスになります。なお、インターフェースを実装していますが、解説は後回し。計算開始のメソッドの呼出で処理が始まります。画面とロジックを分離する観点から、計算処理自体は別クラスにお任せです。この別クラスはPresenterと呼ばれ、画面とロジック(モデル)の受け渡し役になります。

Java

1/** 2 * 計算画面クラス 3 */ 4public class CalcView implements CalcViewTask 5{ 6 private CalcPresenter presenter; 7 8 public CalcView() 9 { 10 this.presenter = new CalcPresenter(this); 11 } 12 13 /** 14 * 計算を開始する。 15 */ 16 public void begin() 17 { 18 int variable_A = 0; 19 int variable_B = 0; 20 int operation = 1; 21 22 Scanner scanner = new Scanner(System.in); 23 24 System.out.println("1:+ , 2:-, 3:* , 4:/"); 25 int operation = scanner.nextInt(); 26 27 System.out.println("type number A"); 28 float variable_A = scanner.nextFloat(); 29 30 System.out.println("type number B"); 31 float variable_B = scanner.nextFloat(); 32 33 // 計算処理の実施 34 presenter.calc(variable_A, variable_B, operation); 35 } 36 37 @Override 38 public void showCalcResult(double result) 39 { 40 System.out.println(String.format("the answer is: %d", result)); 41 } 42}

お次はPresenterクラスで、画面クラスはこのクラスのメソッドを呼び出すことで、(インターフェースの実装経由で)目的の結果を得ます。これはいわゆるコールバックの仕組みです。

Java

1/** 2 * 画面からの命令を受け、結果を画面側へコールバック 3 */ 4public class CalcPresenter 5{ 6 private CalcViewTask task; 7 8 public CalcPresenter(CalcViewTask task) 9 { 10 this.task = task; 11 } 12 13 /** 14 * 指定の計算を実施する。 15 */ 16 public void calc(double a, double b, int operation) 17 { 18 Calc calc = new Calc(); 19 calc.updateNum(variable_A, variable_B, operation); 20 double result = calc.calc(); 21 task.showCalcResult(result); 22 } 23}

Java

1/** 2 * 画面からのコールバック定義 → Presenterから分離することで、画面との疎結合を保つ。 3 */ 4interface CalcViewTask 5{ 6 /** 7 * 計算結果を画面に表示する。 8 */ 9 void showCalcResult(double result); 10}

なぜわざわざインターフェース定義が必要なのか?ですが、もし、インターフェースを介さない場合、画面に計算結果を表示しようとすると、Presenterクラスは画面クラス(CalcView)のインスタンスを直接コンストラクタで受け取り、CalcView#showCalcResult(double) を直接呼び出すことになります。が、しかしです。Presenterクラスが画面クラスのインスタンスを直接参照することは密結合となることと、更にはPresenterクラスが画面クラスの様々なメソッド、例えば CalcView#begin() を呼び出すことが可能になってしまいます。Presenterクラスが呼び出せる画面クラスのメソッドを制限するためにインターフェースを介している、ということになります。
※なお、今回の記述ではCalcView自身がインターフェースの実装を行っているので、Presenter側でCalcTaskView型のインスタンスをCalcViewにキャストすることで、上記の弱点が露わになるのですが、それを回避する方法はあります。

なお、インターフェースを扱うことに、もし慣れていないのであれば、学ばれると良いでしょう。うまく使いこなせるようになると、かなりプログラミングの自由度が増します(より柔軟な共通化が可能になる等)。

「先輩、Javaのインターフェースって何ですか?」の答えに窮した時に読む本

ただの計算処理にしては大げさと思われるでしょうが、MVPの紹介ということで、上記のようになりました。複雑な構造と思われるかも、ですが、画面での機能が増えるとその有り難みを感じられるようになるでしょう(そうなるまで精進されることを期待)。まずはGUIプログラミングを学ぶことをオススメします。JavaだとAndroidですね。

以上です。

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

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

#6

isai

総合スコア148

投稿2024/11/06 13:11

クラスをデータとUIに分けるアプローチは良い方向と思いますね!基本的には、データ(ロジック)とUI(表示)を分けることでコードの再利用性や保守性が向上します。

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

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

#7

jimbe

総合スコア13168

投稿2024/11/06 16:03

#5

まずはGUIプログラミングを学ぶことをオススメします。JavaだとAndroidですね。

一応 Swing もまだ使えるはずですので ^^;

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

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

#8

Mirko_Mug_Cup

総合スコア56

投稿2024/11/13 18:23

#7

Swingは個人的によく使っていたGUIフレームワークです。
最近、他のGUIフレームワークで宣言的UIに触れ、Swingでも同様に使ってみたいな、と思い、Swingをラッピングする形でSwingUIなるモノを作って遊んでいました。

ただ、もはや古くなって業務でも使われる可能性のほとんどないGUIフレームワークを他の方に勧めるのもな、と思い、現実的な選択肢としてAndroidをお勧めしました。とは言え、Androidでも宣言的UIを使おうとすると、KotlinでJetpack Composeを使うことになるので、JavaでGUIアプリを構築するのは中々厳しい状況ではあります…

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

この意見交換はまだ受付中です。

会員登録して回答してみよう

アカウントをお持ちの方は

関連した質問