设计模式
设计基本原则
开闭原则
对扩展开放,对修改关闭,在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果,这要易于维护和升级
通过定义抽象类来实现开闭原则,通过定义抽象类来定义这一块代码的实现规范,再写一个具体的子类来实现具体的功能
比如说,输入法的皮肤替换就是这样实现的:我们先定义一个AbstractSkin定义了所有具体皮肤所需要的格式,然后所有的具体皮肤都需要更具这个抽象类去设计(继承这个抽象类,然后补充他的所有方法)
其中SouGouInput和AbstractSkin是聚合关系,它有一个方法就是设置皮肤,它的参数是AbstractSkin,所以我们添加皮肤的时候不用修改这些代码,只需要传入具体的皮肤实现类即可:
public class SougouInput{ private Abstract skin; public void setSkin(AbstractSkin skin){ this,skin = skin; } public void display(){ skin.display(); }}里氏代换原则
任何父类出现的地方,子类一定可以出现,即子类可以扩展父类的功能 ,但是不能改变父类原有的功能。即子类在继承父类的时候,除了添加新的方法完成新增功能以外,,尽量不要重写父类方法
例子
下图中定义了长方形和正方形,众所周知,正方形可以继承长方形。
但是我们还对长方形实现了一个方法resize,当长方形的width < length的时候,会让width增加,这个方法是针对长方形设计的,但是并不能满足子类正方形——因为正方形在setLength和setWidth的时候,会直接把长和宽一起设置成新的值,这样,调用resize的时候,就会一直循环下去直到栈溢出:
由此,我们知道了,其实在这个案例里面长方形并不能做正方形的父类(尽管在数学意义上可以),因为长方形有正方形不具备的方法。因此,我们可以把长方形和正方形的共性抽离出来,让长方形和正方形都实现这一个共性(四边形接口),至于原来长方形独有的方法就让长方形单独去实现,正方形就不会受到这个束缚
高层模块不能依赖低层模块,二者都应该依赖抽象
抽象不应该依赖细节,细节应该依赖抽象
对抽象进行变成,不要对实现进行变成
就像下面的类图:我们的高层模块Computer不应该直接依赖低层的具体类,而是应该通过一个抽象的接口来增加其灵活性,使得这些硬盘、CPU、内存可以更加灵活,否则在这台电脑里面就只能实现具体实现类中的几个硬件,难以更高程度的自定义

接口隔离原则
客户端不应该被迫依赖于它不适用的方法,一个类对另一个类的依赖应该建立在最小的接口上,如果出现这种依赖了其不适用的方法的情况,就应该把原有的依赖给拆分开,使其只继承/实现它需要的即可

迪米特法则
迪米特法则也叫做最少支持原则,比如说,有一个明星,需要和公司洽谈、和粉丝见面,但是这些都是经纪人帮忙沟通的,所以就可以创建一个Agent类,把Fans,Star,company都注入到里面,通过这个Agent来处理需要用到这几个类的方法
如果两个软件实体无需直接通信,那么就不应该发生直接的相互调用,可以通过第三方转发该调用——这样可以降低类之间的耦合度,提高系统的可维护性
合成复用原则
尽量先使用组合或者聚合等关联形式来实现,其次才考虑使用继承关系来实现 继承复用的缺点:
破坏类的封装性——父类的实现细节会暴露给子类 父类和子类耦合性高(改了父类就会影响到子类) 显示了复用的灵活性——从父类这里继承的实现是静态的,在编译的时候就已经定义,无法在运行时改变
现在,车子分为新能源和燃油车,但是车又有很多颜色,我们使用继承复用可以这样:
而使用聚合复用,使用一个接口,将color作为自己属性的一部分(color和Car之间是聚合关系),就是这样:
加入说我们现在要新增一种动力,在继承复用的时候我们就需要新增一个动力源汽车类和两个颜色的子类;在聚合复用的时候我们就只需要新定义一个动力源汽车即可,因为颜色会通过接口传入。这样就节省了很多操作
创建者模式
工厂模式简介
我们还可以举一个例子来分别对应简单工厂、工厂方法、抽象方法:

