🔰

Javaでクラス図を描くために

に公開

はじめに:

  • 「内部設計ってどうやって考えたらいいんだろう?」と思っているあなたへ。この記事では、まずはクラス図を描けることを目標として、設計に必要なJavaの基本を再確認していきます。
  • 「外部設計ってどうやるの?授業では習ったけどイメージがつかめない人へ」で作成した仕様書をもとに、内部設計をするためにはどうしたらいいのか?を見ていきます。前回の外部設計記事では「何を作るか(仕様)」をまとめました。
    今回はその仕様から“中身”をどう考えて作っていけばいいか?設計力を伸ばす」ことがテーマです。

内部設計では何を考えればいいの?

内部設計とは?

  • 仕様書(=外部設計)の機能を「プログラムでどう実装するか(どう分割して、どう繋げるか)」を考えること。
  • わかりやすく言うと“家”を建てるときにどんな「部屋」に分けておくか図にするイメージ。部屋は目的ごとに整理されてて、連携する部屋への注意事項メモがある。どんな道具がどの部屋にあって・・のようなイメージ。

重要ポイント:
「正解は1つじゃない」
安全で作りやすい分け方・流れを自分で説明できることが大切!

記事では、決まったやり方ではなく 視点ごとに段階的に 「考え方」を整理 していきます。
小さいプロジェクトを直観的に理解して論理的に整理・組み立てられるようになることを目指します。


外部設計

  • この仕様書から内部設計を組み立てます。

プロジェクト:バスケチーム試合登録アプリ

Ver. 日付 変更内容 作成者
1.0 2025/04/25 初版作成 neko_yashiki
1.1 2025/04/26 機能の明確化、出力条件の整理、将来の拡張機能の可能性を追記 neko_yashik
1.2 2025/04/27 使い方ガイドの表示を追記、出力画面のイメージを記載、学年の入力値を統一、エラーを項目として追加 neko_yashik
1.3 2025/04/30 機能の分類を見直し neko_yashik

【機能一覧】

NO. 機能名 機能概要
1 説明表示 アプリ起動時に1回だけ使い方を説明する
2 データ入力 チーム名、学年、シュート数の入力を受け付け、得点を計算するために必要な情報を集める
3 試合結果処理 2チーム分の得点を計算し、勝敗を決定
4 結果表示 チーム名、得点合計、勝敗、引分を表示する
  • 入力条件

    • 標準入力で受け取る。
    • 入力項目と順番:
      • チーム名(最大20文字、空白含む文字列)
      • 学年(1=1年, 2=2年, 3=3年, 4=mix)
      • 2Pシュート数(int型、0以上)
      • 3Pシュート数(int型、0以上)
      • フリースロー成功数(int型、0以上)
  • 特別操作:

    • 「+end」を入力すると試合登録を終了する。
    • どの入力フェーズでも「+end」を受け付ける。
  • 出力条件

    • アプリケーション開始時に1度だけ使い方の詳細を表示する。
    • チーム名、得点合計、勝敗をそれぞれ表示し、1試合分が終わるごとに区切り線(---)を挿入sする。
    • 結果出力は「+end」のあと、それまで登録された全試合をまとめて表示する。
  • 計算ロジック
    【得点計算】
    得点 = (2Pシュート数 × 2) + (3Pシュート数 × 3) + (フリースロー成功数 × 1)
    【勝敗判定】
    得点の比較によって win/loss/draw を決定する。

  • エラー処理

    • 得点入力がint型以外、または負の数なら再入力を促す。
    • 空入力時は再入力を促す。
    • 学年入力が「1〜4」以外なら再入力を促す。
    • 入力途中で「+end」が入力された場合、その試合の入力は無効にする。
出力画面の例
【最初の説明表示の例】

============ バスケチーム試合登録アプリ =========================================
入力方法:
1.チーム名を入力
2.学年を1,2,3,4,のいずれかの数字で入力:
	1 = 1年、 2 = 2年、 3 = 3年、 4 = mix
