【初心者】Javaでケーキを売るプログラムを作ってみた

#〈経緯〉
新卒でIT企業に就職、プログラミング研修で初めてJavaに触れてから1か月半ほど経ちました。簡単なプログラムならある程度作れるようになった(はず)なので、研修での講義と演習を参考に、Javaでケーキを売るプログラムを作成し、備忘録兼アウトプット兼文章を書く練習として記事にまとめようと思いました。
拙い出来ではありますが、見ていただけると嬉しいです。コメントでアドバイスいただけると泣いて喜びます。


#〈要件定義〉
まずプログラムを作成する前に、ケーキ屋をプログラム上で運営するために必要な要素をまとめました。
ケーキ屋さんに必要だと私が判断したのは以下の要素です。

・ケーキについての情報
ケーキの名前、価格、在庫を要素として持つオブジェクトを作成し、またケーキ名や値段をお客さんに伝えたり在庫確認や購入金額の計算を行う機能が必要です。
・購入数についてお客さんから聞き取る
購入数について聞き(入力)、それを数字として認識する機能が必要です。また数字として認識できない場合はお客さんに聞きなおすという行動が必要です。
・新商品を入れたり、在庫を処分する
いつも同じケーキを売るわけではないので、ケーキの種類を足したり引いたりする機能が必要です。

#〈実際に構築してみた〉
前述の３つを軸に、
「メインメソッドのクラス」「Cakeオブジェクトのクラス」「個数入力のクラス」「固定文など定数のクラス」を作成しました。
開発についてはopenJDK ver12、エディターはeclipseを使用しました。
また追加の条件として「ケーキの合計数はおひとり様20個まで」「在庫が売り切れるまで営業を続ける」という制約を付けています。


## メインシステム