工厂方法模式
我们来模拟一个场景:有一个互联网公司会上线一个服务,给用户发放优惠券、实物奖品、第三方兑换卡三种奖品
假如现在我们有如下三种类型的商品接口: 序号 类型 接口 1 优惠券 CouponResult sendCoupon(String uId, String couponNumber, String uuid) 2 实物商品 Boolean deliverGoods(DeliverReq req) 3 第三方爱奇艺兑换卡 void grantToken(String bindMobileNumber, String cardId)
从以上接口来看有如下信息:
三个接口返回类型不同,有对象类型、布尔类型、还有一个空类型。 入参不同,发放优惠券需要仿重、兑换卡需要卡ID、实物商品需要发货位置(对象中含有)。 另外可能会随着后续的业务的发展,会新增其他种商品类型。因为你所有的开发需求都是随着业务对市场的拓展而带来的。
最直接的方法是使用if-else的模式去进行区分,以后每新增一种奖品或者功能,都需要去原有的代码逻辑分支里修改
public class PrizeController {
private Logger logger = LoggerFactory.getLogger(PrizeController.class);
public AwardRes awardToUser(AwardReq req) { String reqJson = JSON.toJSONString(req); AwardRes awardRes = null; try { logger.info("奖品发放开始{}。req:{}", req.getuId(), reqJson); // 按照不同类型方法商品[1优惠券、2实物商品、3第三方兑换卡(爱奇艺)] if (req.getAwardType() == 1) { CouponService couponService = new CouponService(); CouponResult couponResult = couponService.sendCoupon(req.getuId(), req.getAwardNumber(), req.getBizId()); if ("0000".equals(couponResult.getCode())) { awardRes = new AwardRes("0000", "发放成功"); } else { awardRes = new AwardRes("0001", couponResult.getInfo()); } } else if (req.getAwardType() == 2) { GoodsService goodsService = new GoodsService(); DeliverReq deliverReq = new DeliverReq(); deliverReq.setUserName(queryUserName(req.getuId())); deliverReq.setUserPhone(queryUserPhoneNumber(req.getuId())); deliverReq.setSku(req.getAwardNumber()); deliverReq.setOrderId(req.getBizId()); deliverReq.setConsigneeUserName(req.getExtMap().get("consigneeUserName")); deliverReq.setConsigneeUserPhone(req.getExtMap().get("consigneeUserPhone")); deliverReq.setConsigneeUserAddress(req.getExtMap().get("consigneeUserAddress")); Boolean isSuccess = goodsService.deliverGoods(deliverReq); if (isSuccess) { awardRes = new AwardRes("0000", "发放成功"); } else { awardRes = new AwardRes("0001", "发放失败"); } } else if (req.getAwardType() == 3) { String bindMobileNumber = queryUserPhoneNumber(req.getuId()); IQiYiCardService iQiYiCardService = new IQiYiCardService(); iQiYiCardService.grantToken(bindMobileNumber, req.getAwardNumber()); awardRes = new AwardRes("0000", "发放成功"); } logger.info("奖品发放完成{}。", req.getuId()); } catch (Exception e) { logger.error("奖品发放失败{}。req:{}", req.getuId(), reqJson, e); awardRes = new AwardRes("0001", e.getMessage()); }
return awardRes; }
private String queryUserName(String uId) { return "花花"; }
private String queryUserPhoneNumber(String uId) { return "15200101232"; }
}目前来看还行,但如果将代码量扩大20倍,2000行的if-else太难维护了!
所以我们需要运用到工厂方法模式,接下来我们调整一下代码的结构,让其具有更好的扩展性和可维护性:

设计统一接口
结合之前我们提到的三个函数,我们需要设计一个统一的接口:
序号 类型 接口 1 优惠券 CouponResult sendCoupon(String uId, String couponNumber, String uuid) 2 实物商品 Boolean deliverGoods(DeliverReq req) 3 第三方爱奇艺兑换卡 void grantToken(String bindMobileNumber, String cardId)
public interface ICommodity { void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception;}然后我们去具体的Java类中去完成具体业务,比如说:
//优惠券发放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()); }
}那么我们应该怎么去判断使用哪一个具体呢——很抱歉,还是if判断或者switch-case。但是好在具体的业务已经被单独处理了,我们如果以后要加发放的奖品,也只需要添加一行if判断。当我们要改具体业务的时候,那么就找到具体的CommodityService中去处理
public class StoreFactory {
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("不存在的商品服务类型"); }
}总结
从上到下的优化来看,工厂方法模式并不复杂,甚至这样的开发结构在你有所理解后,会发现更加简单了。 那么这样的开发的好处知道后,也可以总结出来它的优点;避免创建者与具体的产品逻辑耦合、满足单一职责,每一个业务逻辑实现都在所属自己的类中完成、满足开闭原则,无需更改使用调用方就可以在程序中引入新的产品类型。但这样也会带来一些问题,比如有非常多的奖品类型,那么实现的子类会极速扩张。因此也需要使用其他的模式进行优化,这些在后续的设计模式中会逐步涉及到。
抽象工厂模式
想象一下这种的情况,我们需要创建圆形、矩形、三角形以及红绿蓝三种颜色
我们如果使用工厂方法模式的话,就需要设计6个工厂类去装配具体的服务,这显然不是最优解,我们需要将同类型的产品放入同一个工厂中
那么我们可以画出以下的示意图:
我们先分别为形状和颜色设计接口,使用接口就可以实现draw和fill方法
public interface Shape { void draw();}public interface Color { void fill();}然后把6个具体类进行实现,比如说:
public class Rectangle implements Shape {
@Override public void draw() { System.out.println("Inside Rectangle::draw() method."); }}至此,我们底部的内容就创建完毕了:
然后我们来为 Color 和 Shape 对象创建抽象类来获取工厂。
public abstract class AbstractFactory { public abstract Color getColor(String color); public abstract Shape getShape(String shape);}为了实现抽象工厂的这两个方法,我们需要创建ShapeFactory和ColorFactory
public class ShapeFactory extends AbstractFactory {
@Override public Shape getShape(String shapeType){ if(shapeType == null){ return null; } if(shapeType.equalsIgnoreCase("CIRCLE")){ return new Circle(); } else if(shapeType.equalsIgnoreCase("RECTANGLE")){ return new Rectangle(); } else if(shapeType.equalsIgnoreCase("SQUARE")){ return new Square(); } return null; }
@Override public Color getColor(String color) { return null; }}
public class ColorFactory extends AbstractFactory {
@Override public Shape getShape(String shapeType){ return null; }
@Override public Color getColor(String color) { if(color == null){ return null; } if(color.equalsIgnoreCase("RED")){ return new Red(); } else if(color.equalsIgnoreCase("GREEN")){ return new Green(); } else if(color.equalsIgnoreCase("BLUE")){ return new Blue(); } return null; }}现在我们功能都实现了,只剩一个对用户开放的接口了:
public class FactoryProducer { public static AbstractFactory getFactory(String choice){ if(choice.equalsIgnoreCase("SHAPE")){ return new ShapeFactory(); } else if(choice.equalsIgnoreCase("COLOR")){ return new ColorFactory(); } return null; }}