3.シュート数の入力:
	2Pシュート数、3Pシュート数、フリースロー成功数の順に「数字のみ」で入力してください。
	シュート数が0の場合は「0」を入力してください。
4.ここで1チーム分の入力が終了します。
	続けて対戦相手チームの入力が始まります。
5.入力終了は「+end」を入力してください。
	※入力の途中で「+end」とした場合、その試合データは保存されずキャンセルされます。
6.終了操作が入力されるまで1→2→3が繰り返されます。
===============================================================================
【入力操作の表示の例】

試合登録
チーム名: 
学年(1=1年, 2=2年, 3=3年, 4=mix): 
2Pシュート数:
3Pシュート数:
フリースロー成功数:

対戦相手の登録
チーム名: 
学年(1=1年, 2=2年, 3=3年, 4=mix): 
2Pシュート数:
3Pシュート数:
フリースロー成功数:
【結果の表示の例】

basketball score
----------------------------------------------
Team: Wings               mix  score: 54 win
Team: Dream Team          1年  score: 32  loss
----------------------------------------------
Team: Flames              2年  score: 86 win
Team: Dream Team          1年  score: 57  loss
----------------------------------------------

内部設計として組み立てるために

φ(.. ) まずざっくり落書き、なんでも書いていくイメージ

  • 必要になりそうなのは・・・
    • チーム名
      • String
        • 入力・出力 / String・・・・Scanner
    • 学年
      • 1 2 3 4(mix) / int
      • 1年、2年、 3年、mix / String
        • 入力 / int
        • 出力 / String
    • シュート数
      • 2P, 3P, 1P /int
        • 入力 int
          • 計算ロジック
    • 計算ロジック
      • 入力 / int
      • 出力 / int
    • 得点
      • 点数 / int
        • 出力 / int
        • 比較 / int
          • win loss draw / String

φ(.. ) 使うか分からないけど単語のメモを書き出してイメージを作っていく
 (クラス名を考える準備にもなる)

Judge          Input           twoPointShots
Calculator     Output          threePointShots
name           Controller      freeThrows
shoot          display
total          Guidance
grade          EXIT
team           message

最初のアプローチ:「仕様を部品に分ける」

① 必要なデータ(型・変数)を洗い出す

  • チーム名:String
  • 学年:int(出力時は学年ごとの表示に変換)
  • 2P/3P/フリースロー:int
  • 得点:int(計算結果)
  • 勝敗:String(win/loss/draw)

② やること(処理)を洗い出す

  • 入力を受け取る
  • 入力チェック・エラー処理
  • 得点計算
  • 勝敗判定・結果出力
  • 入力の繰り返し&終了

「まとまり」(クラスorメソッド)を考える

ポイント:何をグループにするか?
φ(.. ) なんとなく・・きっとこれは仲間にした方がいい・・?

(1) Teamという1つの「かたまり」がいる!

チーム名・・・・・標準入力で受け取る
学年・・・・・・・標準入力で受け取る・・・1,2,3,4から文字列に変換
シュート数・・・・標準入力で受け取る・・・2P, 3P, 1P と種類がある
得点・・・・・・・計算で分かる

これは Teamクラス にする!φ(.. ) 

(2) 入力や出力の“流れ”は別で切り出す

チーム名・・・・・そのまま
学年・・・・・・・変換
シュート数・・・・計算        InputManager 入力を受け取ってからの操作・・φ(-- ) 

出力・・・・・・・1試合ごとに区切り線、チーム名20文字で見た目を整える、printf or format 
OutputManagerみたいな役割を作る?
まとめて出力だからリストか配列が必要?

(3) 得点計算はロジックで分ける

計算クラスに分ける or Team の中に持たせる       名前候補calculateScore

 (2Pシュート数 × 2) + (3Pシュート数 × 3) + (フリースロー成功数 × 1)
 