```java:MainSystem.java

package cake;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class MainSystem{
	public static void main(String[] args) throws IOException{

		System.out.println(Constant.MSG_ACT_BAR);

		//ケーキ型のリストを作成
		List<Cake> cakes = new ArrayList<Cake>();

		//オブジェクト生成
		Cake cake1 = new Cake("ティラミス",20,400);
		Cake cake2 = new Cake("チーズケーキ",40,300);
		Cake cake3 = new Cake("いちごタルト",10,500);

		//リストに格納、ケーキの種類数は現在３つ
		cakes.add(cake1);
		cakes.add(cake2);
		cakes.add(cake3);


		//ループ回数
		int loopNum =0;


		//ケーキの在庫合計 初期化(ループに使用)
		int sumStockCake =0;

		//ケーキ購入数の格納場所作成
		List<Integer> numBuyCake =new ArrayList<Integer>();

		//廃棄額の格納場所
		List<Integer> dustCake =new ArrayList<Integer>();

		System.out.println(Constant.MSG_ACT_BAR);
		System.out.println("\n\n");



		//在庫切れる（sumStockCake =0）までやります
		do{

			//b1･･･購入制限 b2･･･入力エラーと在庫不足 ループ回数を記録
			boolean b1 =true;
			boolean b2 =true;
			loopNum++;


			//いらっしゃいませ
			System.out.println(Constant.MSG_OPENING_01+"\n");
			System.out.println(Constant.MSG_MENU_BAR);
			System.out.println(Constant.MSG_OPENING_02);

			//在庫の数分、cakesリスト内のCakeを呼び出す
			for(int i=0; i<cakes.size(); i++){
				System.out.println(cakes.get(i).getName() +"\t\t"+cakes.get(i).getPrice()+"円\t\t在庫"+cakes.get(i).getStock()+"個");
			}

			System.out.println(Constant.MSG_MENU_BAR);
			System.out.println("\n\n");


			//ケーキ合計購入数のリセット
			int sumBuyCake;

			//b1･･･購入制限のエラーループ
			do{

				sumBuyCake =0;

				for(int i=0; i<cakes.size(); i++){
					//ケーキの個数を入力、b2･･･正の整数以外でエラーループ
					do{
						System.out.print(cakes.get(i).getName() + Constant.MSG_INPUT_ICON);
						String str = InputMany.cakeNum();
						try{

							//現在の購入個数Listに値がすでに入っているなら削除
							if(numBuyCake.size()== (i+1)){
								numBuyCake.remove(i);
							}

							//購入数をInt型に変換しListに追加、変換できないならcatchを行いループ
							numBuyCake.add(Integer.parseInt(str));

							//購入数がマイナスならループ
							if(numBuyCake.get(i)<0){
								System.out.println(Constant.MSG_ERROR_MINUS);
								b2 = true;

							//購入数＞在庫ならループ
							}else if(numBuyCake.get(i) > cakes.get(i).getStock() ){

								System.out.println(Constant.MSG_ERROR_OVERSTOCK1);
								System.out.println(cakes.get(i).getName()+Constant.MSG_ERROR_OVERSTOCK2);
								System.out.println(cakes.get(i).getName()+"の在庫は"+cakes.get(i).getStock()+"個です。");
								b2 = true;

							}else{
								b2 = false;
							}
						}
						catch(NumberFormatException e){
							System.out.println(Constant.MSG_ERROR_INPUT);
							b2 = true;
						}
					}while(b2);
				}

				//ケーキの合計購入個数を計算
				for(int i=0; i<cakes.size(); i++){
					sumBuyCake +=numBuyCake.get(i);
				}


				//ケーキは合計20個以内
				if(sumBuyCake >20){
					System.out.println(Constant.MSG_ERROR_OVER20);
					b1 =true;

					//購入Listのリセット
					numBuyCake.clear();

				//なんか買えよ
				}else if(sumBuyCake ==0){
					System.out.println(Constant.MSG_ERROR_UNDER1);
					b1 =true;
					numBuyCake.clear();


				//無事に購入できたら在庫を減らす
				}else{
					b1 =false;
					for(int i=0; i<cakes.size(); i++){
						cakes.get(i).decStock(numBuyCake.get(i));
					}
				}
				System.out.println("\n\n");

			}while(b1);

			//購入合計金額計算用
			int priceSumCake =0;

			for(int i=0; i<cakes.size(); i++){

				//合計金額処理  ケーキの金額 * 購入数
				priceSumCake += (cakes.get(i).getPrice() * numBuyCake.get(i));

				//購入文用意
				if(numBuyCake.get(i) != 0){
					System.out.println(cakes.get(i).getName()+"を"+numBuyCake.get(i)+"個、");
				}
			}
			System.out.println("合計"+sumBuyCake+"点で");
			System.out.println(priceSumCake + "円でございます。");

			System.out.println(Constant.MSG_THANKYOU);

			//顧客ごとのケーキ購入数のクリアー
			numBuyCake.clear();


			//シュー追加
			if(loopNum ==2){

				System.out.println("\n\n"+Constant.MSG_ACT_BAR+"\n\n");


				Cake cake4 = new Cake("シュークリーム",30,150);
				cakes.add(cake4);

				System.out.println("\n\n"+Constant.MSG_ACT_BAR+"\n\n");


			//在庫処分
			}else if(loopNum %4 ==0){
				System.out.println("\n\n"+Constant.MSG_ACT_BAR+"\n\n");
				System.out.println(Constant.MSG_DUST_SHOOT);

				int i =0;
				int j =1;

				//i番目とi+j番目の在庫を比較、i+jのほうが大きいならばi+j番目をi側にもって行き、ループ終了
				while(i+j<cakes.size()){

					System.out.print(cakes.get(i).getName()+"と"+cakes.get(i+j).getName()+"で在庫がより多いのは");

					if(cakes.get(i).getStock() < cakes.get(i+j).getStock()){
						System.out.println(cakes.get(i+j).getName()+"です。");
						i+=j;
						j=1;
					}else{
						System.out.println(cakes.get(i).getName()+"です。");
						j++;
					}
				}
				System.out.println("廃棄するのは"+cakes.get(i).getName()+"です。在庫は"+cakes.get(i).getStock()+"個でした。");
				System.out.println("廃棄による損失額："+(cakes.get(i).getStock() * cakes.get(i).getPrice())+"円");
				System.out.println("\n\n"+Constant.MSG_ACT_BAR+"\n\n");
				//廃棄を追加、対象の在庫を0にする
				dustCake.add(cakes.get(i).getStock() * cakes.get(i).getPrice());
				cakes.get(i).decStock(cakes.get(i).getStock());
			}

			//在庫0の商品をメニューから削除 削除すると要素数が減るのでiもちゃんと減らす
			for(int i=0; i<cakes.size(); i++){
				if(cakes.get(i).getStock() == 0){
					cakes.remove(i);
					i--;
				}
			}

			//ケーキの在庫合計 0になればループ終了
			sumStockCake =0;
			for(int i=0; i<cakes.size(); i++){
				sumStockCake +=cakes.get(i).getStock();
			}


		//在庫がないならループ終了
		}while(sumStockCake > 0);

		int sumDust =0;

		for(int i=0; i<dustCake.size(); i++){
			sumDust +=dustCake.get(i);
		}
		System.out.println(Constant.MSG_CLOSE_SHOP);
		System.out.println(Constant.MSG_DUST_SUM+sumDust+"円です。");
	}
}
```

