イントロダクション
下のようなツイートをしてみたところ。。。
#java #プログラミング初心者と繋がりたい
設計(クラス図)と実装が結びつけば良い?— たくのじ (@java_takunoji) November 26, 2020
複数の「いいね」をもらったので、自分の知っていることを記載します。
1. 設計と実装のつながり
2. 設計(クラス図)について
3. オブジェクト指向
結論から言うと「設計図(クラス図)は実装を示している」という認識です。
しかし、「オブジェクト指向で躓く人が多い」というウワサから想像するに、この部分は多くの人がつまずく部分だと思われます。
「躓いた数なら日本一」の自信がある自分としては、以下のものが不明瞭な認識のままではなかろうかと思います。
1. 設計と実装のつながり
2. クラス同士の関係
3. クラスのインスタンス化
4. 処理の追いかけ方
サンプルとして下のようなコンソールアプリを作成しました。
※ 以前作成したのものがありました。。。
これのクラス図も作成しました。
これらを材料にして設計(クラス図)と実装のつながりを見ていこうと思います。
# 1. 設計と実装のつながり
実装をしてない状態では、上記のイメージ(クラス図)のように、詳細な処理が書かれていない状態です。
この部分は「自分で考える」必要があります。
具体的には、**単純に疑問点を明確にしていく作業**があるということです。クラス図の内容を箇条書きにすると下のようになります。
1. MainBankクラスはCalculationクラスを持っている(属性を持っている)
2. MainBankクラスはメインメソッドを持っている(操作を持っている)
3. MainBankクラスはatm()を持っている(操作を持っている)
4. Calcurationクラスはyokingaku()を持っている(属性を持っている)
5. CalcurationクラスはgetYokingaku()を持っている(操作を持っている)
6. CalcurationクラスはsetYokingaku()を持っている(操作を持っている)
6. Calcurationクラスはnyukin()を持っている(操作を持っている)
7. Calcurationクラスはvalidate()を持っている(操作を持っている)
8. Calcurationクラスはcalcurate()を持っている(操作を持っている)
9. InputCheckerクラスはvalidNyukinHikidashi()をもっている(操作を持っている)
上のような内容だと**処理の内容が**わかりません。それもそのはず、コーダー(実装者)が行うべき仕事だからです。
早い話が、自分で考えましょうということです。
上のわかっていることで**確定しているところは「属性」**です。なので属性はそのまま実装していしまいます。
#### ポイントその1
フィールド変数に関しては明確になっている、一例ですが下のような部分です。
> 1. MainBankクラスはCalculationクラスを持っている(属性を持っている)
というのは、フィールド変数にCalculationクラスをもっていて、クラス図にある「-」はアクセス修飾子が「private」ということです。
<[MainBank.java](https://github.com/ZenryokuService/PracticeJava1/blob/master/PracticeJava1/src/jp/zenryoku/apps/atm/MainBank.java)>
public class MainBank { /** 金銭管理クラス */ private Calcuration cal; }
<[Calcuration.java](https://github.com/ZenryokuService/PracticeJava1/blob/master/PracticeJava1/src/jp/zenryoku/apps/atm/Calcuration.java)>
public class Calcuration { /** 預金額 */ private int yokingaku;
<[InputChecker.java](https://github.com/ZenryokuService/PracticeJava1/blob/master/PracticeJava1/src/jp/zenryoku/apps/atm/check/InputChecker.java)>
public class InputChecker { }
## 2.設計(クラス図)について
設計(クラス図)について考えていきます。
#### 不明点のポイント
どのように実装したらよいか?というところに焦点を当てます。
不明点の残る「操作」に関してはとりあえずメソッドだけ作ってしまいます。
上のクラス図には詳細な返却値などの指定がないので、この部分も不明点 -> 自由に実装してよいところ、となるわけです。
メインメソッドは最後にします、それはこれから作成する「操作」をそろえてから、「不足する操作(メソッド)」を作ってから考えるべきだからです。※不足分を後で追加すると面倒なのです。。。
料理に例えると、先に作成する料理の、材料をそろえるようなイメージです。
まずは、不明点の洗い出しを行います。操作の内容が不明な状態なのでメソッドの一覧を作成することになります。
1. MainBank#main()に関してはメインメソッドなので最後にします。
2. MainBank#atm()は、名前からしてコーダー銀行アプリを起動するメソッドにします。
2. Calcuration#getYokingaku()は名前からしてフィールドの値を取得するメソッドにします。
3. Calcuration#setYokingaku()も名前からしてフィールドの値を設定するメソッドにします。
4. Calcuration#nyukin()メソッドは、名前からしてお金を入金するときのメソッドにします。
5. Calcuration#validate()メソッドは、名前からして入力チェックのメソッドにします。
6. Calcuration#calcurate()メソッドは、名前からして計算処理を行うメソッドにします。
ここまで作成したら、処理のイメージが湧いてくるかと思います。
大まかに、Calcurationクラスで、入金(出金)を行い、預金金額の管理も行う。というところです。
そのためには「入金(出金)(nyukin())」メソッドを用意して、計算処理のメソッド(calcurate())を用意して。。。
というような実装イメージがわくと思います。
具体的にメソッドの枠を作ってみましょう、具体的にはメソッドを空実装(中身の実装をしない)をしてみます。
### 実際の業務として実装するときは
「この部分に関しては「このように実装します。よろしいでしょうか?」などのように上長に確認しましょう。
### 操作の実装
早速、実装していきます。まずは入力(IN)と出力(OUT)を明確にします。この部分は設計の工程になります。
業務としては、この詳細な設計部分は設計書に記載されていることがほとんどですが、たまに「よろしく!」といわれることがあるので、そのときは、自分で考えます。
「このように実装します」という報告は忘れないようにしましょう。
まずは、設計図(クラス図)でもメソッドの量が多いので、CalcurationクラスのI/O(INとOUTのこと)を決定します。「空実装」を行うという意味です。しかし、ゲッターとセッターに関しては処理が決まっているので実装してしまいます。
ここまでの実装は以下になります。
public class Calcuration { /** 預金額 */ private int yokingaku; /** コンストラクタ */ public Calcuration() { } /** * 預金金額のゲッター * @return the yokingaku */ public int getYokingaku() { return yokingaku; } /** * 預金金額のセッター * @param yokingaku the yokingaku to set */ public void setYokingaku(int yokingaku) { this.yokingaku = yokingaku; } /** * 入金処理 or 引出し * @param input 標準入力 * @param isNyukin */ public void nyukin(Scanner input, boolean isNyukin) { } /** * 入力チェック処理 * @param in * @return true: 入力エラー false: 入力OK! */ private boolean validate(String in) { return false; } /** * * @param in 数字文字 * @param isNyukin ture: 入金処理 false: 引出し処理 */ private boolean calcurate(String in, boolean isNyukin) { return true; } }
### クラス同士の関係
上のクラス図にあるクラスは以下の3つです。
1. MainBank
2. Calcuration
3. InputChecker
これらの関係性を考えると、下のような役割を持たせてやるとよい関係が築けそうです。
1. MainBank => メインメソッドを実行する
2. Calcuration => 預金金額の計算、管理を行う。
3. InputChecker => 入力チェック処理を管理する。チェック処理はここに書くということです。
この部分(クラスの関係)は、アイディアの良し悪しが入ってくる部分です、そして、プログラミングの面白いところでもあります。上記のような関係よりも良い関係があればそのように実装するべきです。
そして、それぞれのクラスの役割が決まってきたら次は、空になっているメソッドの実装を行います。
補足として、クラス関係を作るときにどう考えたら良いかわからない場合には、とりあえず1と2のクラス関係を作りプログラムを動かしてみて下さい。
書いて動かして見れば、理解出来ます。案ずるより生むが易しと言ったところです。
## 3.オブジェクト指向
ここで、頭の中を設計レベルに戻します。
具体的には、どのような処理を行うか?を考えるというところです。
以下のような、手順で考えるとよいと思います。
1. メインメソッドにコメントで処理の順番を記述する
public static void main(String[] args) { // 1.MainBankクラスのインスタンス化 // 2.MainBank#atm()メソッドを呼び出す }
2. MainBank#atm()メソッドにコメントで処理の順番を記述する
public void atm() { // 1. コーダー銀行の受け付け開始文言を出力 // 2. 無限ループを開始 // 3. 初期画面の文言を出力 // 4. 標準入力を受け付ける // 5. "bye"と入力があった場合は処理を終了する // 6. 入力チェックをする // 7. "in"と入力があった場合は入金処理を行う // 8. "out"と入力があった場合は出金処理を行う }
ここで、"in" もしくは "out" が入力されたときは入金処理を呼び出します。入金時と、出金時を区別するための引数も付けます。
3. 各部品クラス(Calcuration, InputChecker)に実装コメントを書く
この様にすると、不明点を明確にすることができるのではないでしょうか?
あくまでも自分の考えた「設計」なので、もっと良い「設計」があるかもしれません。
この様に、下のクラス(オブジェクト)に役割を持たせ、必要な処理(メソッド)を実装します。処理はなるべく周りに影響が出ないように、実装しているクラスのみに影響範囲が収まるように実装します。
具体的には、実装したメソッドの中でほかのクラスの処理を呼び出すとか、用途が限定されるなどのような実装にならないようにするということです。
具体的には、計算をするメソッドを実装するのに、引数を定義せず、返り値だけ定義してしまうと、決まった計算しかできなくなります。なので、メソッドに対する汎用的な使用方法を考えた実装をしましょう。という事です。
そして、他のクラスの処理を**必ず呼び出さない**というのも不適切な判断なので、この部分はフレキシブルに実装するべきです。
つまりは、ひとつのクラスのみを使用する形で実現すると、例えば足し算をするメソッドを複数クラスに実装しなくてはなりませんが、初めから、計算クラスを作っておいて、足し算メソッドを定義しておけば、足し算処理は「計算クラスの足し算メソッド」を利用すれば、同じ処理を実装する必要がなくなります。
オブジェクト指向プログラミングでは、資源の再利用を可能にする事で無駄な作業を減らし、より良いプログラミング・ライフを楽しんで行ける様に考えます。
実装の経験(自分で実装してみるなどの経験)により、より良い方法を身に着け、より良いプログラムがかける様になります。
この様なところが**「知識」ではなく「技術」**なのです。
### クラスのインスタンス化
上記で出てきたクラスについて、考えます。
> 1. MainBank => メインメソッドを実行する
2. Calcuration => 預金金額の計算、管理を行う。
3. InputChecker => 入力チェック処理を管理する。チェック処理はここに書くということです。
ここで、特殊なのは「メインメソッド」です。これは**static**修飾子がついているので、MainBankクラスの中にあっても、定義したメソッド(メンバ・メソッド)を実行するためには、インスタンス化が必要です。
つまり「new」する必要があるということです。
インスタンス化するというのは、**PCのメモリ上に「クラス」で定義したオブジェクトを作成する**ということです。この部分は、とても抽象的なので理解に苦しみました。
自分の場合は、絵にするとわかりやすかったので絵にすると上記のようなクラス図と似たものになりました。

今回の実装では、インスタンス化するクラスは1つなので、複数ある場合を顧慮しなくてよいですが、ただ一つインスタンス化していないクラスつまり、「new」していないクラスがあります。
InputCheckerクラスです。このクラスのメソッドはstatic修飾子がついていて、インスタンス化しなくてよいのです。
staticはクラスの「インスタンスに依存しない」ということなので、起動するあっぷりケーションには必ず1つです。
具体的にはメインメソッドが必ず一つです。そして、staticをつけたXXXメソッドはクラスに一つです。
例えば、上記のCalcurationクラスを複数作成した場合、預金額はCalcurationクラスのインスタンスの数だけ存在します。
public stataic void main(String[] args) { Calcuration calA = new Calcuration(); Calcuration calB = new Calcuration(); calA.nyukin(); // ここで、入金処理 calB.nyukin(); // ここで、入金処理 System.out.println(calA.getYokingaku()); System.out.println(calB.getYokingaku()); }
のように実装した場合は、calAとcalBで保持している預金金額の値が**別々に**計算されます。
逆に、預金金額の修飾子にstaticがついていた場合は、calAとcalBで保持している預金金額の値が**同じに**なります。
### 処理の追いかけ方
これは、そのまま読むしかないのですが、まずは決まっているところから記載します。
**まずはメインメソッド**から、処理が始まります。これは絶対です。
なので、メインメソッドの処理を追いかければそのまま処理を追いかけることになります。
詳細に関しては、すでに記載しているので割愛します。
以上で、設計図と実装のつながりが理解できたと思います。
如何でしょうか?
でわでわ。。。