twoPointShots       値2
threePointShots     値3
freeThrows          値1

・計算:calculateScore()
・判定:JudgeWinner()
これは両方メソッドで確定()つけとこ、どこに置こうか・・φ(.. ) 

(4) 処理の中心「制御」の流れ

使い方ガイド → 1回だけ表示だからスタティックメソッドかな?フラグ?
             ・displayMessage 名前候補・他でも使えそうな名前だなあ・・
             ・displayGuidance 名前候補・ここにピッタリかも

→ 入力 → 得点計算 → 勝敗比較 → 出力 → 繰り返し → 入力へ戻る    
	・ここがメインの流れ
	・Mainクラスで制御 or MainControllerみたいな役割を作るといいかも?
		1使い方
			2得点計算 → 勝敗比較 → 出力のループ
				・1~3の流れと、2のループの流れは別にコントロールする??φ(-- )?
				 →GameController
			3終了
     
※ +end で終了 (特別な操作!)

  • 大まかに形が見えてきたらクラス図を意識していこう!

クラス図を書くための重要ポイント

属性(フィールド)と 操作(メソッド)

  • 書くものは「 クラス名・フィールド・メソッド 」だけ!
  • クラス図は「どんなクラスが必要で、どんなデータや操作を持ってるか」を大ざっぱに考える段階
    【クラス名】
  • 「このクラスは何を表してるか」
    【属性】
  • 「このクラスが持ってる情報(データ)」
    【操作】
  • 「このクラスができること(メソッド)」
クラス名
フィールド①
フィールド②
メソッド①
メソッド②

※設計が複雑な時これ以外の要素が含まれることもあります。

クラスやメソッドをどう決めるか?

  • クラスは「役割ごと」に作る!

    • 例:チームの情報を管理するなら Team クラス
    • 例:試合全体を動かすなら GameManager クラス
  • メソッドは「ひとまとまりの処理」を表す!

    • 例:得点を計算するなら calculateScore()
    • 例:勝敗を判定するなら judgeWinner()

💡クラス=大きなまとまり
💡メソッド=その中の細かい作業 のイメージ!

Team
teamName:チーム名
grade:学年
calculateScore():計算する
judgeWinner():判定する
  • クラス図メモ段階:完成版には public (+) private (-) の記号や引数が必要

プライベートやパブリックをどう決めるか?

  • public(パブリック)

    • 公開するもの
      → 「他のクラスからも使っていいよ!」
      → 外部からのアクセスが必要なものだけパブリックにする。
  • private(プライベート)

    • 非公開、見せない!
      → 「このクラスの中だけで使う!」って意味。
      → 基本、できるだけprivateにして安全に守る

publicクラスはファイルとセット

ファイル名とクラス名が一致しなければならないルール!

// ファイル名が Main.java 

