目录
1.从面相对象到函数式编程
1.1 设计模式与原则
- 单一职责原则:一个类只负责一个功能领域中的相应职责。高内聚,低耦合。
- 开闭原则:对扩展开放,对修改关闭。不修改原有的代码的情况下进行扩展。
- 里氏代换原则:所有引用父类的地方必须透明地使用其子类的对象。
- 依赖倒转原则: 抽象不应该依赖于细节,细节应该依赖于对象。
- 接口隔离原则:接口拆分。当接口太大时,需要将其分割成更细小的接口。
- 迪米特法则: 减少依赖。一个软件实体类应当尽可能少与其他实体发生相互作用。
1. 创建型模式
创建型模式主要用于处理对象的创建问题。
1.1 单例模式
单例模式用于保证对象只能创建一个实例,并提供对实例的全局访问方法。
单例模式的实现:只由单个类组成。为确保单例实例的唯一性,所有的单例构造器都要被声明私有,再通过静态static方法实现全局访问获取实例。
@Data
public class Singleton {
// 姓名
private String name;
private static Singleton instance;
// 私有构造方法
private Singleton(){
System.out.println("Singleton is Instantiated");
}
/**
* 创建实例:如果实例为空则用构造器创建
* @return 实例
*/
public static Singleton getInstance(){
// 被动创建,在真正需要使用时才去创建
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
当我们在代码中使用单例对象时
Singleton.getInstance().getName()
在getInstance方法中,需要判断实例是否为空,是否需要创建实例。这是较为经典之一的懒汉式单例(延迟加载)。懒汉式单例只有在真正使用的时候才会实例化一个对象并交给自己的引用。饿汉式单例在单例类被加载时候,就实例化一个对象并交给自己的引用 ;
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
懒汉式如果实例为空,可能存在两个线程同时调用getInstance方法的情况。必须加锁 synchronized 才能保证单例,但加锁会影响效率。
单例设计模式在JDK源码⾥⾯的应⽤
JDK中Runtime类 饿汉⽅式
JDK中Desktop类 懒汉⽅式
总结: 一开始我是想这我们一般业务创建对象是否可以通过单例模式创建例如下面代码一样。
但是如果用了懒汉,对象引用是一样的。所以容易出现数据的错乱啥的。我觉得单例模式应该是用于那些不常变得数据。(应该是这样的)
for (int i=0; i< 10; i++){
SingletonLazy singletonHungry = SingletonLazy.getInstance();
singletonHungry.setCode("BZ2135");
singletonHungry.setName("测试001");
// ....省略n多代码
}
1.2 工厂模式
工厂模式用于实现逻辑得封装,并通过公共的接口提供对象的实例化服务,在添加新的类时值需要进行少量的修改。
1.2.1 静态工厂模式
收先创建抽象的Vehicle基类以及继承自它的三个具体类:
public abstract class Vehicle {
// 描述
public abstract void desc();
}
public class Bike extends Vehicle {
@Override
public void desc() {
System.out.println("自行车信息");
}
}
public class VehicleFactory {
// 类型枚举
public enum VehicleType {
Bike, Car, Truck;
}
public static VehicleFactory create(VehicleType type) {
if (type.equals(VehicleType.Bike)) {
return new Bike();
}
if (type.equals(VehicleType.Car)) {
return new Car();
} else {
return null;
}
}
}
这个工厂类的实现就是通过不同枚举的类型去判断进行返回不同的对象。
用户只需要调用Vehicle接口,减少耦合,符合依赖倒置原则;但是需要新增新的Vehicle类时,需要进行修改,打破开闭原则。
1.2.2 使用反射机制进行类注册的简单工厂模式
使用map对象来保存产品ID以及对应的类
private Map<String, Class> registeredProducts = new HashMap<>();
增加注册类的方法
public void registerVehicle(String vehicleId, Class vehicleClass){
registeredProducts.put(vehicleId, vehicleClass);
}
可以通过 registeredProducts.get(type) 获取需要执行的类,然后可以进行调用其方法。前提是需要调用方法,把要调用的类注册进去。
1.2.3 ⼯⼚⽅法模式
⼯⼚模式是对简单⼯⼚模式的进⼀步抽象化,其 好处是可以使系统在不修改原来代码的情况下引进新的 产品,即满⾜开闭原则。通过⼯⼚⽗类定义负责创建产品的公共接⼝,通过⼦类 来确定所需要创建的类型。相⽐简单⼯⼚⽽⾔,此种⽅法具有更多的可扩展性和复 ⽤性,同时也增强了代码的可读性。将类的实例化(具体产品的创建)延迟到⼯⼚类的⼦类 (具体⼯⼚)中完成,即由⼦类来决定应该实例化哪⼀个类。
- IProduct:抽象产品类,描述所有实例所共有的公共接。
- Product:具体产品类,实现抽象产品类的接⼝,⼯⼚ 类创建对象,如果有多个需要定义多个。
- IFactory:抽象⼯⼚类,描述具体⼯⼚的公共接⼝。
- Factory:具体⼯场类,实现创建产品类对象,实现抽 象⼯⼚类的接⼝,如果有多个需要定义多个。
- 抽象产品类与具体产品类创建
/** * 抽象产品类 */ public abstract class Vehicle { public abstract void desc(); } /** * 具体产品类-大车 */ public class Car extends Vehicle { @Override public void desc() { System.out.println("大车"); } } /** * 具体产品类-自行车 */ public class Bike extends Vehicle{ @Override public void desc() { System.out.println("自行车"); } }
- 抽象⼯⼚类与具体⼯场类创建
/** * 抽象⼯⼚类 */ public interface VehicleFactory { Vehicle getVehicle(); } /** * 具体工厂类 */ public class CarVehicleFactory implements VehicleFactory { @Override public Vehicle getVehicle() { return new Car(); } } /** * 具体工厂类 */ public class BikeVehicleFactory implements VehicleFactory { @Override public Vehicle getVehicle() { return new Bike(); } }
⼯⼚⽅法模式与简单工厂模式主要区别是在于,简单工厂模式每次新增一个类都需要改工厂创建方法,违背了开闭原则。工厂方法模式主要是工厂创建由调用方进行创建。
BikeVehicleFactory bikeVehicleFactory = new BikeVehicleFactory();
Vehicle vehicle = bikeVehicleFactory.getVehicle();
vehicle.desc();
1.2.4 抽象⼯⼚⽅法模式
抽象⼯⼚模式:基于上述两种模式的拓展,是⼯⼚⽅法模式的升级版,当需要创建的产品有多个产品线时使⽤抽象⼯⼚模式是⽐较好的选择。抽象⼯⼚模式在Spring中应⽤得最为⼴泛的⼀种设计模式。抽象工厂模式就是将一系列相关的实体类组成一个具体类族,由同一个工厂统一进行生产。强调的是⼀系列相关的产品对象(产品族)。
正方形,圆形,椭圆形是一系列的产品。产品等级结构好比各类的产品,例如奔驰,宝马,马萨拉蒂等品牌。
(1) 定义两个接⼝ Pay、Refund。创建具体的Pay产品、创建具体的Refund产品。定义一系列产品,例如如下创建微信产品。
public interface PayFactory {
/**
* 统一下单
*/
void unifiedorder();
}
/**
* 退款抽象接口
*/
public interface RefundFactory {
/**
* 退款
*/
void refund();
}
// --------------------------- 产品具体实现: 微信功能好比正方形,圆形 ---------------------------
public class WechatPay implements PayFactory {
@Override
public void unifiedorder() {
System.out.println("微信支付支付 统一下单接口");
}
}
public class WechatRefund implements RefundFactory {
@Override
public void refund() {
System.out.println("微信支付 退款");
}
}
(2) 创建抽象⼯⼚ OrderFactory 接⼝ ⾥⾯两个⽅法 createPay/createRefund。
public interface OrderFactory {
/**
* 创建支付
* @return 支付产品工厂
*/
PayFactory createPay();
/**
* 创建退款
* @return 退款产品工厂
*/
RefundFactory createRefund();
}
// --------------------------- 一系列产品组建成产品族 ---------------------------
public class WechatOrderFactory implements OrderFactory {
@Override
public PayFactory createPay() {
return new WechatPay();
}
@Override
public RefundFactory createRefund() {
return new WechatRefund();
}
}
(3)定义⼀个超级⼯⼚创造器,通过传递参数获取对应的⼯⼚。
public class FactoryProducer {
public static OrderFactory getFactory(String type) {
if (type.equalsIgnoreCase("WECHAT")) {
return new WechatOrderFactory();
} else if (type.equalsIgnoreCase("ALI")) {
return new AliOrderFactory();
}
return null;
}
}
2. 结构型模式
2.1 适配器模式
适配器模式使得原本由于接⼝不兼容⽽不能⼀起⼯作的那些类可以⼀起⼯作。常见的适配器:类的适配器模式、对象的适配器模式、接⼝的适配器模式。
应⽤场景:系统需要使⽤现有的类,⽽这些类的接⼝不符合系统的需要。JDK中InputStreamReader就是适配器,JDBC就是我们⽤的最多的适配器模式。
2.1.1类的适配器模式
将⼀个类转换成满⾜另⼀个新接⼝的类时,可以使⽤类的适配器模式,创建⼀个新类继承原有的类,实现新的接⼝即可。
(1)创建老接口。
public class OldModule {
public void methodA(){
System.out.println("老接口");
}
}
(2)创建新接口,包含了老接口以及新接口。
// 扩展的新接口: 包含老接口、新接口
public interface TargetModule {
void methodA();
void methodB();
}
(3) 创建⼀个新类,继承原有的类,实现新的接⼝。
public class Adapter extends OldModule implements TargetModule{
@Override
public void methodB() {
System.out.println("新接口");
}
}
2.1.2 桥接设计模式
将⼀个对象转换成满⾜另⼀个新接⼝的对象 时,可以创建⼀个适配器类,持有原类的⼀个实例,在适配器类的⽅法中,调⽤实例的⽅法就⾏
以下的例子是:
我们需要构建⼀个⼿机类,我们知道⼿机有很多品牌,苹果、华为等,从另外⼀个颜⾊维度,⼜有多种颜⾊,红、⻩、蓝等, 那如果描述这些类的话,传统⽅式就直接通过继承,就需要特别 多的类,品牌2,颜⾊3,就是6个类了,如果后续再增加品牌就 更多了,类数⽬将会激增,即所谓的类爆炸 使⽤桥接模式就可以解决这个问题,且灵活度⼤⼤提⾼。
(1) 定义颜色类以及颜色实现类。
public interface Color {
void useColor();
}
// 颜色实现类
public class RedColor implements Color {
@Override
public void useColor() {
System.out.println("红色组合");
}
}
(2)手机类,让它持有该颜色类。
public abstract class Phone {
protected Color color;
protected void setColor(Color color){
this.color = color;
}
/**
* ⼿机的⽅法
*/
abstract public void run();
}
(3)手机具体类以及调用颜色类方法
public class HwPhone extends Phone {
public HwPhone(Color color){
super.setColor(color);
}
@Override
public void run() {
color.useColor();
System.out.println("华为手机");
}
}
(4)使用
Phone phone = new HwPhone(new RedColor());
phone.run();
3 行为型模式
行为型模式关注对象交互、通信和控制流。
3.1 责任链模式
责任链模式可以让处理器按以下方式处理:如果需要则处理请求,否则请求传递给下个处理器。
以下代码是移动端通过不同参数请求不同接口,大量的if - else语句可以通过责任链模式进行改造。
@PostMapping("/app")
public ResponseEntity insertHmapTenantClient(@RequestBody AppRequestDTO appRequestDTO) throws Exception {
String service = appRequestDTO.getService();
if ("/users/v3/search".equals(service)) {
// 执行获取用户方法接口
} else if ("/getApprovalToPendTotal".equals(service)) {
// 执行获取审批中单据数量接口
} else if ("/postApproval".equals(service)) {
// 发送审批接口
} else if ("/checkInvoice".equals(service)) {
// 校验发票接口
} else if ("/saveInvoice".equals(service)) {
// 保存发票接口
}
}
首先构建处理器:抽象类,提供给所有实际处理器进行集成,拥有handlRequest方法用于接收需要处理的请求。
@Data
public abstract class AppRequestHandler {
//下一个请求处理器
protected AppRequestHandler nextAppRequestHanlder;
//处理请假请求业务的抽象方法
public abstract ResponseEntity headerRequest(AppRequestDTO appRequestDTO);
}
具体的处理器:实现handleRequest 方法的具体类。检查自身是否能处理该请求,不能则传递给下个具体处理器。
获取用户具体的处理器
public class UserHandler extends AppRequestHandler {
@Override
public ResponseEntity headerRequest(AppRequestDTO appRequestDTO) {
if(appRequestDTO.getService().equals("/users/v3/search")){
//调用用户查询接口
System.out.println("调用用户查询接口");
}else {
// 不具备处理条件 下个处理器
this.nextAppRequestHanlder.headerRequest(appRequestDTO);
}
return null;
}
}
发送审批的具体处理器
public class workflowHandler extends AppRequestHandler {
@Override
public ResponseEntity headerRequest(AppRequestDTO appRequestDTO) {
if (appRequestDTO.getService().equals("/postApproval")) {
System.out.println("调用工作流接口");
} else {
nextAppRequestHanlder.headerRequest(appRequestDTO);
}
return null;
}
}
客户端:使用责任链模式的应用程序结构,实例化一个处理器的链,在第一个对象调用handleRequest方法
@PostMapping("/app")
public ResponseEntity insertHmapTenantClient(@RequestBody AppRequestDTO appRequestDTO) {
AppRequestHandler userHandler = new UserHandler(); // 第一个处理器,获取用户的具体实现
AppRequestHandler workflowHandler = new workflowHandler(); //第二个处理器,发送审批的具体实现
userHandler.setNextAppRequestHanlder(workflowHandler); // 设置链路,用户的下个处理器是工作流处理器
userHandler.headerRequest(appRequestDTO); //开始链路调用
return null;
}
问题: 如果我要针对不同的处理器进行返回不同的参数,应该怎么办?你这个抽象的处理器返回的是固定的方法!能否使用泛型或者说我用个统一的封装格式返回。比如用ResponseEntity。
3.2 命令模式
一开始我看书,以为命令模式是可以处理多If - else问题,很不能理解。后来发现命令模式的特点就是将请求的调用者与请求的最终执行者进行了解耦。
命令模式:所有的请求都会被包装成为一个对象。
命令模式中,一般有如下几个角色:
command:命令的抽象接口,其中包含execute方法。该方法应该由具体命令实现。
public interface Command {
public void execute();
}
concreteCommand:具体的命令实现类。它必须执行命令并处理每个具体命令相关的参数,它将命令委托给接收者。
public class WorkFlowCommand implements Command {
private AppReceiver receiver; //接收者
public WorkFlowCommand(AppReceiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.sendWorkFlow();
}
}
receiver:请求的接收者。负责执行与命令关联操作类
public class AppReceiver {
/**
* 发送工作流
*/
public void sendWorkFlow() {
System.out.println("调用工作流接口....");
}
}
invoker:请求的调用者。触发命令的类。
public class AppInvoker {
private Command command;
// 设置具体的命令
public void setCommand(Command command) {
this.command = command;
}
// 执行命令
public void doCommand() {
command.execute();
}
}
client:实例化接收者对象和具体命令的实际类。
@PostMapping("/app")
public void performAction(@RequestBody AppRequestDTO appRequestDTO){
// invoker:请求的调用者。触发命令的类。
AppInvoker appInvoker = new AppInvoker();
// receiver:请求的接收者。负责执行与命令关联操作类
AppReceiver appReceiver = new AppReceiver();
// 具体的命令实现类。
WorkFlowCommand workFlowCommand = new WorkFlowCommand(appReceiver);
// 接受命令并执行
appInvoker.setCommand(workFlowCommand);
appInvoker.doCommand();
}
命令模式的核心思想就是将命令或者请求封装成对象,分离请求调用者和请求最终执行者。
优点:将请求调用者和执行者解耦,适用于底层接口封装,可以通过只增加类就可以实现接口扩展,不需要修改原来的代码。
缺点:如果存在较多的命令或者请求,需要较多的命令类。
参考文档:Java设计模式之命令模式 - chinotan的个人空间 - OSCHINA - 中文开源技术交流社区 Java设计模式(18)之命令模式_innerpeacez成长之路-CSDN博客
问题:我们现在写代码对每一个功能都是封装Service层供调用,Service层每个类就相当于一个个命令。而命令模式就是提供个统一的方法来封装命令所需参数执行一个动作。这不是一样的吗?而且我个人觉得需要新建类变多了。每个命令单独一个类。
3.3 策略模式
策略模式定义了一系列的算法,封装每个算法,并使它们可以相互替换,让算法独立于使用它的客户而变化。
我们要改造以下代码是 根据不同用户进行获取不同商品的折扣 常用的写法:
如果要扩展新的用户类型,就需要加else If 违反了开闭原则
public double sale(String type, double fee){
if("VIP".equals(type)){
// 省略各种业务逻辑
return 0.5 * fee;
}else if("Normal".equals(type)){
return 0.7 * fee;
}else if("SVIP".equals(type)){
return 0.2 * fee;
}else{
return fee;
}
}
首先创建Strategy(抽象策略)特定策略的抽象
public interface DiscountService {
// 用户类型
String type();
// 折扣多少
double discount(double fee);
}
ConcreteStrategy(具体策略): 实现抽象策略的类
普通用户:
@Component
public class NormalDiscountService implements DiscountService {
// 普通用户
@Override
public String type() {
return "Normal";
}
// 折扣
@Override
public double discount(double fee) {
return 0.9 * fee;
}
}
VIP用户
@Component
public class VipDiscountService implements DiscountService {
@Override
public String type() {
return "VIP";
}
@Override
public double discount(double fee) {
return 0.5 * fee;
}
}
Context(环境):运行特定策略类
@Service
public class SaleService {
// 之前的所有具体策略都加入了Spring容器中,可以通过构造器注入获取
HashMap<String, DiscountService> serviceHashMap = new HashMap<>();
public SaleService(List<DiscountService> discountServices) {
for (DiscountService discountService : discountServices) {
serviceHashMap.put(discountService.type(), discountService);
}
}
// 根据type获取策略
public double sale(String type, double fee){
return serviceHashMap.get(type).discount(fee);
}
}
扩展spring 如果自动构建注入List<DiscountService> discountService :
注意:这样做的话需要创建多个策略,那么如何避免多个策略呢? 如果用策略模式+工厂(反射)+枚举呢
参考文档:策略模式+工厂模式(反射)+枚举代替 if..else if.._落叶无声的专栏-CSDN博客
3.4 观察者模式
当一个对象的状态改变时,已经登记的其他对象能够观察到这一改变并被更新。例如:发布订阅、消息通知机制、事件监听、事件驱动编程。
利用观察者模式设计高扩展性的代码。最主要还是用于解耦!
下面代码是 下订单后需要执行一系列的操作,我们利用Spring事件机制改造。
public void saveOrder(){
// 违反开闭原则,单一职责
// 创建订单
System.out.println("创建订单......");
// 发送短信
System.out.println("发送短信......");
// 扩展
System.out.println("推送到支付.....");
}
Suject(主题):由类实现的可观察的接口。应通知的观察者使用attach方法注册,使用detach方法取消注册。
public class OrderEvent extends ApplicationEvent {
public OrderEvent(Object source) {
super(source);
}
}
ConcreteSuject(具体主题):具体主题是实现主题接口的类,它处理观察者列表并更新它们的变化。
ApplicationEvent:ApplicationContext基于Observer模式(java.util包中有对应实现),提供了针对Bean的事件传播功能。
@Component
public class SmsListener implements ApplicationListener<OrderEvent>{
@Override
public void onApplicationEvent(OrderEvent orderEvent) {
//发送短信逻辑
System.out.println("发送短信成功....");
}
}
@Component
public class WxListener implements ApplicationListener<OrderEvent>{
@Override
public void onApplicationEvent(OrderEvent orderEvent) {
//推送微信逻辑
System.out.println("推送微信成功....");
}
}
ObServer(观察者):观察者根据主题中的更改进行更新。该方法通知它们新的状态变化。
public void saveOrder(){
applicationContext.publishEvent(new OrderEvent(new Object()));
}
注意:可以采用SpringCloud Bus
3.5 状态模式
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
创建一个接口。
public interface State {
void handle();
}
创建实现接口的实体类。
public class SendOrderState implements State {
@Override
public void handle() {
System.out.println("订单已经发货");
System.out.println("调⽤短信服务,告诉⽤户 已经发货");
System.out.println("更新物流信息\n");
}
}
public class PayOrderState implements State {
@Override
public void handle() {
System.out.println("新订单已经⽀付");
System.out.println("调⽤商户客服服务,订单 已经⽀付");
System.out.println("调⽤物流服务,未发货\n");
}
}
创建 Context 类。
public class OrderContext {
private State state;
public OrderContext() {
}
public void setState(State state) {
this.state = state;
System.out.println("订单状态边更!!");
this.state.handle();
}
}
使用 Context 来查看当状态 State 改变时的行为变化。
public static void main(String[] args) {
OrderContext orderContext = new OrderContext();
orderContext.setState(new NewOrderState());
orderContext.setState(new PayOrderState());
orderContext.setState(new SendOrderState());
}