## ケーキオブジェクト周りのクラス

```java:Cake.java

package cake;
public class Cake{

	//ケーキの名前、在庫、値段
	private String name;
	private int stock;
	private int price;


	private Cake(){
		System.out.println(Constant.MSG_MAKE_CAKE);
	}
	//名前、在庫、値段を受け取りオブジェクトを生成する
	public Cake(String name, int stock, int price){
		this();
		this.name =name;
		this.stock =stock;
		this.price =price;
		System.out.println(this.name +Constant.MSG_ADD_CAKE);
	}

	//ケーキの名前を返す
	public String getName(){
		return name;
	}

	//在庫情報を返す
	public int getStock(){
		return stock;
	}

	//ケーキの値段を返す
	public int getPrice(){
		return price;
	}

	//購入判定
	public void decStock(int c){
		this.stock -=c;
	}
}

```


## 入力周りのクラス

```java:InputMany.java

package cake;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class InputMany{

	public static String cakeNum() throws IOException{

		BufferedReader br =new BufferedReader(new InputStreamReader(System.in));
		String str = br.readLine();
		return str;
	}

}
```


## 定型文

```java:Constant.java

package cake;
public class Constant{

	public static final String MSG_MAKE_CAKE ="Cakeオブジェクトを生成しました。";
	public static final String MSG_ADD_CAKE ="を商品に追加しました。";

	public static final String MSG_OPENING_01 ="いらっしゃいませ！\nケーキショップ『Pastel』へようこそ。";
	public static final String MSG_OPENING_02 ="本日のメニュー：";

	public static final String MSG_MENU_BAR ="********************************";
	public static final String MSG_INPUT_ICON ="を購入する個数を入力してください。\n＞";
	public static final String MSG_ERROR_INPUT = "数字を入力してください。";

	public static final String MSG_ERROR_OVER20 = "購入合計が20個を超えています。\n合計は20個までです。\n";
	public static final String MSG_ERROR_UNDER1 ="商品は1点以上お買い求めください。";
	public static final String MSG_ERROR_OVERSTOCK1 = "申し訳ございません。";
	public static final String MSG_THANKYOU ="お買い上げありがとうございます。\n\n\n";
	public static final String MSG_ERROR_OVERSTOCK2 = "の在庫は切れております。";
	public static final String MSG_ERROR_MINUS = "マイナスの値を入力しないでください。";
	public static final String MSG_CLOSE_SHOP = "本日の営業は終了となりました。\nご来店ありがとうございました。";


	public static final String MSG_ACT_BAR ="---------------------------------------------------";
	public static final String MSG_DUST_SHOOT ="売れ残りの在庫処分を行います。";
	public static final String MSG_DUST_SUM ="本日の廃棄処分合計額は";
}
```


# 〈意識した部分〉

個人的な一番の工夫点として、Cakeオブジェクトの格納にリストを用いた点です。

```java:MainSystem_v1_2.java

List<Cake> cakes = new ArrayList<Cake>();
Cake cake1 = new Cake("ティラミス",20,400);
cakes.add(cake1);
```

格納する要素数が可変であるリストで行うことで、addでの追加やremoveでの処分を容易にしました。
配列を用いるとfor構文を使ってCakeの要素を取得する際にすでに在庫のない要素まで読み込んでしまいます。


# 〈改善点について〉

ループ2回目で突然割り込んできたシュークリームをもう少しスマートに記述できるのでしょうか…？
在庫補充のためのプログラム追加、データベースからケーキを生成できるようにするための改修も可能であると思います。