目录
一、单一职责原则
单一职责原则又称单一功能原则,面向对象五基本原则之一。
单一职责原则定义:一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。
错误示范:
public class VideoUserService {
public void serveGrade(String userType){
if ("VIP用户".equals(userType)){
System.out.println("VIP用户,视频1080P蓝光");
} else if ("普通用户".equals(userType)){
System.out.println("普通用户,视频720P超清");
} else if ("访客用户".equals(userType)){
System.out.println("访客用户,视频480P高清");
}
}
}
@Test
void singleton() {
VideoUserService service = new VideoUserService();
service.serveGrade("VIP用户");
service.serveGrade("普通用户");
service.serveGrade("访客用户");
}
正确代码
定义一个接口,分别来一个他们的子实现类
public interface IVideoUserService {
void definition();
void advertisement();
}
public class VipVideoUserService implements IVideoUserService {
public void definition() {
System.out.println("VIP用户,视频1080P蓝光");
}
public void advertisement() {
System.out.println("VIP会员,视频无广告");
}
}
public class OrdinaryVideoUserService implements IVideoUserService {
public void definition() {
System.out.println("普通用户,视频720P超清");
}
public void advertisement() {
System.out.println("普通用户,视频有广告");
}
}
public class GuestVideoUserService implements IVideoUserService {
public void definition() {
System.out.println("访客用户,视频480P高清");
}
public void advertisement() {
System.out.println("访客用户,视频有广告");
}
}
void singleton02() {
IVideoUserService guest = new GuestVideoUserService();
guest.advertisement();
guest.definition();
}
二、开闭原则
开闭原则是:规定软件中的对象(其中包括类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的。
举个例子,我么要求圆的面积,π的大概值为3.14,第一个人需要3.14,第二个人想要更加精确,但是又不能动第一个人的数据,所以对自己的进行扩展,代码如下
接口
public interface ICalculationArea {
/**
* 计算面积,长方形
*
* @param x 长
* @param y 宽
* @return 面积
*/
double rectangle(double x, double y);
/**
* 计算面积,三角形
* @param x 边长x
* @param y 边长y
* @param z 边长z
* @return 面积
*
* 海伦公式:S=√[p(p-a)(p-b)(p-c)] 其中:p=(a+b+c)/2
*/
double triangle(double x, double y, double z);
/**
* 计算面积,圆形
* @param r 半径
* @return 面积
*
* 圆面积公式:S=πr²
*/
double circular(double r);
}
第一个人的需求
public class CalculationArea implements ICalculationArea {
private final static double π = 3.14D;
public double rectangle(double x, double y) {
return x * y;
}
public double triangle(double x, double y, double z) {
double p = (x + y + z) / 2;
return Math.sqrt(p * (p - x) * (p - y) * (p - z));
}
public double circular(double r) {
return π * r * r;
}
}
第二个人的需求
public class CalculationAreaExt extends CalculationArea {
private final static double π = 3.141592653D;
@Override
public double circular(double r) {
return π * r * r;
}
}
测试
@Test
void openClose01() {
ICalculationArea area = new CalculationAreaExt();
double circular = area.circular(10);
System.out.println(circular);
}
三、里氏替换原则
里氏替换原则,继承必须确保超类所拥有的性质在子类中依然成立。可以理解为,一个同名不同地方的餐馆,他的营销模式或者内容要一致,可以替换的,而不是风格全变了。
public abstract class BankCard {
private Logger logger = LoggerFactory.getLogger(BankCard.class);
private String cardNo; // 卡号
private String cardDate; // 开卡时间
public BankCard(String cardNo, String cardDate) {
this.cardNo = cardNo;
this.cardDate = cardDate;
}
abstract boolean rule(BigDecimal amount);
// 正向入账,+ 钱
public String positive(String orderId, BigDecimal amount) {
// 入款成功,存款、还款
logger.info("卡号{} 入款成功,单号:{} 金额:{}", cardNo, orderId, amount);
return "0000";
}
// 逆向入账,- 钱
public String negative(String orderId, BigDecimal amount) {
// 入款成功,存款、还款
logger.info("卡号{} 出款成功,单号:{} 金额:{}", cardNo, orderId, amount);
return "0000";
}
/**
* 交易流水查询
*
* @return 交易流水
*/
public List<String> tradeFlow() {
logger.info("交易流水查询成功");
List<String> tradeList = new ArrayList<String>();
tradeList.add("100001,100.00");
tradeList.add("100001,80.00");
tradeList.add("100001,76.50");
tradeList.add("100001,126.00");
return tradeList;
}
public String getCardNo() {
return cardNo;
}
public String getCardDate() {
return cardDate;
}
}
public class CashCard extends BankCard {
private Logger logger = LoggerFactory.getLogger(CashCard.class);
public CashCard(String cardNo, String cardDate) {
super(cardNo, cardDate);
}
boolean rule(BigDecimal amount) {
return true;
}
/**
* 提现
*
* @param orderId 单号
* @param amount 金额
* @return 状态码 0000成功、0001失败、0002重复
*/
public String withdrawal(String orderId, BigDecimal amount) {
// 模拟支付成功
logger.info("提现成功,单号:{} 金额:{}", orderId, amount);
return super.negative(orderId, amount);
}
/**
* 储蓄
*
* @param orderId 单号
* @param amount 金额
*/
public String recharge(String orderId, BigDecimal amount) {
// 模拟充值成功
logger.info("储蓄成功,单号:{} 金额:{}", orderId, amount);
return super.positive(orderId, amount);
}
/**
* 风险校验
*
* @param cardNo 卡号
* @param orderId 单号
* @param amount 金额
* @return 状态
*/
public boolean checkRisk(String cardNo, String orderId, BigDecimal amount) {
// 模拟风控校验
logger.info("风控校验,卡号:{} 单号:{} 金额:{}", cardNo, orderId, amount);
return true;
}
}
public class CreditCard extends CashCard {
private Logger logger = LoggerFactory.getLogger(CreditCard.class);
public CreditCard(String cardNo, String cardDate) {
super(cardNo, cardDate);
}
boolean rule2(BigDecimal amount) {
return amount.compareTo(new BigDecimal(1000)) <= 0;
}
/**
* 提现,信用卡贷款
*
* @param orderId 单号
* @param amount 金额
* @return 状态码
*/
public String loan(String orderId, BigDecimal amount) {
boolean rule = rule2(amount);
if (!rule) {
logger.info("生成贷款单失败,金额超限。单号:{} 金额:{}", orderId, amount);
return "0001";
}
// 模拟生成贷款单
logger.info("生成贷款单,单号:{} 金额:{}", orderId, amount);
// 模拟支付成功
logger.info("贷款成功,单号:{} 金额:{}", orderId, amount);
return super.negative(orderId, amount);
}
/**
* 还款,信用卡还款
*
* @param orderId 单号
* @param amount 金额
* @return 状态码
*/
public String repayment(String orderId, BigDecimal amount) {
// 模拟生成还款单
logger.info("生成还款单,单号:{} 金额:{}", orderId, amount);
// 模拟还款成功
logger.info("还款成功,单号:{} 金额:{}", orderId, amount);
return super.positive(orderId, amount);
}
}
@Test
void replace01() {
logger.info("里氏替换前,CashCard类:");
CashCard bankCard = new CashCard("6214567800989876", "2022-03-05");
// 提现
bankCard.withdrawal("100001", new BigDecimal(100));
// 储蓄
bankCard.recharge("100001", new BigDecimal(100));
logger.info("里氏替换后,CreditCard类:");
CashCard creditCard = new CreditCard("6214567800989876", "2022-03-05");
// 提现
creditCard.withdrawal("100001", new BigDecimal(1000000));
// 储蓄
creditCard.recharge("100001", new BigDecimal(100));
}
@Test
void replace02() {
CreditCard creditCard = new CreditCard("6214567800989876", "2022-03-05");
// 支付,贷款
creditCard.loan("100001", new BigDecimal(100));
// 还款
creditCard.repayment("100001", new BigDecimal(100));
}
两张卡,做了同样的事情,结果是一致的,替换的方式是正确的。
四、迪米特法则
迪米特法则,意义在于降低类之间的耦合。由于每个对象尽量减少对其他对象的了解,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或者有很少)依赖关系。也就是模块与模块之间最少知道,是单个模块能自己独立出来(高内聚,低耦合)。
举个例子,校长、学生、老师,校长应该直接可以观察老师,老师观察学生,而不是校长直接观察学生,而是老师来提供信息给校长。
public class Principal {
private Teacher teacher = new Teacher("丽华", "3年1班");
// 查询班级信息,总分数、学生人数、平均值
public Map<String, Object> queryClazzInfo(String clazzId) {
// 获取班级信息;学生总人数、总分、平均分
int stuCount = teacher.clazzStudentCount();
double totalScore = teacher.clazzTotalScore();
double averageScore = teacher.clazzAverageScore();
// 组装对象,实际业务开发会有对应的类
Map<String, Object> mapObj = new HashMap<>();
mapObj.put("班级", teacher.getClazz());
mapObj.put("老师", teacher.getName());
mapObj.put("学生人数", stuCount);
mapObj.put("班级总分数", totalScore);
mapObj.put("班级平均分", averageScore);
return mapObj;
}
}
public class Teacher {
private String name; // 老师名称
private String clazz; // 班级
private static List<Student> studentList; // 学生
public Teacher() {
}
public Teacher(String name, String clazz) {
this.name = name;
this.clazz = clazz;
}
static {
studentList = new ArrayList<>();
studentList.add(new Student("花花", 10, 589));
studentList.add(new Student("豆豆", 54, 356));
studentList.add(new Student("秋雅", 23, 439));
studentList.add(new Student("皮皮", 2, 665));
studentList.add(new Student("蛋蛋", 19, 502));
}
// 总分
public double clazzTotalScore() {
double totalScore = 0;
for (Student stu : studentList) {
totalScore += stu.getGrade();
}
return totalScore;
}
// 平均分
public double clazzAverageScore(){
double totalScore = 0;
for (Student stu : studentList) {
totalScore += stu.getGrade();
}
return totalScore / studentList.size();
}
// 班级人数
public int clazzStudentCount(){
return studentList.size();
}
public static List<Student> getStudentList() {
return studentList;
}
public String getName() {
return name;
}
public String getClazz() {
return clazz;
}
}
public class Student {
private String name; // 学生姓名
private int rank; // 考试排名(总排名)
private double grade; // 考试分数(总分)
public Student() {
}
public Student(String name, int rank, double grade) {
this.name = name;
this.rank = rank;
this.grade = grade;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getRank() {
return rank;
}
public void setRank(int rank) {
this.rank = rank;
}
public double getGrade() {
return grade;
}
public void setGrade(double grade) {
this.grade = grade;
}
}
@Test
void dimite() {
Principal principal = new Principal();
Map<String, Object> map = principal.queryClazzInfo("3年1班");
logger.info("查询结果:{}", JSON.toJSONString(map));
}
五、接口隔离原则
要求 程序员尽量将臃肿庞大的接口拆分为更小的和更具体的接口,让接口中只包含客户感兴趣的方法。更小的接口,更具体的接口(高内聚,低耦合)。
public interface ISkillArchery {
// 射箭
void doArchery();
}
public interface ISkillInvisible {
// 隐袭
void doInvisible();
}
public interface ISkillSilent {
// 沉默
void doSilent();
}
public interface ISkillVertigo {
// 眩晕
void doVertigo();
}
// 后裔
public class HeroHouYi implements ISkillArchery, ISkillInvisible, ISkillSilent {
@Override
public void doArchery() {
System.out.println("后裔的灼日之矢");
}
@Override
public void doInvisible() {
System.out.println("后裔的隐身技能");
}
@Override
public void doSilent() {
System.out.println("后裔的沉默技能");
}
}
// 廉颇
public class HeroLianPo implements ISkillInvisible, ISkillSilent, ISkillVertigo {
@Override
public void doInvisible() {
System.out.println("廉颇的隐身技能");
}
@Override
public void doSilent() {
System.out.println("廉颇的沉默技能");
}
@Override
public void doVertigo() {
System.out.println("廉颇的眩晕技能");
}
}
@Test
public void InterfaceSegregation(){
// 后裔
HeroHouYi heroHouYi = new HeroHouYi();
heroHouYi.doArchery();
// 廉颇
HeroLianPo heroLianPo = new HeroLianPo();
heroLianPo.doInvisible();
}
六、依赖倒置原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象(依赖接口,降低耦合),简单来说就是要求对抽象进行编程,不要对实现进行编程。其核心思想是:要面向接口编程,不要面向实现编程。依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。
举个例子:上级给下级颁布任务,不可能是上级走到下级面前告诉他要完成xx任务,如果有n个下级,那上级就要走n次,这显然是不可能的,这时,我上级定义了一个标准,下级只需要按照这个标准提交任务即可,这样形成了一个依赖倒置。
// 抽奖接口
public interface IDraw {
List<BetUser> prize(List<BetUser> list, int count);
}
// 用户
public class BetUser {
private String userName; // 用户姓名
private int userWeight; // 用户权重
public BetUser() {
}
public BetUser(String userName, int userWeight) {
this.userName = userName;
this.userWeight = userWeight;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getUserWeight() {
return userWeight;
}
public void setUserWeight(int userWeight) {
this.userWeight = userWeight;
}
}
// 抽奖控制
public class DrawControl {
private IDraw draw;
public List<BetUser> doDraw(IDraw draw, List<BetUser> betUserList, int count) {
return draw.prize(betUserList, count);
}
}
// 随机抽奖
public class DrawRandom implements IDraw {
@Override
public List<BetUser> prize(List<BetUser> list, int count) {
// 集合数量很小直接返回
if (list.size() <= count) return list;
// 乱序集合
Collections.shuffle(list);
// 取出指定数量的中奖用户
List<BetUser> prizeList = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
prizeList.add(list.get(i));
}
return prizeList;
}
}
public class DrawWeightRank implements IDraw {
@Override
public List<BetUser> prize(List<BetUser> list, int count) {
// 按照权重排序
list.sort((o1, o2) -> {
int e = o2.getUserWeight() - o1.getUserWeight();
if (0 == e) return 0;
return e > 0 ? 1 : -1;
});
// 取出指定数量的中奖用户
List<BetUser> prizeList = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
prizeList.add(list.get(i));
}
return prizeList;
}
}
@Test
public void DependencyInversion() {
List<BetUser> betUserList = new ArrayList<>();
betUserList.add(new BetUser("花花", 65));
betUserList.add(new BetUser("豆豆", 43));
betUserList.add(new BetUser("小白", 72));
betUserList.add(new BetUser("笨笨", 89));
betUserList.add(new BetUser("丑蛋", 10));
DrawControl drawControl = new DrawControl();
List<BetUser> prizeRandomUserList = drawControl.doDraw(new DrawRandom(), betUserList, 3);
logger.info("随机抽奖,中奖用户名单:{}", JSON.toJSON(prizeRandomUserList));
List<BetUser> prizeWeightUserList = drawControl.doDraw(new DrawWeightRank(), betUserList, 3);
logger.info("权重抽奖,中奖用户名单:{}", JSON.toJSON(prizeWeightUserList));
}
七、工厂方法
工厂模式又称工厂方法模式,是一种创建型设计模式,其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。
这种设计模式也是 Java 开发中最常见的一种模式,它的主要意图是定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
简单说就是为了提供代码结构的扩展性,屏蔽每一个功能类中的具体实现逻辑。让外部可以更加简单的只是知道调用即可,同时,这也是去掉众多ifelse
的方式。当然这可能也有一些缺点,比如需要实现的类非常多,如何去维护,怎样减低开发成本。但这些问题都可以在后续的设计模式结合使用中,逐步降低。
public interface ICommodity {
void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception;
}
// 工厂
public class StoreFactory {
/**
* 奖品类型方式实例化
* @param commodityType 奖品类型
* @return 实例化对象
*/
public ICommodity getCommodityService(Integer commodityType) {
if (null == commodityType) return null;
if (1 == commodityType) return new CouponCommodityService();
if (2 == commodityType) return new GoodsCommodityService();
if (3 == commodityType) return new CardCommodityService();
throw new RuntimeException("不存在的奖品服务类型");
}
/**
* 奖品类信息方式实例化
* @param clazz 奖品类
* @return 实例化对象
*/
public ICommodity getCommodityService(Class<? extends ICommodity> clazz) throws IllegalAccessException, InstantiationException {
if (null == clazz) return null;
return clazz.newInstance();
}
}
public class CardCommodityService implements ICommodity {
private Logger logger = LoggerFactory.getLogger(CardCommodityService.class);
// 模拟注入
private IQiYiCardService iQiYiCardService = new IQiYiCardService();
public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
String mobile = queryUserMobile(uId);
iQiYiCardService.grantToken(mobile, bizId);
logger.info("请求参数[爱奇艺兑换卡] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
logger.info("测试结果[爱奇艺兑换卡]:success");
}
private String queryUserMobile(String uId) {
return "15200101232";
}
}
public class CouponCommodityService implements ICommodity {
private Logger logger = LoggerFactory.getLogger(CouponCommodityService.class);
private CouponService couponService = new CouponService();
public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
CouponResult couponResult = couponService.sendCoupon(uId, commodityId, bizId);
logger.info("请求参数[优惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
logger.info("测试结果[优惠券]:{}", JSON.toJSON(couponResult));
if (!"0000".equals(couponResult.getCode())) throw new RuntimeException(couponResult.getInfo());
}
}
public class GoodsCommodityService implements ICommodity {
private Logger logger = LoggerFactory.getLogger(GoodsCommodityService.class);
private GoodsService goodsService = new GoodsService();
public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
DeliverReq deliverReq = new DeliverReq();
deliverReq.setUserName(queryUserName(uId));
deliverReq.setUserPhone(queryUserPhoneNumber(uId));
deliverReq.setSku(commodityId);
deliverReq.setOrderId(bizId);
deliverReq.setConsigneeUserName(extMap.get("consigneeUserName"));
deliverReq.setConsigneeUserPhone(extMap.get("consigneeUserPhone"));
deliverReq.setConsigneeUserAddress(extMap.get("consigneeUserAddress"));
Boolean isSuccess = goodsService.deliverGoods(deliverReq);
logger.info("请求参数[实物商品] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
logger.info("测试结果[实物商品]:{}", isSuccess);
if (!isSuccess) throw new RuntimeException("实物商品发放失败");
}
private String queryUserName(String uId) {
return "花花";
}
private String queryUserPhoneNumber(String uId) {
return "15200101232";
}
}
@Test
public void FactoryMethod01() throws Exception {
StoreFactory storeFactory = new StoreFactory();
// 1. 优惠券
ICommodity commodityService_1 = storeFactory.getCommodityService(1);
commodityService_1.sendCommodity("10001", "EGM1023938910232121323432", "791098764902132", null);
// 2. 实物商品
ICommodity commodityService_2 = storeFactory.getCommodityService(2);
commodityService_2.sendCommodity("10001", "9820198721311", "1023000020112221113", new HashMap<String, String>() {{
put("consigneeUserName", "谢飞机");
put("consigneeUserPhone", "15200292123");
put("consigneeUserAddress", "吉林省.长春市.双阳区.XX街道.檀溪苑小区.#18-2109");
}});
// 3. 第三方兑换卡(模拟爱奇艺)
ICommodity commodityService_3 = storeFactory.getCommodityService(3);
commodityService_3.sendCommodity("10001", "AQY1xjkUodl8LO975GdfrYUio", null, null);
}
@Test
public void FactoryMethod02() throws Exception {
StoreFactory storeFactory = new StoreFactory();
// 1. 优惠券
ICommodity commodityService = storeFactory.getCommodityService(CouponCommodityService.class);
commodityService.sendCommodity("10001", "EGM1023938910232121323432", "791098764902132", null);
}
八、抽象工厂
抽象工厂模式与工厂方法模式虽然主要意图都是为了解决,接口选择问题。但在实现上,抽象工厂是一个中心工厂,创建其他工厂的模式。
@Test
public void AbstractFactory() throws Exception {
CacheService proxy_EGM = JDKProxyFactory.getProxy(CacheService.class, EGMCacheAdapter.class);
proxy_EGM.set("user_name_01", "xxx");
String val01 = proxy_EGM.get("user_name_01");
logger.info("缓存服务 EGM 测试,proxy_EGM.get 测试结果:{}", val01);
CacheService proxy_IIR = JDKProxyFactory.getProxy(CacheService.class, IIRCacheAdapter.class);
proxy_IIR.set("user_name_01", "yyy");
String val02 = proxy_IIR.get("user_name_01");
logger.info("缓存服务 IIR 测试,proxy_IIR.get 测试结果:{}", val02);
}
public class JDKProxyFactory {
public static <T> T getProxy(Class<T> cacheClazz, Class<? extends ICacheAdapter> cacheAdapter) throws Exception {
InvocationHandler handler = new JDKInvocationHandler(cacheAdapter.newInstance());
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
return (T) Proxy.newProxyInstance(classLoader, new Class[]{cacheClazz}, handler);
}
}
public class JDKInvocationHandler implements InvocationHandler {
private ICacheAdapter cacheAdapter;
public JDKInvocationHandler(ICacheAdapter cacheAdapter) {
this.cacheAdapter = cacheAdapter;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return ICacheAdapter.class.getMethod(method.getName(), ClassLoaderUtils.getClazzByArgs(args)).invoke(cacheAdapter, args);
}
}
public interface ICacheAdapter {
String get(final String key);
void set(String key, String value);
void set(String key, String value, long timeout, TimeUnit timeUnit);
void del(String key);
}
public class IIRCacheAdapter implements ICacheAdapter {
private IIR iir = new IIR();
@Override
public String get(String key) {
return iir.get(key);
}
@Override
public void set(String key, String value) {
iir.set(key, value);
}
@Override
public void set(String key, String value, long timeout, TimeUnit timeUnit) {
iir.setExpire(key, value, timeout, timeUnit);
}
@Override
public void del(String key) {
iir.del(key);
}
}
public class EGMCacheAdapter implements ICacheAdapter {
private EGM egm = new EGM();
public String get(String key) {
return egm.gain(key);
}
public void set(String key, String value) {
egm.set(key, value);
}
public void set(String key, String value, long timeout, TimeUnit timeUnit) {
egm.setEx(key, value, timeout, timeUnit);
}
public void del(String key) {
egm.delete(key);
}
}
九、建造者模式
建造者模式所完成的内容就是通过将多个简单对象通过一步步的组装构建出一个复杂对象的过程。
而这样的根据相同的物料
,不同的组装所产生出的具体的内容,就是建造者模式的最终意图,也就是;将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
@Test
public void test_Builder(){
Builder builder = new Builder();
// 豪华欧式
System.out.println(builder.levelOne(132.52D).getDetail());
// 轻奢田园
System.out.println(builder.levelTwo(98.25D).getDetail());
// 现代简约
System.out.println(builder.levelThree(85.43D).getDetail());
}
public class Builder {
public IMenu levelOne(Double area) {
return new DecorationPackageMenu(area, "豪华欧式")
.appendCeiling(new LevelTwoCeiling()) // 吊顶,二级顶
.appendCoat(new DuluxCoat()) // 涂料,多乐士
.appendFloor(new ShengXiangFloor()); // 地板,圣象
}
public IMenu levelTwo(Double area){
return new DecorationPackageMenu(area, "轻奢田园")
.appendCeiling(new LevelTwoCeiling()) // 吊顶,二级顶
.appendCoat(new LiBangCoat()) // 涂料,立邦
.appendTile(new MarcoPoloTile()); // 地砖,马可波罗
}
public IMenu levelThree(Double area){
return new DecorationPackageMenu(area, "现代简约")
.appendCeiling(new LevelOneCeiling()) // 吊顶,一级顶
.appendCoat(new LiBangCoat()) // 涂料,立邦
.appendTile(new DongPengTile()); // 地砖,东鹏
}
}
public interface IMenu {
/**
* 吊顶
*/
IMenu appendCeiling(Matter matter);
/**
* 涂料
*/
IMenu appendCoat(Matter matter);
/**
* 地板
*/
IMenu appendFloor(Matter matter);
/**
* 地砖
*/
IMenu appendTile(Matter matter);
/**
* 明细
*/
String getDetail();
}
public interface Matter {
/**
* 场景;地板、地砖、涂料、吊顶
*/
String scene();
/**
* 品牌
*/
String brand();
/**
* 型号
*/
String model();
/**
* 平米报价
*/
BigDecimal price();
/**
* 描述
*/
String desc();
}
// 装修包
public class DecorationPackageMenu implements IMenu {
private List<Matter> list = new ArrayList<Matter>(); // 装修清单
private BigDecimal price = BigDecimal.ZERO; // 装修价格
private BigDecimal area; // 面积
private String grade; // 装修等级;豪华欧式、轻奢田园、现代简约
private DecorationPackageMenu() {
}
public DecorationPackageMenu(Double area, String grade) {
this.area = new BigDecimal(area);
this.grade = grade;
}
public IMenu appendCeiling(Matter matter) {
list.add(matter);
price = price.add(area.multiply(new BigDecimal("0.2")).multiply(matter.price()));
return this;
}
public IMenu appendCoat(Matter matter) {
list.add(matter);
price = price.add(area.multiply(new BigDecimal("1.4")).multiply(matter.price()));
return this;
}
public IMenu appendFloor(Matter matter) {
list.add(matter);
price = price.add(area.multiply(matter.price()));
return this;
}
public IMenu appendTile(Matter matter) {
list.add(matter);
price = price.add(area.multiply(matter.price()));
return this;
}
public String getDetail() {
StringBuilder detail = new StringBuilder("\r\n-------------------------------------------------------\r\n" +
"装修清单" + "\r\n" +
"套餐等级:" + grade + "\r\n" +
"套餐价格:" + price.setScale(2, BigDecimal.ROUND_HALF_UP) + " 元\r\n" +
"房屋面积:" + area.doubleValue() + " 平米\r\n" +
"材料清单:\r\n");
for (Matter matter: list) {
detail.append(matter.scene()).append(":").append(matter.brand()).append("、").append(matter.model()).append("、平米价格:").append(matter.price()).append(" 元。\n");
}
return detail.toString();
}
}
十、原型模式
原型模式主要解决的问题就是创建重复对象,而这部分对象
内容本身比较复杂,生成过程可能从库或者RPC接口中获取数据的耗时较长,因此采用克隆的方式节省时间。
举个例子,一场考试在保证大家的公平性一样的题目下,开始出现试题混排更有做的好的答案选项也混排。这样大大的增加了抄的成本,也更好的做到了考试的公平性。
@Test
public void prototype() throws CloneNotSupportedException {
QuestionBankController questionBankController = new QuestionBankController();
System.out.println(questionBankController.createPaper("花花", "1000001921032"));
System.out.println(questionBankController.createPaper("豆豆", "1000001921051"));
System.out.println(questionBankController.createPaper("大宝", "1000001921987"));
}
// 题库管理
public class QuestionBankController {
private QuestionBank questionBank = new QuestionBank();
public QuestionBankController() {
questionBank.append(new ChoiceQuestion("JAVA所定义的版本中不包括", new HashMap<String, String>() {{
put("A", "JAVA2 EE");
put("B", "JAVA2 Card");
put("C", "JAVA2 ME");
put("D", "JAVA2 HE");
put("E", "JAVA2 SE");
}}, "D")).append(new ChoiceQuestion("下列说法正确的是", new HashMap<String, String>() {{
put("A", "JAVA程序的main方法必须写在类里面");
put("B", "JAVA程序中可以有多个main方法");
put("C", "JAVA程序中类名必须与文件名一样");
put("D", "JAVA程序的main方法中如果只有一条语句,可以不用{}(大括号)括起来");
}}, "A")).append(new ChoiceQuestion("变量命名规范说法正确的是", new HashMap<String, String>() {{
put("A", "变量由字母、下划线、数字、$符号随意组成;");
put("B", "变量不能以数字作为开头;");
put("C", "A和a在java中是同一个变量;");
put("D", "不同类型的变量,可以起相同的名字;");
}}, "B")).append(new ChoiceQuestion("以下()不是合法的标识符", new HashMap<String, String>() {{
put("A", "STRING");
put("B", "x3x;");
put("C", "void");
put("D", "de$f");
}}, "C")).append(new ChoiceQuestion("表达式(11+3*8)/4%3的值是", new HashMap<String, String>() {{
put("A", "31");
put("B", "0");
put("C", "1");
put("D", "2");
}}, "D"))
.append(new AnswerQuestion("小红马和小黑马生的小马几条腿", "4条腿"))
.append(new AnswerQuestion("铁棒打头疼还是木棒打头疼", "头最疼"))
.append(new AnswerQuestion("什么床不能睡觉", "牙床"))
.append(new AnswerQuestion("为什么好马不吃回头草", "后面的草没了"));
}
public String createPaper(String candidate, String number) throws CloneNotSupportedException {
QuestionBank questionBankClone = (QuestionBank) questionBank.clone();
questionBankClone.setCandidate(candidate);
questionBankClone.setNumber(number);
return questionBankClone.toString();
}
}
// 题库
public class QuestionBank implements Cloneable{
private String candidate; // 考生
private String number; // 考号
private ArrayList<ChoiceQuestion> choiceQuestionList = new ArrayList<>();
private ArrayList<AnswerQuestion> answerQuestionList = new ArrayList<>();
public QuestionBank append(ChoiceQuestion choiceQuestion) {
choiceQuestionList.add(choiceQuestion);
return this;
}
public QuestionBank append(AnswerQuestion answerQuestion) {
answerQuestionList.add(answerQuestion);
return this;
}
@Override
public Object clone() throws CloneNotSupportedException {
QuestionBank questionBank = (QuestionBank) super.clone();
questionBank.choiceQuestionList = (ArrayList<ChoiceQuestion>) choiceQuestionList.clone();
questionBank.answerQuestionList = (ArrayList<AnswerQuestion>) answerQuestionList.clone();
// 题目乱序
Collections.shuffle(questionBank.choiceQuestionList);
Collections.shuffle(questionBank.answerQuestionList);
// 答案乱序
ArrayList<ChoiceQuestion> choiceQuestionList = questionBank.choiceQuestionList;
for (ChoiceQuestion question : choiceQuestionList) {
Topic random = TopicRandomUtil.random(question.getOption(), question.getKey());
question.setOption(random.getOption());
question.setKey(random.getKey());
}
return questionBank;
}
public void setCandidate(String candidate) {
this.candidate = candidate;
}
public void setNumber(String number) {
this.number = number;
}
@Override
public String toString() {
StringBuilder detail = new StringBuilder("考生:" + candidate + "\r\n" +
"考号:" + number + "\r\n" +
"--------------------------------------------\r\n" +
"一、选择题" + "\r\n\n");
for (int idx = 0; idx < choiceQuestionList.size(); idx++) {
detail.append("第").append(idx + 1).append("题:").append(choiceQuestionList.get(idx).getName()).append("\r\n");
Map<String, String> option = choiceQuestionList.get(idx).getOption();
for (String key : option.keySet()) {
detail.append(key).append(":").append(option.get(key)).append("\r\n");;
}
detail.append("答案:").append(choiceQuestionList.get(idx).getKey()).append("\r\n\n");
}
detail.append("二、问答题" + "\r\n\n");
for (int idx = 0; idx < answerQuestionList.size(); idx++) {
detail.append("第").append(idx + 1).append("题:").append(answerQuestionList.get(idx).getName()).append("\r\n");
detail.append("答案:").append(answerQuestionList.get(idx).getKey()).append("\r\n\n");
}
return detail.toString();
}
}
// 选择题
public class ChoiceQuestion {
private String name; // 题目
private Map<String, String> option; // 选项;A、B、C、D
private String key; // 答案;B
public ChoiceQuestion() {
}
public ChoiceQuestion(String name, Map<String, String> option, String key) {
this.name = name;
this.option = option;
this.key = key;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, String> getOption() {
return option;
}
public void setOption(Map<String, String> option) {
this.option = option;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}
// 解答题
public class AnswerQuestion {
private String name; // 问题
private String key; // 答案
public AnswerQuestion() {
}
public AnswerQuestion(String name, String key) {
this.name = name;
this.key = key;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}
考生:花花
考号:1000001921032
--------------------------------------------
一、选择题第1题:表达式(11+3*8)/4%3的值是
A:2
B:0
C:31
D:1
答案:A第2题:变量命名规范说法正确的是
A:变量不能以数字作为开头;
B:不同类型的变量,可以起相同的名字;
C:变量由字母、下划线、数字、$符号随意组成;
D:A和a在java中是同一个变量;
答案:A第3题:下列说法正确的是
A:JAVA程序中类名必须与文件名一样
B:JAVA程序的main方法必须写在类里面
C:JAVA程序的main方法中如果只有一条语句,可以不用{}(大括号)括起来
D:JAVA程序中可以有多个main方法
答案:B第4题:以下()不是合法的标识符
A:de$f
B:void
C:STRING
D:x3x;
答案:B第5题:JAVA所定义的版本中不包括
A:JAVA2 HE
B:JAVA2 EE
C:JAVA2 SE
D:JAVA2 ME
E:JAVA2 Card
答案:A二、问答题
第1题:小红马和小黑马生的小马几条腿
答案:4条腿第2题:为什么好马不吃回头草
答案:后面的草没了第3题:铁棒打头疼还是木棒打头疼
答案:头最疼第4题:什么床不能睡觉
答案:牙床
考生:豆豆
考号:1000001921051
--------------------------------------------
一、选择题第1题:下列说法正确的是
A:JAVA程序的main方法必须写在类里面
B:JAVA程序中类名必须与文件名一样
C:JAVA程序中可以有多个main方法
D:JAVA程序的main方法中如果只有一条语句,可以不用{}(大括号)括起来
答案:A第2题:以下()不是合法的标识符
A:de$f
B:STRING
C:x3x;
D:void
答案:D第3题:表达式(11+3*8)/4%3的值是
A:2
B:0
C:1
D:31
答案:A第4题:变量命名规范说法正确的是
A:A和a在java中是同一个变量;
B:变量不能以数字作为开头;
C:不同类型的变量,可以起相同的名字;
D:变量由字母、下划线、数字、$符号随意组成;
答案:B第5题:JAVA所定义的版本中不包括
A:JAVA2 Card
B:JAVA2 EE
C:JAVA2 HE
D:JAVA2 ME
E:JAVA2 SE
答案:C二、问答题
第1题:小红马和小黑马生的小马几条腿
答案:4条腿第2题:为什么好马不吃回头草
答案:后面的草没了第3题:铁棒打头疼还是木棒打头疼
答案:头最疼第4题:什么床不能睡觉
答案:牙床
考生:大宝
考号:1000001921987
--------------------------------------------
一、选择题第1题:以下()不是合法的标识符
A:x3x;
B:STRING
C:de$f
D:void
答案:D第2题:JAVA所定义的版本中不包括
A:JAVA2 ME
B:JAVA2 EE
C:JAVA2 HE
D:JAVA2 SE
E:JAVA2 Card
答案:C第3题:变量命名规范说法正确的是
A:变量由字母、下划线、数字、$符号随意组成;
B:不同类型的变量,可以起相同的名字;
C:A和a在java中是同一个变量;
D:变量不能以数字作为开头;
答案:D第4题:下列说法正确的是
A:JAVA程序的main方法必须写在类里面
B:JAVA程序中类名必须与文件名一样
C:JAVA程序的main方法中如果只有一条语句,可以不用{}(大括号)括起来
D:JAVA程序中可以有多个main方法
答案:A第5题:表达式(11+3*8)/4%3的值是
A:2
B:1
C:0
D:31
答案:A二、问答题
第1题:小红马和小黑马生的小马几条腿
答案:4条腿第2题:为什么好马不吃回头草
答案:后面的草没了第3题:什么床不能睡觉
答案:牙床第4题:铁棒打头疼还是木棒打头疼
答案:头最疼
十一、单例模式
单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
单例模式有 3 个特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点;
懒汉式单例
该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。代码如下:
public class LazySingleton
{
private static volatile LazySingleton instance=null; //保证 instance 在所有线程中同步
private LazySingleton(){} //private 避免类在外部被实例化
public static synchronized LazySingleton getInstance()
{
//getInstance 方法前加同步
if(instance==null)
{
instance=new LazySingleton();
}
return instance;
}
}
饿汉式单例
该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。
public class HungrySingleton
{
private static final HungrySingleton instance=new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance()
{
return instance;
}
}
饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。
应用实例
用懒汉式单例模式模拟产生美国当今总统对象。
public class SingletonLazy
{
public static void main(String[] args)
{
President zt1=President.getInstance();
zt1.getName(); //输出总统的名字
President zt2=President.getInstance();
zt2.getName(); //输出总统的名字
if(zt1==zt2)
{
System.out.println("他们是同一人!");
}
else
{
System.out.println("他们不是同一人!");
}
}
}
class President
{
private static volatile President instance=null; //保证instance在所有线程中同步
//private避免类在外部被实例化
private President()
{
System.out.println("产生一个总统!");
}
public static synchronized President getInstance()
{
//在getInstance方法上加同步
if(instance==null)
{
instance=new President();
}
else
{
System.out.println("已经有一个总统,不能产生新总统!");
}
return instance;
}
public void getName()
{
System.out.println("我是美国总统:特朗普。");
}
}
产生一个总统! 我是美国总统:特朗普。 已经有一个总统,不能产生新总统! 我是美国总统:特朗普。 他们是同一人!
单例模式的应用场景
以下是它通常适用的场景的特点。
在应用场景中,某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
当某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
十二、适配器模式
适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。也称包装器(Wrapper),属于结构型模式,可以理解为原本不兼容的接口,通过适配修改做到统一。
工作原理
将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容。从用户的角度看不到被适配者,是解耦的。用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法,用户收到反馈结果,感觉只是和目标接口交互。
简单来说,适配器模式就像个插头转换器,让不同标准的插头和插座可以一起使用,而插座就是原来的接口,插头是用户期望的接口。或者类比电源适配器,把原来的220V电压转换成5V电压等。
//被适配的类
public class Voltage220V {
public int output220V() {
int src = 220;
System.out.println("电压=" + src);
return src;
}
}
//适配接口
public interface IVoltage5V {
public int output5V();
}
//适配器类
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
// TODO Auto-generated method stub
//获取220V电压
int srcV = output220V();
int dstV = srcV / 44 ; //转成5v
return dstV;
}
}
//调用
public class Phone {
public void charging(IVoltage5V iVoltage5V) {
if(iVoltage5V.output5V() == 5) {
System.out.println("电压是5v,可以充电");
} else if (iVoltage5V.output5V() > 5) {
System.out.println("电压大于5v,不可充电");
}
}
}
//测试
public class Client {
public static void main(String[] args) {
System.out.println("===类适配器测试===");
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}