打造一个小超市
写这个小程序的目的
- 熟悉面向接口编程
- 熟悉面向对象模块知识
接口包
1.Card(卡)
package MyFirstSuperMarket;
/**
* 定义卡的接口
* 包含方法 processCardDiscount(计算卡使用中产生的优惠额度)
* 包含参数 totalCost(产生优惠前的总消费额)
* totalCostAfterDiscount(折扣后的总消费额)
* Customer customer(传入的顾客参数)
* ShoppingCart shoppingCart(传入的顾客的购物车参数)
*/
public interface Card {
double processCardDiscount(double totalCost, double totalCostAfterDiscount,
Customer customer, ShoppingCart shoppingCart);
}
2.Category(类别)
package MyFirstSuperMarket;
/**
* 定义枚举类型 Category(种类)
* 列举种类并赋值
* 定义构造方法,提供最低价格和最高价格两个参数给枚举类型
* 提供最低和最高价格的公有类获取和修改方法
*/
public enum Category {
FOOD(10, 100),
COOK(200, 2000),
SNACK(5, 100),
CLOTHES(200, 1000),
ELECTRIC(200, 10000);
int lowerPrice;
int higherPrice;
Category(int lowerPrice, int higherPrice) {
this.lowerPrice = lowerPrice;
this.higherPrice = higherPrice;
}
public int getLowerPrice() {
return lowerPrice;
}
public void setLowerPrice(int lowerPrice) {
this.lowerPrice = lowerPrice;
}
public int getHigherPrice() {
return higherPrice;
}
public void setHigherPrice(int higherPrice) {
this.higherPrice = higherPrice;
}
}
3.Customer(顾客)
package MyFirstSuperMarket;
/**
* 定义顾客接口
* 实现获取顾客ID,定义购物开始初始化工作的方法(getCustID)
* 定义顾客选择种类的方法(chooseCategory)
* 定义购买商品的方法,并传参 buyMerchandise(商品)类型
* 定义购买需要支付金额,传参购物车和总消费额,返回double类型支付额的方法(payFor)
* 定义获取消费金额方法(getMoneySpent)
* 定义检查顾客是否要去结账的方法(wantToCheckOut)
*/
public interface Customer {
String getCustID();
void startShopping();
Category chooseCategory();
int buyMerchandise(Merchandise merchandise);
boolean wantToCheckOut();
double payFor(ShoppingCart shoppingCart, double totalCost);
double getMoneySpent();
}
4.DiscountStrategy(折扣策略)
package MyFirstSuperMarket;
/**
* 定义打折策略接口
* 实现打折方法(discount),并传参购物车
*/
public interface DiscountStrategy {
double discount(ShoppingCart shoppingCart);
}
5.HasCard(是否有卡)
package MyFirstSuperMarket;
/**
* 定义有卡的接口
* 实现对有卡顾客获取卡的方法(getCard)
*/
public interface HasCard {
Card getCard();
}
6.Market(超市)
package MyFirstSuperMarket;
/**
* 定义超市接口
* 暂无操作
*/
public interface Market {
}
7.Merchandise(商品)
package MyFirstSuperMarket;
/**
* 定义商品接口
* 实现商品相关数据的操作方法
* 获取商品名字,售价,进阶,种类和库存
* 定义购买方法 buy(传参购买数量)和购买失败后购物车内商品的放回操作 putBack(传参数量)
*/
public interface Merchandise {
String getName();
double getSoldPrice();
double getPurchasePrice();
int buy(int count);
void putBack(int count);
Category getCategory();
int getCount();
}
8.Shopman(超市员工)
package MyFirstSuperMarket;
/**
* 定义导购及超市工作人员接口
* 实现服务顾客的操作并传参顾客
*/
public interface Shopman {
void serveCustomer(Customer customer);
}
9.ShoppingCart(购物车)
package MyFirstSuperMarket;
import java.util.Date;
/**
* 定义购物车类
* 实现了商品的入购物车方法以及商品的总金额(未使用任何优惠前)并输出到控制台
*/
public class ShoppingCart {
//合理将变量和方法访问控制符号设置为private
//定义商品数组类型变量buy(即顾客放入购物车的商品)
private Merchandise[] buy;
//定义和buy相同下标的数组来记录相同下标下的buy的商品的数量
private int[] count;
//定义当前和最大变量来记录和控制购物车大小
private int curr;
private int max;
/**
* 构造方法,初始化购物车类
* 传入参数 maxTypeToBuy(最多可以购买的商品种类)
* 为 buy和 count初始化空间,数组大小为 maxTypeToBuy
* 为 max 和 curr 赋初值
* @param maxTypeToBuy 最多可以购买的商品的种类
*/
public ShoppingCart(int maxTypeToBuy) {
buy = new Merchandise[maxTypeToBuy];
count = new int[maxTypeToBuy];
max = maxTypeToBuy;
curr = 0;
}
/**
* 定义是否可以继续购入的方法
* 比较当前和最大值的关系
* @return 能否继续购入的布尔值
*/
public boolean canHold() {
return curr < max;
}
/**
* 定义添加方法
* @param m 要加入购物车的商品 m
* @param countToBuy 要买 m 的数量
* @return 添加是否成功的布尔值
*/
public boolean add(Merchandise m, int countToBuy) {
if (!canHold()) {
return false;
}
buy[curr] = m;
this.count[curr] = countToBuy;
curr++;
//m调用商品里的 buy 方法
m.buy(countToBuy);
return true;
}
/**
* 定义计算商品原始价值的方法
* 方便后续计算优惠策略后的金额
* @return 返回购物车内的商品总价值
*/
public double calculateOriginCost() {
//定义局部变量 ret ,并初始化
double ret = 0;
//因为 pos++ 操作在判断和计算之前,所以初始化为-1,到执行第一次时刚好为零
//pos为商品数组的编号
int pos = -1;
//遍历购物车的商品即buy数组,为空就continue,不为空就执行++操作
//循环结束返回 ret
for (Merchandise m :
buy) {
pos++;
if (m == null) {
continue;
}
ret += m.getPurchasePrice() * count[pos];
}
return ret;
}
/**
* 覆盖Object类的toString方法
* 实现购物信息到控制台的输出操作
* @return 购物种类,数量,价格及总金额的输出
*/
@Override
public String toString() {
//StringBuilder可以在sb被修改时在原对象上修改而不是新生成
StringBuilder sb = new StringBuilder();
sb.append("==================================\n");
//Date类获取当前购物时间
sb.append("购物时间:").append(new Date()).append("\n");
int pos = -1;
for (Merchandise m :
buy) {
pos++;
if (m == null) {
continue;
}
sb.append(m.getCategory().name()).append("\t").append(m.getName()).append("\t").append(count[pos])
.append("\t").append(m.getPurchasePrice() * count[pos]).append("\n");
}
sb.append("应付总金额为:").append(calculateOriginCost()).append("\n");
sb.append("======================================");
return sb.toString();
}
}
10.SuperMarket(超市)
package MyFirstSuperMarket;
/**
* 定义超市接口
* 实现获取所有商品的方法 getAllMerchandise,类型为 Merchandise数组
* 实现某种类别的商品随机获取,传参 category(商品类别)
* 实现每日收入的汇总和销售日报的生成
*/
public interface SuperMarket {
Merchandise[] getAllMerchandise();
Merchandise[] getRandomMerchandiseOfCategory(Category category);
void addEarnedMoney(double earnedMoney);
void dailyReport();
}
接口实现包
1.AbsCustomer(抽象顾客类)
package impl;
import MyFirstSuperMarket.Category;
import MyFirstSuperMarket.Customer;
import MyFirstSuperMarket.Merchandise;
import MyFirstSuperMarket.ShoppingCart;
import static util.ShoppingUtil.getRandomCategory;
/**
* 抽象类
* 顾客的抽象类实现了顾客的接口
* 实现顾客接口的所有方法
*/
public abstract class AbsCustomer implements Customer {
//定义顾客本来打算买的东西的种类 shouldBuy,类型为Category
private Category shouldBuy;
//声明顾客ID,可以继续浏览的商品种类数量和当前已经浏览的商品种类的数量
private String custID;
private double moneySpent;
private int guangLeft = 0;
private int guangCount = 0;
//定义静态且不可修改的变量,声明了可以逛的次数为5
public static final int DEFAULT_GUANG_COUNT = 5;
//多态实现构造方法,一个提供浏览逛的次数的设置,一个不提供
public AbsCustomer(Category shouldBuy, String custID, int guangCount) {
this.shouldBuy = shouldBuy;
this.custID = custID;
this.guangCount = guangCount;
}
public AbsCustomer(Category shouldBuy, String custID) {
this.shouldBuy = shouldBuy;
this.custID = custID;
}
//之后的都是一些实现接口和及自己的get/set方法
public int getGuangCount() {
return guangCount;
}
public void setGuangCount(int guangCount) {
this.guangCount = guangCount;
}
public Category getShouldBuy() {
return shouldBuy;
}
@Override
public String getCustID() {
return custID;
}
@Override
//购物开始
//将剩余的逛的次数置为初始值
public void startShopping() {
guangLeft = guangCount;
}
@Override
//选择类型前先看必须买的,否则返回随机商品种类
public Category chooseCategory() {
//+1为了实现一开始就选择进入是否有想买的商品
if (guangLeft + 1 >= guangCount) {
return shouldBuy;
} else {
return getRandomCategory();
}
}
@Override
//执行到此步骤说明已经逛了一次之后,可以逛的剩余次数减一,返回布尔值
//剩余次数小于等于零返回true,想要去结账
public boolean wantToCheckOut() {
guangLeft--;
return guangLeft <= 0;
}
/**
*在方法内实现顾客花费金额的累加
* @param shoppingCart 购物车
* @param totalCost 总价
* @return返回总价即需要支付的钱
*/
@Override
public double payFor(ShoppingCart shoppingCart, double totalCost) {
moneySpent += totalCost;
return totalCost;
}
@Override
public double getMoneySpent() {
return moneySpent;
}
}
2.CashCard(现金卡)
package impl;
import MyFirstSuperMarket.Card;
import MyFirstSuperMarket.Customer;
import MyFirstSuperMarket.ShoppingCart;
/**
* 现金卡类
* 实现接口 卡
*/
public class CashCard implements Card {
private double point;
public CashCard(double point) {
this.point = point;
}
@Override
/**
* 声明方法 processCardDiscount ,传参总消费金额,折扣后总金额,顾客和对应的购物车
* 返回优惠的金额
*/
public double processCardDiscount(double totalCost, double totalCostAfterDiscount,
Customer customer, ShoppingCart shoppingCart) {
//现金卡大于待支付金额和小于待支付金额两种情况
if (totalCostAfterDiscount < point) {
point -= totalCostAfterDiscount;
return totalCostAfterDiscount;
} else {
point = 0;
return point;
}
}
}
3.SimpleMerchandise(简单商品)
package impl;
import MyFirstSuperMarket.Category;
import MyFirstSuperMarket.Merchandise;
/**
* 简单的商品类
* 实现商品接口
*/
public class SimpleMerchandise implements Merchandise {
private String name;
private double soldPrice;
private double purchasePrice;
private int count;
private Category category;
public SimpleMerchandise(String name, double soldPrice, double purchasePrice, int count, Category category) {
this.name = name;
this.soldPrice = soldPrice;
this.purchasePrice = purchasePrice;
this.count = count;
this.category = category;
}
@Override
public String getName() {
return name;
}
@Override
public double getSoldPrice() {
return soldPrice;
}
@Override
public double getPurchasePrice() {
return purchasePrice;
}
@Override
public int buy(int count) {
this.count -= count;
return count;
}
@Override
public void putBack(int count) {
this.count += count;
}
@Override
public Category getCategory() {
return category;
}
@Override
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
4.SimpleShopMan(简单超市员工)
package impl;
import MyFirstSuperMarket.*;
import static util.ShoppingUtil.outPut;
/**
* 简单超市服务员
* 实现超市服务员接口
* 从超市角度输出顾客购物清单,优惠金额和实付金额
*/
public class SimpleShopMan implements Shopman {
private SuperMarket superMarket;
public SimpleShopMan(SuperMarket superMarket) {
this.superMarket = superMarket;
}
private static final int MAX_BUY_DEFAULT = 9;
@Override
/**
* 服务顾客类
*/
public void serveCustomer(Customer customer) {
int maxTypeToBuy = MAX_BUY_DEFAULT;
//强转检测合法性,为最大类型购买赋值为guangCount
if (customer instanceof AbsCustomer) {
maxTypeToBuy = ((AbsCustomer) customer).getGuangCount();
}
//为局部变量 shoppingCart new空间并传参顾客的最大逛商品数量给购物车的最大购买种类数量
ShoppingCart shoppingCart = new ShoppingCart(maxTypeToBuy);
customer.startShopping();
//顾客不结账并且购物车可以放东西就执行循环
//
while ((!customer.wantToCheckOut()) && shoppingCart.canHold()) {
Category category = customer.chooseCategory();
if (category == null) {
continue;
}
Merchandise[] toChoose = superMarket.getRandomMerchandiseOfCategory(category);
for (Merchandise m : toChoose) {
if (m == null) {
continue;
}
int buyCount = customer.buyMerchandise(m);
if (buyCount > 0) {
shoppingCart.add(m, buyCount);
break;
}
}
}
double originCost = shoppingCart.calculateOriginCost();
double finalCost = originCost;
//判断是否有卡,并使用卡优惠
double savedMoney = 0;
if (customer instanceof HasCard) {
Card card = ((HasCard) customer).getCard();
savedMoney = card.processCardDiscount(originCost, finalCost, customer, shoppingCart);
finalCost -= savedMoney;
}
double moneyEarned = customer.payFor(shoppingCart, finalCost);
superMarket.addEarnedMoney(moneyEarned);
outPut("顾客" + customer.getCustID() + "购物清单如下:");
outPut(shoppingCart.toString());
outPut("优惠金额为:" + savedMoney);
outPut("实付金额为:" + moneyEarned);
}
}
5.SimpleSuperMarket(简单超市)
package impl;
import MyFirstSuperMarket.Category;
import MyFirstSuperMarket.Merchandise;
import MyFirstSuperMarket.SuperMarket;
import static util.ShoppingUtil.outPut;
public class SimpleSuperMarket implements SuperMarket {
private String name = "无名";
private Merchandise[] all;
private int[] allCount;
private double totalMoneyEarn;
private int customerCount;
public SimpleSuperMarket(Merchandise[] all) {
this.all = all;
allCount = new int[all.length];
for (int i = 0; i < all.length; i++) {
allCount[i] = all[i].getCount();
}
}
@Override
public Merchandise[] getAllMerchandise() {
return all;
}
@Override
//在一定公式计算下概率为有无商品赋值,其余为空
public Merchandise[] getRandomMerchandiseOfCategory(Category category) {
Merchandise[] ret = new Merchandise[5];
int pos = 0;
for (Merchandise m :
all) {
//m的种类和category相同并且满足后面的条件,将其加入ret,pos++,寻找下一个category的商品
if (m.getCategory() == category && Math.random() > 0.5 && pos < ret.length - 1) {
ret[pos] = m;
pos++;
}
}
return ret;
}
@Override
public void addEarnedMoney(double earnedMoney) {
customerCount++;
this.totalMoneyEarn += earnedMoney;
}
@Override
public void dailyReport() {
outPut("营业额为:" + totalMoneyEarn);
outPut("商品售出情况如下:");
for (int i = 0; i < all.length; i++) {
if (allCount[i] != all[i].getCount()) {
System.out.println(all[i].getCategory().name()
+ "\t" + all[i].getName() + "\t" + (allCount[i] - all[i].getCount()));
}
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int[] getAllCount() {
return allCount;
}
public void setAllCount(int[] allCount) {
this.allCount = allCount;
}
}
6.SuiYuanCustomer(随缘顾客)
package impl;
import MyFirstSuperMarket.Category;
import MyFirstSuperMarket.Merchandise;
/**
* 抽象类必须被继承才可以使用
* 随缘顾客继承可抽象顾客类
*/
public class SuiYuanCustomer extends AbsCustomer {
public static final double MUST_BUY_CHANCE = 0.8;
public static final double GUANG_BUY_CHANCE = 0.1;
public SuiYuanCustomer(Category shouldBuy, String custID) {
super(shouldBuy, custID, DEFAULT_GUANG_COUNT);
}
@Override
//自己设计的一些是否购买的逻辑
public int buyMerchandise(Merchandise merchandise) {
//购买的几率
double chanceToBuy = merchandise.getCategory() == getShouldBuy() ?
MUST_BUY_CHANCE : GUANG_BUY_CHANCE;
//购买的数量
//即使是应该买的,也有可能购买数量为0
if (chanceToBuy < Math.random()) {
return 0;
} else {
return 1 + (int) (Math.random() * 3);
}
}
}
7.ThinkAndBuyCustomer(有购买需求的顾客)
package impl;
import MyFirstSuperMarket.Card;
import MyFirstSuperMarket.Category;
import MyFirstSuperMarket.HasCard;
import MyFirstSuperMarket.Merchandise;
/**
* 知道自己要买什么的顾客
* 继承抽象顾客类并且实现有卡的接口
*/
public class ThinkAndBuyCustomer extends AbsCustomer implements HasCard {
private Card card = VIPCard.Level2;
public ThinkAndBuyCustomer(Category shouldBuy, String custID) {
super(shouldBuy, custID, DEFAULT_GUANG_COUNT);
}
@Override
//符合自己购买需求的商品类返回购买数量1
//不符合的取商品售价和该种类商品中间价为作比较再返回值
public int buyMerchandise(Merchandise merchandise) {
Category category = merchandise.getCategory();
if (category == getShouldBuy()) {
return 1;
}
double soldPrice = merchandise.getSoldPrice();
double avgPrice = (category.getHigherPrice() + category.getLowerPrice()) / 2;
if (soldPrice < avgPrice) {
return 1;
} else {
return 0;
}
}
public void setCard(Card card) {
this.card = card;
}
@Override
public Card getCard() {
return card;
}
}
8.VIPCard(VIP卡)
package impl;
import MyFirstSuperMarket.Card;
import MyFirstSuperMarket.Customer;
import MyFirstSuperMarket.ShoppingCart;
/**
* 枚举 VIP等级
* 实现卡类接口
* 覆盖卡使用后的折扣力度
*/
public enum VIPCard implements Card {
Level0(1),
Level1(0.99),
Level2(0.95),
Level3(0.9),
Level4(0.85),
Level5(0.8);
private double discount;
VIPCard(double discount) {
this.discount = discount;
}
@Override
public double processCardDiscount(double totalCost, double totalCostAfterDiscount,
Customer customer, ShoppingCart shoppingCart) {
return totalCostAfterDiscount * (1 - discount);
}
}
超市工具包
1.ShoppingUtil(整合方法,额外方法类)
package util;
import MyFirstSuperMarket.Category;
import MyFirstSuperMarket.Customer;
import MyFirstSuperMarket.Merchandise;
import MyFirstSuperMarket.SuperMarket;
import impl.*;
import java.util.Scanner;
/**
* 商品工具包类
* 创造超市,顾客,卡
*/
public class ShoppingUtil {
//将输入输出集合化,避免太多的输出语句
private static final Scanner in = new Scanner(System.in);
public static Scanner inPut() {
return in;
}
public static void outPut(Object obj) {
System.out.println(String.valueOf(obj));
}
public static SuperMarket createSupermarket() {
int merchandisePerCategory = 10;
Merchandise[] all = new Merchandise[Category.values().length * merchandisePerCategory];
for (Category category : Category.values()) {
for (int i = 0; i < merchandisePerCategory; i++) {
double soldPrice = Math.random() * (category.getHigherPrice() - category.getLowerPrice())
+ category.getLowerPrice();
double purchasePrice = soldPrice * 0.7;
all[category.ordinal() * merchandisePerCategory + i] = new SimpleMerchandise(
category.name() + i, soldPrice, purchasePrice, 200, category
);
}
}
SimpleSuperMarket superMarket = new SimpleSuperMarket(all);
outPut("请输入超市的名字:");
String s = inPut().next();
if (s.trim().length() > 0) {
superMarket.setName(s.trim());
}
return superMarket;
}
public static Customer createCustomer(boolean auto) {
if (auto) {
String custId = "CUST" + (int) (Math.random() * 10000);
Category shouldBuy = getRandomCategory();
if (Math.random() < 0.5) {
return new SuiYuanCustomer(shouldBuy, custId);
} else {
ThinkAndBuyCustomer ret = new ThinkAndBuyCustomer(shouldBuy, custId);
ret.setCard(getRandomVIPCard());
return ret;
}
}
return null;
}
public static Category getRandomCategory() {
return Category.values()[(int) (Math.random() * 1000) % Category.values().length];
}
public static VIPCard getRandomVIPCard() {
return VIPCard.values()[(int) (Math.random() * 1000) % VIPCard.values().length];
}
}
程序运行
1.ShoppingAPP(main函数)
import MyFirstSuperMarket.Customer;
import MyFirstSuperMarket.Shopman;
import MyFirstSuperMarket.SuperMarket;
import impl.SimpleShopMan;
import static util.ShoppingUtil.*;
public class ShoppingAPP {
public static void main(String[] args) {
SuperMarket superMarket = createSupermarket();
Shopman shopman = new SimpleShopMan(superMarket);
boolean open = true;
while (open) {
//开业后启用超市员工
new ShoppingTask(shopman).executeTask();
outPut("是否继续营业?(YES)");
open = !inPut().next().trim().equalsIgnoreCase("no");
}
superMarket.dailyReport();
}
}
class ShoppingTask {
private Shopman shopman;
public ShoppingTask(Shopman shopman) {
this.shopman = shopman;
}
public void executeTask() {
Customer customer = createCustomer(true);
shopman.serveCustomer(customer);
}
}
总 结
- 接口提供规范,方法名。方法体由实现接口的类覆盖实现
- 抽象类中的方法只能被继承,然后被子类对象调用
- 调用接口的方法必须实现接口中全部的方法
- 抽象类的用法是为了实现接口(大部分)
- 抽象方法和抽象类一样都不可以被直接调用,抽象方法可以被覆盖使用(用于不想让子类直接使用父类的方法,而是让其通过覆盖使用)
- 抽象类不一定包含抽象方法,包含抽象方法的类一定是抽象类
- 抽象方法中的抽象方法不一定要全部被实现,只需要实现有用的即可
- 保持包内的神秘性,合理使用访问限定修饰符 private
- 如果父类没有无参构造方法,子类构造方法要调用父类构造方法,且置于第一行
- hashCode和equals方法需要被覆盖才有用
- 1.若重写了equals(Object obj)方法,则有必要重写hashCode()方法。
2.若两个对象equals(Object obj)返回true,则hashCode()有必要也返回相同的int数。
3.若两个对象equals(Object obj)返回false,则hashCode()不一定返回不同的int数。
4.若两个对象hashCode()返回相同int数,则equals(Object obj)不一定返回true。
5.若两个对象hashCode()返回不同int数,则equals(Object obj)一定返回false。- 反射可以实现private方法和变量的调用
- 通过在特定工具包实现方法来避免重复性操作。eg:通过在util包实现output和input来避免大篇幅的输入输出操作
- 接口中在JDK8中可以有default和静态方法,不需要其他类实现
- 接口只是定义规范,具体业务实现需要定义相关的类去实现接口来完成,最后统一调度
后续还会优化
待更新