public class Main {   // クラス名が Main
	public static void main(String[] args) { 

// ファイル名が Team.java 

public class Team {   // クラス名が Team
	
	private String name;
	private int grade;
	
ファイル名 クラス名 実装機能 詳細
Main.java Main 実行
Team.java Team
  • 「publicクラスは1ファイルに1つだけ」というルールがある。
  • 「 public でないクラス(=パッケージプライベートクラス)」は、同じファイルに何個か書くことができる。

プライベート属性だけのクラスはプライベート?

💡private と public の考え方

class Team {
    private String teamName;
    private int grade;
    private int twoPointShots;
    private int threePointShots;
    private int freeThrows;
    private int totalScore;
    private String result;
       
  • クラスがプライベート属性だけを持つクラスでも、コンストラクタとかゲッター、セッターがある なら、その クラス自体は public にすることが多い。
    • → クラスを外部から利用するためには、クラス自体が public である必要がある。
    • 外からオブジェクト作ったり( new する)、ゲッターセッターで値を操作するため。

"使いたいか"が大事

public class Team {  // 外からはクラスの存在は見えてる・private たちは見えない
	
	// データ隠蔽・プライベートフィールド
    private String teamName;
    private int grade;
    private int twoPointShots;
    private int threePointShots;
    private int freeThrows;
    private int totalScore;
    private String result;
       
  • private な属性にアクセスできるのはそのクラス内部だけなので、外部からその属性に直接アクセスできるわけではない💡
  • クラスを使うには、コンストラクタゲッター、セッター を介して操作する💡

ちょっと確認👀

🤔 使うか使わないか だったら プライベートフィールド じゃなくて パブリック にしない のは何故?
🤔 プライベートでも セッター(setter)・ゲッター(getter)を作れば、外部からアクセスできる
- 結局外部からアクセスしてるけど・・・?
🤔 そもそも 外部ってなに? 標準入力は外部?

標準入力は外から受け取ってるように見えるけど

  • クラス単体から見たら「標準ライブラリ」を使ってるだけ。
  • 外部アクセス対象とはちょっと違う。
  • 標準入力を扱うからって、そのクラスをpublicにする理由には直接ならない。

クラスが別になったら「外」?

  • 別ファイル・別クラスになったら、"外部"
  • クラス単位!これこそ 外部
  • 外部とは「他のクラス」として、クラス単位で区別されるもの、つまり、あるクラスの中から他のクラスのメソッドやフィールドにアクセスする場合、そのクラスは「外部のクラス」にアクセスするってことになる!

データ隠蔽と情報隠蔽:プライベートについて

  • データ隠蔽:フィールドをプライベート・情報を隠したい(個人名・電話番号など)
  • 情報隠蔽:メソッドをプライベート・何をしてるか教えない(レシピは秘密)
  • 必要なものだけ公開し、公開する必要のないものは隠す!

💡“他のクラスのに 直接さわってほしいもの ”だけpublicにして、それ以外はprivate に隠そう!
✨プライベート(隠すこと)をベースにして安全にパブリックを使うようにしていく。

  • これにより、クラス内部の実装を隠蔽し、コードの保守性やセキュリティを高めることができる。

アクセスの仕方をコントロールして安全 Getter・Setter

  • フィールドをプライベートにして 直接アクセスをできない ようにする!
  • ゲッターとセッターを使えば 間接的にアクセス できる!
  • セッターにチェック機能を入れることもできる。
  • 「このルールでしか変えさせない!」ができる。

基本のアクセス修飾子

修飾子 アクセス範囲 Javaでの修飾子
+ どこからでもアクセス可能 public
# 同じパッケージ内、またはサブクラスからアクセス可能 protected
~ 同じパッケージ内でのみアクセス可能 package-private なし
- そのクラス内からのみアクセス可能 private
  • 他にもスタティックはアンダーラインで表すなどあります。

パッケージプライベート

1ファイルで完結するコードの例

// ファイルの最初に package 文がない(デフォルトパッケージ)

// 修飾子なし = パッケージプライベート
class Team {

    String teamName; // ここもパッケージプライベート
    int grade;
    ......
    ....
  
}

// パブリックなクラス、ファイル名は BasketballMain.java にしないといけない
public class BasketballMain {
    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        ArrayList<Match> matches = new ArrayList<>();
        
        .......
        .......

  • パッケージプライベートは「パッケージ」ってついてるけど、修飾子のこと。
  • パッケージプライベートな領域(同じパッケージ内)のアクセス範囲になる。
  • デフォルトパッケージとは関係ない(明示的にパッケージ文があってもパッケージプライベートにできる)
Team
~teamName: String
~grade: int
  • クラス図に当てはめると「 ~ 」
  • 同じパッケージ内のみという安全性はあるけど、あまり推奨されない。
    • 「データは外部から直接アクセスできないように隠す」で private が推奨される。
    • とはいえ、パッケージプライベートの理解も大事!練習プログラムなら使っていこう。

フィールドをパッケージプライベートから private にするとメソッドが増える例

// パッケージプライベートなクラス
class Team { 
	// パッケージプライベートなフィールド
	String teamName; 
	int grade;
	int twoPoint;
	int threePoint;
	int freeThrow;
	int totalPoints;
	
	// コンストラクタ(パッケージプライベート)
	Team(String teamName, int grade, int twoPoint, int threePoint, int freeThrow) {
		
		this.teamName = teamName;
		this.grade = grade;
		this.twoPoint = twoPoint;
		this.threePoint = threePoint;
		this.freeThrow = freeThrow;
		this.totalPoints = calculateScore();
	}
	
	// プライベートなメソッド・このクラスで完結する動き
	private int calculateScore() {
		return (twoPoint * 2) + (threePoint * 3) + (freeThrow * 1);
	}
}
class Team {
	// フィールドをプライベートに変更
    private String teamName;
    private int grade;
    private int twoPoint;
    private int threePoint;
    private int freeThrow;
    private int totalPoints;  
     
    // コンストラクタはMainクラスなど他のクラスでインスタンスを作ったときに必要だからパブリックにする
    public Team(String teamName, int grade, int twoPoint, int threePoint, int freeThrow) {
        this.teamName = teamName;
        this.grade = grade;
        this.twoPoint = twoPoint;
        this.threePoint = threePoint;
        this.freeThrow = freeThrow;
        this.totalPoints =  calculateScore();
    }
    // Teamクラスのフィールドを使って計算し、Teamクラスに返すだけだからプライベート
    private int  calculateScore() {
        return (twoPoint * 2) + (threePoint * 3) + (freeThrow * 1);
    }
    // ゲッター・セッターはパブリック(フィールドをプライベートに変更したから必要になったメソッド)
    // 外部からフィールドにアクセスするために必要なゲッターメソッド
    public String getTeamName() {
        return teamName;
    }
    public int getGrade() {
        return grade;
    }
    public int getTotalPoints() {
        return totalPoints;
    }
    // フィールド更新のためのセッターメソッド
	public void setTeamName(String teamName) { // セッターはメンバ変数すべてに対して必要
		 this.teamName = teamName;
	}
}
  • 一気に増えた・・・けど、後々発生するかもしれない面倒を回避するためにも、原則である private にする方が良い✨
  • 「 +getter/setterは全フィールド分実装(図では省略)」って書くこともあるらしい
Team
-teamName: String
-grade: int
-twoPoint: int
-threePoint: int
-totalPoints: int
-calculateScore(): int
+getTeamName(): String
+getGrade(): int
+getTotalPoints(): int
+setTeamName(String teamName): void

フィールドなの?メンバ変数なの?いろんな呼び方

フィールド= 属性と、メンバ変数(クラスメンバの中の変数)は、ほぼ同じ意味。
クラスメンバ(変数やメソッドとか含めたクラスのメンバー)→の中の変数を「メンバ変数」っていってる。

💡インスタンス変数

  • staticキーワードなしのフィールド
  • 各オブジェクトごとに独立した値を持つ
  • インスタンス化で生成される

💡クラス変数

  • staticキーワード付きのフィールド
  • 全インスタンスで共有される値を持つ
フィールド(メンバ変数) // インスタンス変数もクラス変数も含む呼び方
│
├── インスタンス変数(staticなし)
└── クラス変数(staticあり)

*ローカル変数(メソッドの中にいる)// メンバ変数(フィールド)ではない

クラスを責任ベースで考えるために

開始→終了までの流れを「責任」で分けながらクラス図に当てはめていきます。

クラス名
-フィールド:型
-フィールド:型
+メソッド(引数):戻り値
  • アクセス修飾子<フィールド名> : <型> で属性(メンバ変数)を表します。
  • アクセス修飾子<メソッド名>(<引数>) : <戻り値> で操作(メソッド)を表します。

🧮「たとえば標準入力から2つの数値を受け取って足し算するだけ」

  • 1クラスで出来る! ← でもこれでは意味がない。設計ではない。
  • 2クラスにする! ← コードの量的にちょうどいい。
    • Mainクラス・・入力→計算クラスへ→結果を表示(全体の流れを制御)
    • 計算クラス・・計算専門
  • 4クラスにする! ← 責任、設計という観点では正解だけど・・
    • Mainクラス・・全体の流れを制御
    • 入力クラス・・エラーチェックなど
    • 計算クラス・・計算専門
    • 出力クラス・・エラーメッセージや結果の表示

💡「粒度 」でバランスよく考える!

粒度とは、ものごとをどれくらい細かく分けて考えるか、という「細かさの度合い」

  • 粒度が粗い(あらい)・・・大きくまとめてある。たくさんの責任や処理をひとつに詰め込んでいる。
  • 粒度が細かい(こまかい)・・・小さな単位に分けてある。ひとつひとつの役割が限定されている。

クラス設計で「責任」を分けるとは、「ひとつのクラスがどの程度“仕事”を持つか=どんな範囲まで担当するか」を決めること。
🧮「たとえば標準入力から2つの数値を受け取って足し算するだけ」なら、(本当は分けなくてもいいとは思いつつ)2つにしてMainクラスで全体の流れをコントロールにしていい!

分けようとすればどこまでも・・・

  • 「まず責任で分けてみて、粒度(細かさ)を見ながら責任をいくつかまとめる」でOK
  • 最初から「いい感じの粒度は分からない」

クラス図に当てはめる

「たとえば標準入力から2つの数値を受け取って足し算するだけ」 をクラス図にしてみる

そんな状況を書くことは無いけどクラス1つの場合

  • Mainクラスのみ
Main
+main(args: String[]): void

2クラスに分ける

  • Mainはある前提でCalculatorクラスを別につくる。
  • MainとCalculatorは同じパッケージにある必要がある。
Calculator
~num1: int
~num2: int
~add(n1: n2): int

2クラスで修飾子をプライベートにする

  • フィールドをプライベートに変更。
  • コンストラクタ+Calculator() を置いてフィールドを初期化する。
    ※クラス図にコンストラクタは書いてない。
  • 結果だけをMainに返して表示、またはCalculatorクラスで結果を出力ならこれでOK(クラス図的には)。
  • Calculatorクラスで結果を出力するのは責任のバランスがおかしいので推奨されない。
  • Mainクラスのローカル変数を使えば1+1=2と式の形で結果表示も可能。
Calculator
-num1: int
-num2: int
+add(n1: int, n2: int): int

Calculatorクラスが将来的にデータをもっと操作するような場合

  • 拡張性を高くするためゲッター・セッターの追加。
  • ※これは1つの例です。修飾子は外部のクラスとの連携で変わります。
Calculator
-num1: int
-num2: int
-result: int
-add(n1: int, n2: int): int
+getNum1(): int
+getNum2(): int
+setNum1(n1: int): void
+setNum2(n1: int): void
+setResult(n1: result): void

ここから、バスケチーム試合登録アプリ・・は次回!

1チームのオブジェクトと、更に1試合=2つのチーム情報のハンドリングや結果をまとめて出力するためのリスト利用など、
設計するにもコードに対する知識がないと不安・・そこを解消するような記事にできるよう頑張ります。


最後に:ここまで読んでいただき、ありがとうございます。

内部設計、正直言ってかなり難しいですね…💦
論理的に考える部分やクラスをどのように分けて、それらがどう繋がるかを考えるのは本当に大変です。さらに、Javaの基礎知識がまだまだ足りていないと、設計からコードに落とし込むところまで進めないので、最低限のコーディング力をしっかりと身につける必要があると痛感しています。

ですが、こうやって少しずつ理解を深めていくことが、後々の成長に繋がると思っています。次回は、さらに深堀りしていく予定なので、これからも一歩ずつ進んでいきたいと思います!

GitHubで編集を提案

Discussion