做网站要学些什么条件建设职业技术学院官网
- 作者: 五速梦信息网
- 时间: 2026年04月18日 09:54
当前位置: 首页 > news >正文
做网站要学些什么条件,建设职业技术学院官网,营销推广有哪些形式,神华科技 网站建设作者#xff1a;后端小肥肠 #x1f347; 我写过的文章中的相关代码放到了gitee#xff0c;地址#xff1a;xfc-fdw-cloud: 公共解决方案 #x1f34a; 有疑问可私信或评论区联系我。 #x1f951; 创作不易未经允许严禁转载。 姊妹篇#xff1a; 【设计模式】#xf… 作者后端小肥肠 我写过的文章中的相关代码放到了gitee地址xfc-fdw-cloud: 公共解决方案 有疑问可私信或评论区联系我。 创作不易未经允许严禁转载。 姊妹篇 【设计模式】万字总结深入理解Java中的创建型设计模式_java设计模式创建-CSDN博客 【设计模式】结构型模式全攻略:从入门到精通的万字实战指南_结构型模式代理模式详解-CSDN博客 目录 1. 前言
- 观察者模式 2.1. 观察者模式介绍 2.2. 观察者模式原理 2.3. 观察者模式实现 2.4. 观察者模式应用实例 2.5. 观察者模式总结 3. 模板方法模式 3.1. 模板方法模式介绍 3.2. 模板方法模式原理 3.3. 模板方法模式实现 3.4. 模板方法模式应用实例 3.5. 模板方法模式总结
- 策略模式 4.1. 策略模式概述 4.2. 策略模式原理 4.3. 策略模式实现 4.4. 策略模式应用实例 4.5. 策略模式总结
- 责任链模式 5.1. 职责链模式介绍 5.2. 职责链模式原理 5.3. 职责链模式实现 5.4. 职责链模式应用实例 5.5. 职责链模式总结
- 命令模式 6.1. 命令模式介绍 6.2. 命令模式原理 6.3. 命令模式实现 6.4. 命令模式总结
- 结语 1. 前言 行为型模式用于描述程序在运行时如何处理复杂的流程控制特别是在多个类或对象之间的协作中确保能够完成单个对象无法独立完成的任务。这类模式主要关注算法的实现和对象之间职责的分配。 根据行为的分派方式行为型模式可以分为类行为模式和对象行为模式。类行为模式通过继承机制在类之间分派行为而对象行为模式则通过组合或聚合在对象之间分配行为。由于组合或聚合关系的耦合度低于继承关系且更符合“合成复用原则”因此对象行为模式通常比类行为模式具有更高的灵活性。 为了系统地介绍这些模式行为模式将分为两篇文章进行探讨。本文将聚焦于基础行为模式具体包括 观察者模式模板方法模式策略模式职责链模式命令模式 这些模式在软件设计中起着至关重要的作用是理解和应用行为型模式的基础。在下篇文章中我们将进一步探讨更复杂和高级的行为型模式。通过逐步掌握这些模式您将为构建更灵活、更易维护的系统架构打下坚实的基础。
- 观察者模式
2.1. 观察者模式介绍
观察者模式的应用场景非常广泛小到代码层面的解耦大到架构层面的系统解耦再或者 一些产品的设计思路都有这种模式的影子.
现在我们常说的基于事件驱动的架构其实也是观察者模式的一种最佳实践。当我们观察某一个对象时对象传递出的每一个行为都被看成是一个事件观察者通过处理每一个事件来完成自身的操作处理。
生活中也有许多观察者模式的应用,比如 汽车与红绿灯的关系,红灯停,绿灯行,在这个过程中交通信号灯是汽车的观察目标,而汽车是观察者. 观察者模式(observer pattern)的原始定义是定义对象之间的一对多依赖关系这样当一个对象改变状态时它的所有依赖项都会自动得到通知和更新。
解释一下上面的定义: 观察者模式它是用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应的作出反应. 在观察者模式中发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以应对多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展. 观察者模式的别名有发布-订阅(Publish/Subscribe)模式,模型-视图(Model-View)模式、源-监听(Source-Listener) 模式等 2.2. 观察者模式原理
观察者模式结构中通常包括: 观察目标和观察者两个继承层次结构. 在观察者模式中有如下角色 Subject抽象主题抽象被观察者抽象主题角色把所有观察者对象保存在一个集合里每个主题都可以有任意数量的观察者抽象主题提供一个接口可以增加和删除观察者对象。 ConcreteSubject具体主题具体被观察者该角色将有关状态存入具体观察者对象在具体主题的内部状态发生改变时给所有注册过的观察者发送通知。 Observer抽象观察者是观察者的抽象类它定义了一个更新接口使得在得到主题更改通知时更新自己。 ConcrereObserver具体观察者实现抽象观察者定义的更新接口以便在得到主题更改通知时更新自身的状态。在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要与具体目标保持一致.
2.3. 观察者模式实现 观察者
/*** 抽象观察者* date 2022/10/11/ public interface Observer {//update方法: 为不同观察者的更新(响应)行为定义相同的接口,不同的观察者对该方法有不同的实现public void update(); }/* 具体观察者* date 2022/10/11/ public class ConcreteObserverOne implements Observer {Overridepublic void update() {//获取消息通知,执行业务代码System.out.println(ConcreteObserverOne 得到通知!);} }/* 具体观察者* date 2022/10/11/ public class ConcreteObserverTwo implements Observer {Overridepublic void update() {//获取消息通知,执行业务代码System.out.println(ConcreteObserverTwo 得到通知!);} } 被观察者
/* 抽象目标类* date 2022/10/11/ public interface Subject {void attach(Observer observer);void detach(Observer observer);void notifyObservers(); } /* 具体目标类* date 2022/10/11/ public class ConcreteSubject implements Subject {//定义集合,存储所有观察者对象private ArrayListObserver observers new ArrayList();//注册方法,向观察者集合中增加一个观察者Overridepublic void attach(Observer observer) {observers.add(observer);}//注销方法,用于从观察者集合中删除一个观察者Overridepublic void detach(Observer observer) {observers.remove(observer);}//通知方法Overridepublic void notifyObservers() {//遍历观察者集合,调用每一个观察者的响应方法for (Observer obs : observers) {obs.update();}} } 测试类
public class Client { public static void main(String[] args) {//创建目标类(被观察者)ConcreteSubject subject new ConcreteSubject(); //注册观察者类,可以注册多个subject.attach(new ConcreteObserverOne());subject.attach(new ConcreteObserverTwo()); //具体主题的内部状态发生改变时给所有注册过的观察者发送通知。subject.notifyObservers();} } 2.4. 观察者模式应用实例 接下来我们使用观察模式,来实现一个买房摇号的程序.摇号结束,需要通过短信告知用户摇号结果,还需要向MQ中保存用户本次摇号的信息. 1 ) 未使用设计模式 /* 模拟买房摇号服务* date 2022/10/11/ public class DrawHouseService {//摇号抽签public String lots(String uId){if(uId.hashCode() % 2 0){return 恭喜ID为: uId 的用户,在本次摇号中中签! !;}else{return 很遗憾,ID为: uId 的用户,您本次未中签! !;}} }public class LotteryResult {private String uId; // 用户idprivate String msg; // 摇号信息private Date dataTime; // 业务时间//getset….. }/* 开奖服务接口* date 2022/10/11/ public interface LotteryService {//摇号相关业务public LotteryResult lottery(String uId); }/* 开奖服务* date 2022/10/11/ public class LotteryServiceImpl implements LotteryService {//注入摇号服务private DrawHouseService houseService new DrawHouseService();Overridepublic LotteryResult lottery(String uId) {//摇号String result houseService.lots(uId);//发短信System.out.println(发送短信通知用户ID为: uId ,您的摇号结果如下: result);//发送MQ消息System.out.println(记录用户摇号结果(MQ), 用户ID: uId ,摇号结果: result);return new LotteryResult(uId,result,new Date());} }Test public void test1(){LotteryService ls new LotteryServiceImpl();String result ls.lottery(1234567887654322);System.out.println(result); } 1 ) 使用观察者模式进行优化 上面的摇号业务中,摇号、发短信、发MQ消息是一个顺序调用的过程,但是除了摇号这个核心功能以外, 发短信与记录信息到MQ的操作都不是主链路的功能,需要单独抽取出来,这样才能保证在后面的开发过程中保证代码的可扩展性和可维护性. 事件监听 /* 事件监听接口* date 2022/10/11/ public interface EventListener {void doEvent(LotteryResult result); } /* 短信发送事件* date 2022/10/11/ public class MessageEventListener implements EventListener {Overridepublic void doEvent(LotteryResult result) {System.out.println(发送短信通知用户ID为: result.getuId() ,您的摇号结果如下: result.getMsg());} } /* MQ消息发送事件* date 2022/10/11/ public class MQEventListener implements EventListener {Overridepublic void doEvent(LotteryResult result) {System.out.println(记录用户摇号结果(MQ), 用户ID: result.getuId() ,摇号结果: result.getMsg());} } 事件处理 /* 事件处理类* date 2022/10/11/ public class EventManager {public enum EventType{MQ,Message}//监听器集合MapEnumEventType, ListEventListener listeners new HashMap();public EventManager(EnumEventType… operations) {for (EnumEventType operation : operations) {this.listeners.put(operation,new ArrayList());}}/* 订阅* param eventType 事件类型* param listener 监听/public void subscribe(EnumEventType eventType, EventListener listener){ListEventListener users listeners.get(eventType);users.add(listener);}/** 取消订阅* param eventType 事件类型* param listener 监听/public void unsubscribe(EnumEventType eventType,EventListener listener){ListEventListener users listeners.get(eventType);users.remove(listener);}/** 通知* param eventType 事件类型* param result 结果/public void notify(EnumEventType eventType, LotteryResult result){ListEventListener users listeners.get(eventType);for (EventListener listener : users) {listener.doEvent(result);}} } 摇号业务处理 /** 开奖服务接口* date 2022/10/11/ public abstract class LotteryService{private EventManager eventManager;public LotteryService(){//设置事件类型eventManager new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);//订阅eventManager.subscribe(EventManager.EventType.Message,new MessageEventListener());eventManager.subscribe(EventManager.EventType.MQ,new MQEventListener());}public LotteryResult lotteryAndMsg(String uId){LotteryResult result lottery(uId);//发送通知eventManager.notify(EventManager.EventType.Message,result);eventManager.notify(EventManager.EventType.MQ,result);return result;}public abstract LotteryResult lottery(String uId); }/* 开奖服务* date 2022/10/11**/ public class LotteryServiceImpl extends LotteryService {//注入摇号服务private DrawHouseService houseService new DrawHouseService();Overridepublic LotteryResult lottery(String uId) {//摇号String result houseService.lots(uId);return new LotteryResult(uId,result,new Date());} } 测试 Test public void test2(){LotteryService ls new LotteryServiceImpl();LotteryResult result ls.lotteryAndMsg(1234567887654322);System.out.println(result); } 2.5. 观察者模式总结 - 观察者模式的优点 降低了目标与观察者之间的耦合关系两者之间是抽象耦合关系。 被观察者发送通知所有注册的观察者都会收到信息【可以实现广播机制】
- 观察者模式的缺点 如果观察者非常多的话那么所有的观察者收到被观察者发送的通知会耗时 如果被观察者有循环依赖的话那么被观察者发送通知会使观察者循环调用会导致系统崩溃
3 ) 观察者模式常见的使用场景 当一个对象状态的改变需要改变其他对象时。比如商品库存数量发生变化时需要通知商品详情页、购物车等系统改变数量。 一个对象发生改变时只想要发送通知而不需要知道接收者是谁。比如订阅微信公众号的文章发送者通过公众号发送订阅者并不知道哪些用户订阅了公众号。 需要创建一种链式触发机制时。比如在系统中创建一个触发链A 对象的行为将影响 B 对象B 对象的行为将影响 C 对象……这样通过观察者模式能够很好地实现。 微博或微信朋友圈发送的场景。这是观察者模式的典型应用场景一个人发微博或朋友圈只要是关联的朋友都会收到通知一旦取消关注此人以后将不会收到相关通知。 需要建立基于事件触发的场景。比如基于 Java UI 的编程所有键盘和鼠标事件都由它的侦听器对象和指定函数处理。当用户单击鼠标时订阅鼠标单击事件的函数将被调用并将所有上下文数据作为方法参数传递给它。
4 ) JDK 中对观察者模式的支持 JDK中提供了Observable类以及Observer接口,它们构成了JDK对观察者模式的支持. java.util.Observer 接口: 该接口中声明了一个方法,它充当抽象观察者,其中声明了一个update方法. void update(Observable o, Object arg); java.util.Observable 类: 充当观察目标类(被观察类) , 在该类中定义了一个Vector集合来存储观察者对象.下面是它最重要的 3 个方法。 void addObserver(Observer o) 方法用于将新的观察者对象添加到集合中。 void notifyObservers(Object arg) 方法调用集合中的所有观察者对象的 update方法通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。 void setChange() 方法用来设置一个 boolean 类型的内部标志注明目标对象发生了变化。当它为true时notifyObservers() 才会通知观察者。
用户可以直接使用Observer接口和Observable类作为观察者模式的抽象层,再自定义具体观察者类和具体观察目标类,使用JDK中提供的这两个类可以更加方便的实现观察者模式. 3. 模板方法模式 3.1. 模板方法模式介绍 模板方法模式(template method pattern)原始定义是在操作中定义算法的框架将一些步骤推迟到子类中。模板方法让子类在不改变算法结构的情况下重新定义算法的某些步骤。 模板方法中的算法可以理解为广义上的业务逻辑,并不是特指某一个实际的算法.定义中所说的算法的框架就是模板, 包含算法框架的方法就是模板方法. 例如: 我们去医院看病一般要经过以下4个流程挂号、取号、排队、医生问诊等其中挂号、 取号 、排队对每个病人是一样的可以在父类中实现但是具体医生如何根据病情开药每个人都是不一样的所以开药这个操作可以延迟到子类中实现。 模板方法模式是一种基于继承的代码复用技术,它是一种类行为模式. 模板方法模式其结构中只存在父类与子类之间的继承关系. 模板方法的作用主要是提高程序的复用性和扩展性: 复用指的是,所有的子类可以复用父类中提供的模板方法代码 扩展指的是,框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能.
3.2. 模板方法模式原理 模板方法模式的定位很清楚就是为了解决算法框架这类特定的问题同时明确表示需要使用继承的结构。 模板方法Template Method模式包含以下主要角色 抽象父类定义一个算法所包含的所有步骤并提供一些通用的方法逻辑。 具体子类继承自抽象父类根据需要重写父类提供的算法步骤中的某些步骤。
抽象类Abstract Class负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。 模板方法定义了算法的骨架按某种顺序调用其包含的基本方法。 基本方法是实现算法各个步骤的方法是模板方法的组成部分。基本方法又可以分为三种 抽象方法(Abstract Method) 一个抽象方法由抽象类声明、由其具体子类实现。 具体方法(Concrete Method) 一个具体方法由一个抽象类或具体类声明并实现其子类可以进行覆盖也可以直接继承。 钩子方法(Hook Method) 在抽象类中已经实现包括用于判断的逻辑方法和需要子类重写的空方法两种。 一般钩子方法是用于判断的逻辑方法这类方法名一般为isXxx返回值类型为boolean类型。
3.3. 模板方法模式实现 UML类图对应的代码实现 /*** 抽象父类* date 2022/10/12/ public abstract class AbstractClassTemplate {void step1(String key){System.out.println(在模板类中 - 执行步骤1);if(step2(key)){step3();}else{step4();}step5();}boolean step2(String key){System.out.println(在模板类中 - 执行步骤2);if(x.equals(key)){return true;}return false;}abstract void step3();abstract void step4();void step5(){System.out.println(在模板类中 - 执行步骤5);}void run(String key){step1(key);}}public class ConcreteClassA extends AbstractClassTemplate{Overridevoid step3() {System.out.println(在子类A中 - 执行步骤 3);}Overridevoid step4() {System.out.println(在子类A中 - 执行步骤 4);} }public class ConcreteClassB extends AbstractClassTemplate {Overridevoid step3() {System.out.println(在子类B中 - 执行步骤 3);}Overridevoid step4() {System.out.println(在子类B中 - 执行步骤 4);} }public class Test01 {public static void main(String[] args) {AbstractClassTemplate concreteClassA new ConcreteClassA();concreteClassA.run();System.out.println();AbstractClassTemplate concreteClassB new ConcreteClassB();concreteClassB.run(x);} }// 输出结果 在模板类中 - 执行步骤1 在模板类中 - 执行步骤2 在子类A中 - 执行步骤 4 在模板类中 - 执行步骤5在模板类中 - 执行步骤1 在模板类中 - 执行步骤2 在子类B中 - 执行步骤 3 在模板类中 - 执行步骤5 3.4. 模板方法模式应用实例 P2P公司的借款系统中有一个利息计算模块,利息的计算流程是这样的: 用户登录系统,登录时需要输入账号密码,如果登录失败(比如用户密码错误),系统需要给出提示 如果用户登录成功,则根据用户的借款的类型不同,使用不同的利息计算方式进行计算 系统需要显示利息.
/* 账户抽象类* date 2022/10/12/ public abstract class Account {//step1 具体方法-验证用户信息是否正确public boolean validate(String account,String password){System.out.println(账号: account ,密码: password);if(account.equalsIgnoreCase(tom) password.equalsIgnoreCase(123456)){return true;}else{return false;}}//step2 抽象方法-计算利息public abstract void calculate();//step3 具体方法-显示利息public void display(){System.out.println(显示利息!);}//模板方法public void handle(String account,String password){if(!validate(account,password)){System.out.println(账户或密码错误!!);return;}calculate();display();} }/* 借款一个月* date 2022/10/12/ public class LoanOneMonth extends Account{Overridepublic void calculate() {System.out.println(借款周期30天,利率为10%!);} }/* 借款七天* date 2022/10/12**/ public class LoanSevenDays extends Account{Overridepublic void calculate() {System.out.println(借款周期7天,无利息!仅收取贷款金额1%的服务费!);}Overridepublic void display() {System.out.println(七日内借款无利息!);}}public class Client {public static void main(String[] args) {Account a1 new LoanSevenDays();a1.handle(tom,12345);System.out.println();Account a2 new LoanOneMonth();a2.handle(tom,123456);} }3.5. 模板方法模式总结 优点 在父类中形式化的定义一个算法,而由它的子类来实现细节处理,在子类实现详细的处理代码时,并不会改变父类算法中步骤的执行顺序. 模板方法可以实现一种反向的控制结构,通过子类覆盖父类的钩子方法,来决定某一个特定步骤是否需要执行 在模板方法模式中可以通过子类来覆盖父类的基本方法,不同的子类可以提供基本方法的不同实现,更换和增加新的子类很方便,符合单一职责原则和开闭原则.
缺点 对每个不同的实现都需要定义一个子类这会导致类的个数增加系统更加庞大设计也更加抽象。 父类中的抽象方法由子类实现子类执行的结果会影响父类的结果这导致一种反向的控制结构它提高了代码阅读的难度。
模板方法模式的使用场景一般有 多个类有相同的方法并且逻辑可以共用时 将通用的算法或固定流程设计为模板在每一个具体的子类中再继续优化算法步骤或流程步骤时 重构超长代码时发现某一个经常使用的公有方法。
- 策略模式
4.1. 策略模式概述
策略模式(strategy pattern)的原始定义是定义一系列算法将每一个算法封装起来并使它们可以相互替换。策略模式让算法可以独立于使用它的客户端而变化。
其实我们在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况例如出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等。 在软件开发中,经常会遇到这种情况,开发一个功能可以通过多个算法去实现,我们可以将所有的算法集中在一个类中,在这个类中提供多个方法,每个方法对应一个算法, 或者我们也可以将这些算法都封装在一个统一的方法中,使用if…else…等条件判断语句进行选择.但是这两种方式都存在硬编码的问题,后期需要增加算法就需要修改源代码,这会导致代码的维护变得困难.
比如网购你可以选择工商银行、农业银行、建设银行等等但是它们提供的算法都是一致的就是帮你付款。 在软件开发中也会遇到相似的情况当实现某一个功能存在多种算法或者策略我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。
4.2. 策略模式原理
在策略模式中可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法,在这里每一个封装算法的类都可以被称为一种策略,为了保证这些策略在使用时具有一致性,一般会提供一个抽象的策略类来做算法的声明.而每种算法对应一个具体的策略类. 策略模式的主要角色如下 抽象策略Strategy类这是一个抽象角色通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。 具体策略Concrete Strategy类实现了抽象策略定义的接口提供具体的算法实现或行为。 环境或上下文Context类是使用算法的角色, 持有一个策略类的引用最终给客户端调用。
4.3. 策略模式实现 策略模式的本质是通过Context类来作为中心控制单元对不同的策略进行调度分配。 /*** 抽象策略类* date 2022/10/13/ public interface Strategy {void algorithm(); }public class ConcreteStrategyA implements Strategy {Overridepublic void algorithm() {System.out.println(执行策略A);} }public class ConcreteStrategyB implements Strategy {Overridepublic void algorithm() {System.out.println(执行策略B);} }/* 环境类* date 2022/10/13/ public class Context {//维持一个对抽象策略类的引用private Strategy strategy;public Context(Strategy strategy) {this.strategy strategy;}//调用策略类中的算法public void algorithm(){strategy.algorithm();} }public class Client {public static void main(String[] args) {Strategy strategyA new ConcreteStrategyA();Context context new Context(strategyA); //可以在运行时指定类型,通过配置文件反射机制实现context.algorithm();} } 4.4. 策略模式应用实例 面试问题: 如何用设计模式消除代码中的if-else 物流行业中通常会涉及到EDI报文(XML格式文件)传输和回执接收每发送一份EDI报文后续都会收到与之关联的回执标识该数据在第三方系统中的流转状态。 这里列举几种回执类型MT1101、MT2101、MT4101、MT8104系统在收到不同的回执报文后会执行对应的业务逻辑处理。我们就业回执处理为演示案例 1 ) 不使用设计模式 回执类
/* 回执信息* date 2022/10/13/ public class Receipt {private String message; //回执信息private String type; //回执类型(MT1101、MT2101、MT4101、MT8104)public Receipt() {}public Receipt(String message, String type) {this.message message;this.type type;}public String getMessage() {return message;}public void setMessage(String message) {this.message message;}public String getType() {return type;}public void setType(String type) {this.type type;} } 回执生成器
public class ReceiptBuilder {public static ListReceipt genReceiptList(){//模拟回执信息ListReceipt receiptList new ArrayList();receiptList.add(new Receipt(MT1101回执,MT1011));receiptList.add(new Receipt(MT2101回执,MT2101));receiptList.add(new Receipt(MT4101回执,MT4101));receiptList.add(new Receipt(MT8104回执,MT8104));//……return receiptList;}} 客户端
public class Client { public static void main(String[] args) { ListReceipt receiptList ReceiptBuilder.genReceiptList(); //循环判断for (Receipt receipt : receiptList) {if(MT1011.equals(receipt.getType())){System.out.println(接收到MT1011回执!);System.out.println(解析回执内容);System.out.println(执行业务逻辑A\n);}else if(MT2101.equals(receipt.getType())){System.out.println(接收到MT2101回执!);System.out.println(解析回执内容);System.out.println(执行业务逻辑B\n);}else if(MT4101.equals(receipt.getType())) {System.out.println(接收到MT4101回执!);System.out.println(解析回执内容);System.out.println(执行业务逻辑C\n);}else if(MT8104.equals(receipt.getType())) {System.out.println(接收到MT8104回执!);System.out.println(解析回执内容);System.out.println(执行业务逻辑D);} //……}} } 2 ) 使用策略模式进行优化 通过策略模式, 将所有的if-else分支的业务逻辑抽取为各种策略类,让客户端去依赖策略接口,保证具体策略类的改变不影响客户端. 策略接口
/* 回执处理策略接口* date 2022/10/13/ public interface ReceiptHandleStrategy { void handleReceipt(Receipt receipt); } 具体策略类
public class Mt1011ReceiptHandleStrategy implements ReceiptHandleStrategy {Overridepublic void handleReceipt(Receipt receipt) {System.out.println(解析报文MT1011: receipt.getMessage());} }public class Mt2101ReceiptHandleStrategy implements ReceiptHandleStrategy {Overridepublic void handleReceipt(Receipt receipt) {System.out.println(解析报文MT2101: receipt.getMessage());} }…… 策略上下文类(策略接口的持有者)
/* 上下文类,持有策略接口* date 2022/10/13**/ public class ReceiptStrategyContext {private ReceiptHandleStrategy receiptHandleStrategy;public void setReceiptHandleStrategy(ReceiptHandleStrategy receiptHandleStrategy) {this.receiptHandleStrategy receiptHandleStrategy;}//调用策略类中的方法public void handleReceipt(Receipt receipt){if(receipt ! null){receiptHandleStrategy.handleReceipt(receipt);}} } 策略工厂
public class ReceiptHandleStrategyFactory { public ReceiptHandleStrategyFactory() {} //使用Map集合存储策略信息,彻底消除if…elseprivate static MapString,ReceiptHandleStrategy strategyMap; //初始化具体策略,保存到map集合public static void init(){strategyMap new HashMap();strategyMap.put(MT1011,new Mt1011ReceiptHandleStrategy());strategyMap.put(MT2101,new Mt2101ReceiptHandleStrategy());} //根据回执类型获取对应策略类对象public static ReceiptHandleStrategy getReceiptHandleStrategy(String receiptType){return strategyMap.get(receiptType);} } 客户端
public class Client { public static void main(String[] args) { //模拟回执ListReceipt receiptList ReceiptBuilder.genReceiptList(); //策略上下文ReceiptStrategyContext context new ReceiptStrategyContext(); //策略模式将策略的 定义、创建、使用这三部分进行了解耦for (Receipt receipt : receiptList) {//获取置策略ReceiptHandleStrategyFactory.init();ReceiptHandleStrategy strategy ReceiptHandleStrategyFactory.getReceiptHandleStrategy(receipt.getType());//设置策略context.setReceiptHandleStrategy(strategy);//执行策略context.handleReceipt(receipt);}} } 经过上面的改造我们已经消除了if-else的结构每当新来了一种回执只需要添加新的回执处理策略并修改ReceiptHandleStrategyFactory中的Map集合。如果要使得程序符合开闭原则则需要调整ReceiptHandleStrategyFactory中处理策略的获取方式通过反射的方式获取指定包下的所有IReceiptHandleStrategy实现类然后放到字典Map中去. 4.5. 策略模式总结 - 策略模式优点 策略类之间可以自由切换 由于策略类都实现同一个接口所以使它们之间可以自由切换。 易于扩展 增加一个新的策略只需要添加一个具体的策略类即可基本不需要改变原有的代码符合“开闭原则“ 避免使用多重条件选择语句if else充分体现面向对象设计思想。
- 策略模式缺点 客户端必须知道所有的策略类并自行决定使用哪一个策略类。 策略模式将造成产生很多策略类可以通过使用享元模式在一定程度上减少对象的数量。
- 策略模式使用场景 一个系统需要动态地在几种算法中选择一种时可将每个算法封装到策略类中。 策略模式最大的作用在于分离使用算法的逻辑和算法自身实现的逻辑这样就意味着当我们想要优化算法自身的实现逻辑时就变得非常便捷一方面可以采用最新的算法实现逻辑另一方面可以直接弃用旧算法而采用新算法。使用策略模式能够很方便地进行替换。 一个类定义了多种行为并且这些行为在这个类的操作中以多个条件语句的形式出现可将每个条件分支移入它们各自的策略类中以代替这些条件语句。 在实际开发中有许多算法可以实现某一功能如查找、排序等通过 if-else 等条件判断语句来进行选择非常方便。但是这就会带来一个问题当在这个算法类中封装了大量查找算法时该类的代码就会变得非常复杂维护也会突然就变得非常困难。虽然策略模式看上去比较笨重但实际上在每一次新增策略时都通过新增类来进行隔离短期虽然不如直接写 if-else 来得效率高但长期来看维护单一的简单类耗费的时间其实远远低于维护一个超大的复杂类。 系统要求使用算法的客户不应该知道其操作的数据时可使用策略模式来隐藏与算法相关的数据结构。 如果我们不希望客户知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关数据结构,可以提高算法的保密性与安全性. 设计原则和思想其实比设计模式更加的普适和重要,掌握了代码的设计原则和思想,我们自然而然的就可以使用到设计模式,还有可能自己创建出一种新的设计模式.
- 责任链模式
5.1. 职责链模式介绍
职责链模式(chain of responsibility pattern) 定义: 避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求.将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止.
在职责链模式中多个处理器也就是刚刚定义中说的“接收对象”依次处理同一个请 求。一个请求先经过 A 处理器处理然后再把请求传递给 B 处理器B 处理器处理完后再 传递给 C 处理器以此类推形成一个链条。链条上的每个处理器各自承担各自的处理职 责所以叫作职责链模式。 5.2. 职责链模式原理
职责链模式结构 职责链模式主要包含以下角色: 抽象处理者Handler角色定义一个处理请求的接口包含抽象处理方法和一个后继连接(链上的每个处理者都有一个成员变量来保存对于下一处理者的引用,比如上图中的successor) 。 具体处理者Concrete Handler角色实现抽象处理者的处理方法判断能否处理本次请求如果可以处理请求则处理否则将该请求转给它的后继者。 客户类Client角色创建处理链并向链头的具体处理者对象提交请求它不关心处理细节和请求的传递过程。
5.3. 职责链模式实现 责任链模式的实现非常简单每一个具体的处理类都会保存在它之后的下一个处理类。当处理完成后就会调用设置好的下一个处理类直到最后一个处理类不再设置下一个处理类这时处理链条全部完成。 public class RequestData {private String data;public RequestData(String data) {this.data data;}public String getData() {return data;}public void setData(String data) {this.data data;} }/*** 抽象处理者类* date 2022/10/14/ public abstract class Handler {protected Handler successor null;public void setSuccessor(Handler successor){this.successor successor;}public abstract void handle(RequestData requestData); }public class HandlerA extends Handler {Overridepublic void handle(RequestData requestData) {System.out.println(HandlerA 执行代码逻辑! 处理: requestData.getData());requestData.setData(requestData.getData().replace(A,));if(successor ! null){successor.handle(requestData);}else{System.out.println(执行中止!);}} }public class HandlerB extends Handler {Overridepublic void handle(RequestData requestData) {System.out.println(HandlerB 执行代码逻辑! 处理: requestData.getData());requestData.setData(requestData.getData().replace(B,));if(successor ! null){successor.handle(requestData);}else{System.out.println(执行中止!);}} }public class HandlerC extends Handler {Overridepublic void handle(RequestData requestData) {System.out.println(HandlerC 执行代码逻辑! 处理: requestData.getData());requestData.setData(requestData.getData());if(successor ! null){successor.handle(requestData);}else{System.out.println(执行中止!);}} }public class Client {public static void main(String[] args) {Handler h1 new HandlerA();Handler h2 new HandlerB();Handler h3 new HandlerC();h1.setSuccessor(h2);h2.setSuccessor(h3);RequestData requestData new RequestData(请求数据ABCDE);h1.handle(requestData);}} 5.4. 职责链模式应用实例 接下来我们模拟有一个双11期间,业务系统审批的流程,临近双十一公司会有陆续有一些新的需求上线,为了保证线上系统的稳定,我们对上线的审批流畅做了严格的控制.审批的过程会有不同级别的负责人加入进行审批(平常系统上线只需三级负责人审批即可,双十一前后需要二级或一级审核人参与审批),接下来我们就使用职责链模式来设计一下此功能. 1) 不使用设计模式 /* 审核信息* date 2022/10/14/ public class AuthInfo {private String code;private String info ;public AuthInfo(String code, String… infos) {this.code code;for (String str : infos) {info this.info.concat(str );}}public String getCode() {return code;}public void setCode(String code) {this.code code;}public String getInfo() {return info;}public void setInfo(String info) {this.info info;}Overridepublic String toString() {return AuthInfo{ code code \ , info info \ };} }/* 模拟审核服务* date 2022/10/14/ public class AuthService {//审批信息 审批人Id申请单Idprivate static MapString,Date authMap new HashMapString, Date();/* 审核流程* param uId 审核人id* param orderId 审核单id/public static void auth(String uId, String orderId){System.out.println(进入审批流程,审批人ID: uId);authMap.put(uId.concat(orderId),new Date());}//查询审核结果public static Date queryAuthInfo(String uId, String orderId){return authMap.get(uId.concat(orderId)); //key审核人id审核单子id} }public class AuthController {//审核接口public AuthInfo doAuth(String name, String orderId, Date authDate) throws ParseException {//三级审批Date date null;//查询是否存在审核信息,查询条件: 审核人ID订单ID,返回Map集合中的Datedate AuthService.queryAuthInfo(1000013, orderId);//如果为空,封装AuthInfo信息(待审核)返回if(date null){return new AuthInfo(0001,单号: orderId,状态: 等待三级审批负责人进行审批);}//二级审批SimpleDateFormat f new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);// 时间格式化//二级审核人主要审核双十一之前, 11-01 ~ 11-10号的请求,所以要对传入的审核时间进行判断//审核时间 大于 2022-11-01 并且 小于 2022-11-10,Date1.after(Date2),当Date1大于Date2时返回TRUE,Date1.before(Date2)当Date1小于Date2时返回TRUEif(authDate.after(f.parse(2022-11-01 00:00:00)) authDate.before(f.parse(2022-11-10 00:00:00))){//条件成立,查询二级审核的审核信息date AuthService.queryAuthInfo(1000012,orderId);//如果为空,还是待二级审核人审核状态if(date null){return new AuthInfo(0001,单号: orderId,状态: 等待二级审批负责人进行审批);}}//一级审批//审核范围是在11-11日 ~ 11-31日if(authDate.after(f.parse(2022-11-11 00:00:00)) authDate.before(f.parse(2022-11-31 00:00:00))){date AuthService.queryAuthInfo(1000011,orderId);if(date null){return new AuthInfo(0001,单号: orderId,状态: 等待一级审批负责人进行审批);}}return new AuthInfo(0001,单号: orderId,申请人: name , 状态: 审批完成!);} }public class Client {public static void main(String[] args) throws ParseException {AuthController controller new AuthController();SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);Date date sdf.parse(2022-11-12 00:00:00);//设置申请流程//三级审核//1.调用doAuth方法,模拟发送申请人相关信息AuthInfo info1 controller.doAuth(研发小周, 100001000010000, date);System.out.println(当前审核状态: info1.getInfo());/** 2.模拟进行审核操作, 虚拟审核人ID: 1000013* 调用auth() 方法进行审核操作, 就是向Map中添加一个 审核人ID和申请单ID/AuthService.auth(1000013, 100001000010000);System.out.println(三级负责人审批完成,审批人: 王工);System.out.println();//二级审核//1.调用doAuth方法,模拟发送申请人相关信息AuthInfo info2 controller.doAuth(研发小周, 100001000010000, date);System.out.println(当前审核状态: info2.getInfo());/** 2.模拟进行审核操作, 虚拟审核人ID: 1000012* 调用auth() 方法进行审核操作, 就是向Map中添加一个 审核人ID和申请单ID/AuthService.auth(1000012, 100001000010000);System.out.println(二级负责人审批完成,审批人: 张经理);System.out.println();//一级审核//1.调用doAuth方法,模拟发送申请人相关信息AuthInfo info3 controller.doAuth(研发小周, 100001000010000, date);System.out.println(当前审核状态: info3.getInfo());/** 2.模拟进行审核操作, 虚拟审核人ID: 1000012* 调用auth() 方法进行审核操作, 就是向Map中添加一个 审核人ID和申请单ID/AuthService.auth(1000011, 100001000010000);System.out.println(一级负责人审批完成,审批人: 罗总);} 2 ) 职责链模式重构代码 下图是为当前业务设计的责任链结构,统一抽象类AuthLink 下 有三个子类,将三个子类的执行通过编排,模拟出一条链路,这个链路就是业务中的责任链. /** 抽象审核链类/ public abstract class AuthLink { protected Logger logger LoggerFactory.getLogger(AuthLink.class); protected SimpleDateFormat f new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);protected String levelUserId; //审核人IDprotected String levelUserName; //审核人姓名protected AuthLink next; //持有下一个处理类的引用 public AuthLink(String levelUserId, String levelUserName) {this.levelUserId levelUserId;this.levelUserName levelUserName;} //获取下一个处理类public AuthLink getNext() {return next;} //责任链中添加处理类public AuthLink appendNext(AuthLink next) {this.next next;return this;} //抽象审核方法public abstract AuthInfo doAuth(String uId, String orderId, Date authDate); } /** 一级负责人/ public class Level1AuthLink extends AuthLink { private Date beginDate f.parse(2020-11-11 00:00:00);private Date endDate f.parse(2020-11-31 23:59:59); public Level1AuthLink(String levelUserId, String levelUserName) throws ParseException {super(levelUserId, levelUserName);} Overridepublic AuthInfo doAuth(String uId, String orderId, Date authDate) {Date date AuthService.queryAuthInfo(levelUserId, orderId);if (null date) {return new AuthInfo(0001, 单号, orderId, 状态待一级审批负责人 , levelUserName);}AuthLink next super.getNext();if (null next) {return new AuthInfo(0000, 单号, orderId, 状态一级审批完成, 时间, f.format(date), 审批人, levelUserName);}if (authDate.before(beginDate) || authDate.after(endDate)) {return new AuthInfo(0000, 单号, orderId, 状态一级审批完成, 时间, f.format(date), 审批人, levelUserName);}return next.doAuth(uId, orderId, authDate);} } /*** 二级负责人/ public class Level2AuthLink extends AuthLink { private Date beginDate f.parse(2020-11-11 00:00:00);private Date endDate f.parse(2020-11-31 23:59:59); public Level2AuthLink(String levelUserId, String levelUserName) throws ParseException {super(levelUserId, levelUserName);} public AuthInfo doAuth(String uId, String orderId, Date authDate) {Date date AuthService.queryAuthInfo(levelUserId, orderId);if (null date) {return new AuthInfo(0001, 单号, orderId, 状态待二级审批负责人 , levelUserName);}AuthLink next super.getNext();if (null next) {return new AuthInfo(0000, 单号, orderId, 状态二级审批完成, 时间, f.format(date), 审批人, levelUserName);} if (authDate.before(beginDate) || authDate.after(endDate) ) {return new AuthInfo(0000, 单号, orderId, 状态二级审批完成, 时间, f.format(date), 审批人, levelUserName);} return next.doAuth(uId, orderId, authDate);} } /** 三级负责人*/ public class Level3AuthLink extends AuthLink { public Level3AuthLink(String levelUserId, String levelUserName) {super(levelUserId, levelUserName);} public AuthInfo doAuth(String uId, String orderId, Date authDate) {Date date AuthService.queryAuthInfo(levelUserId, orderId);if (null date) {return new AuthInfo(0001, 单号, orderId, 状态待三级审批负责人 , levelUserName);}AuthLink next super.getNext();if (null next) {return new AuthInfo(0000, 单号, orderId, 状态三级审批完成, 时间, f.format(date), 审批人, levelUserName);} return next.doAuth(uId, orderId, authDate);} } 测试public class Client { private Logger logger LoggerFactory.getLogger(ApiTest.class); Testpublic void test_AuthLink() throws ParseException { AuthLink authLink new Level3AuthLink(1000013, 王工).appendNext(new Level2AuthLink(1000012, 张经理).appendNext(new Level1AuthLink(1000011, 段总))); SimpleDateFormat f new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);Date currentDate f.parse(2020-11-18 23:49:46); logger.info(测试结果{}, JSON.toJSONString(authLink.doAuth(研发牛马, 1000998004813441, currentDate))); // 模拟三级负责人审批AuthService.auth(1000013, 1000998004813441);logger.info(测试结果{}, 模拟三级负责人审批王工);logger.info(测试结果{}, JSON.toJSONString(authLink.doAuth(研发牛马, 1000998004813441, currentDate))); // 模拟二级负责人审批AuthService.auth(1000012, 1000998004813441);logger.info(测试结果{}, 模拟二级负责人审批张经理);logger.info(测试结果{}, JSON.toJSONString(authLink.doAuth(研发牛马, 1000998004813441, currentDate))); // 模拟一级负责人审批AuthService.auth(1000011, 1000998004813441);logger.info(测试结果{}, 模拟一级负责人审批段总);logger.info(测试结果{}, JSON.toJSONString(authLink.doAuth(研发牛马, 1000998004813441, currentDate))); } } 从上面的代码结果看,我们的责任链已经生效,按照责任链的结构一层一层审批.当工作流程发生变化可以动态地改变链内的成员或者修改它们的次序也可动态地新增或者删除责任。并且每个类只需要处理自己该处理的工作不能处理的传递给下一个对象完成明确各类的责任范围符合类的单一职责原则。 5.5. 职责链模式总结 - 职责链模式的优点 降低了对象之间的耦合度 该模式降低了请求发送者和接收者的耦合度。 增强了系统的可扩展性 可以根据需要增加新的请求处理类满足开闭原则。 增强了给对象指派职责的灵活性 当工作流程发生变化可以动态地改变链内的成员或者修改它们的次序也可动态地新增或者删除责任。 责任链简化了对象之间的连接 一个对象只需保持一个指向其后继者的引用不需保持其他所有处理者的引用这避免了使用众多的 if 或者 if···else 语句。 责任分担 每个类只需要处理自己该处理的工作不能处理的传递给下一个对象完成明确各类的责任范围符合类的单一职责原则。
- 职责链模式的缺点: 不能保证每个请求一定被处理。由于一个请求没有明确的接收者所以不能保证它一定会被处理该请求可能一直传到链的末端都得不到处理。 对比较长的职责链请求的处理可能涉及多个处理对象系统性能将受到一定影响。 职责链建立的合理性要靠客户端来保证增加了客户端的复杂性可能会由于职责链的错误设置而导致系统出错如可能会造成循环调用。
- 使用场景分析
责任链模式常见的使用场景有以下几种情况。 在运行时需要动态使用多个关联对象来处理同一次请求时。比如请假流程、员工入职流程、编译打包发布上线流程等。 不想让使用者知道具体的处理逻辑时。比如做权限校验的登录拦截器。 需要动态更换处理对象时。比如工单处理系统、网关 API 过滤规则系统等。 职责链模式常被用在框架开发中,用来实现框架的过滤器、拦截器功能,让框架的使用者在不修改源码的情况下,添加新的过滤拦截功能.
- 命令模式
6.1. 命令模式介绍
命令模式(command pattern)的定义: 命令模式将请求命令封装为一个对象这样可以使用不同的请求参数化其他对象将不 同请求依赖注入到其他对象并且能够支持请求命令的排队执行、记录日志、撤销等 附加控制功能。
命令模式的核心是将指令信息封装成一个对象,并将此对象作为参数发送给接收方去执行,达到使命令的请求与执行方解耦,双方只通过传递各种命令对象来完成任务.
在实际的开发中如果你用到的编程语言并不支持用函数作为参数来传递那么就可以借助命令模式将函数封装为对象来使用。 我们知道C 语 言支持函数指针我们可以把函数当作变量传递来传递去。但是在大部分编程语言中函 数没法儿作为参数传递给其他函数也没法儿赋值给变量。借助命令模式我们可以将函数 封装成对象。具体来说就是设计一个包含这个函数的类实例化一个对象传来传去这样 就可以实现把函数像对象一样使用。 6.2. 命令模式原理 命令模式包含以下主要角色 抽象命令类Command角色 定义命令的接口声明执行的方法。 具体命令Concrete Command角色具体的命令实现命令接口通常会持有接收者并调用接收者的功能来完成命令要执行的操作。 实现者/接收者Receiver角色 接收者真正执行命令的对象。任何类都可能成为一个接收者只要它能够实现命令要求实现的相应功能。 调用者/请求者Invoker角色 要求命令对象执行请求通常会持有命令对象可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方也就是说相当于使用命令对象的入口。
6.3. 命令模式实现 模拟酒店后厨的出餐流程,来对命令模式进行一个演示,命令模式角色的角色与案例中角色的对应关系如下: 服务员: 即调用者角色,由她来发起命令. 厨师: 接收者,真正执行命令的对象. 订单: 命令中包含订单
/*** 订单类* date 2022/10/19/ public class Order {private int diningTable; //餐桌号码//存储菜名与份数private MapString,Integer foodMenu new HashMap();public int getDiningTable() {return diningTable;}public void setDiningTable(int diningTable) {this.diningTable diningTable;}public MapString, Integer getFoodMenu() {return foodMenu;}public void setFoodDic(MapString, Integer foodMenu) {this.foodMenu foodMenu;} }/* 厨师类 - Receiver角色* date 2022/10/19/ public class Chef {public void makeFood(int num,String foodName){System.out.println(num 份, foodName);} }/* 抽象命令接口* date 2022/10/19/ public interface Command {void execute(); //只需要定义一个统一的执行方法 }/* 具体命令* date 2022/10/19/ public class OrderCommand implements Command {//持有接收者对象private Chef receiver;private Order order;public OrderCommand(Chef receiver, Order order) {this.receiver receiver;this.order order;}Overridepublic void execute() {System.out.println(order.getDiningTable() 桌的订单: );SetString keys order.getFoodMenu().keySet();for (String key : keys) {receiver.makeFood(order.getFoodMenu().get(key),key);}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(order.getDiningTable() 桌的菜已上齐.);} }/* 服务员- Invoker调用者* date 2022/10/19**/ public class Waiter {//可以持有很多的命令对象private ArrayListCommand commands;public Waiter() {commands new ArrayList();}public Waiter(ArrayListCommand commands) {this.commands commands;}public void setCommands(Command command) {commands.add(command);}//发出命令 ,指挥厨师工作public void orderUp(){System.out.println(服务员: 叮咚,有新的订单,请厨师开始制作……);for (Command cmd : commands) {if(cmd ! null){cmd.execute();}}} }public class Client {public static void main(String[] args) {Order order1 new Order();order1.setDiningTable(1);order1.getFoodMenu().put(鲍鱼炒饭,1);order1.getFoodMenu().put(茅台迎宾,1);Order order2 new Order();order2.setDiningTable(3);order2.getFoodMenu().put(海参炒面,1);order2.getFoodMenu().put(五粮液,1);//创建接收者Chef receiver new Chef();//将订单和接收者封装成命令对象OrderCommand cmd1 new OrderCommand(receiver,order1);OrderCommand cmd2 new OrderCommand(receiver,order2);//创建调用者Waiter invoke new Waiter();invoke.setCommands(cmd1);invoke.setCommands(cmd2);//将订单发送到后厨invoke.orderUp();} } 6.4. 命令模式总结 - 命令模式优点 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类它满足“开闭原则”对扩展比较灵活。 可以实现宏命令。命令模式可以与组合模式结合将多个命令装配成一个组合命令即宏命令。
- 命令模式缺点 使用命令模式可能会导致某些系统有过多的具体命令类。 系统结构更加复杂。
- 使用场景 系统需要将请求调用者和请求接收者解耦使得调用者和接收者不直接交互。 系统需要在不同的时间指定请求、将请求排队和执行请求。 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
- 结语 在本文中我们探讨了几种基础的行为型设计模式包括观察者模式、模板方法模式、策略模式、职责链模式和命令模式。这些模式为实现对象间的灵活协作和复杂流程的控制提供了有效的解决方案是理解行为型设计模式的基础。 在下一章中我们将深入探讨更为高级和复杂的行为模式如状态模式、中介者模式和访问者模式等。通过这些模式的学习您将进一步提升对行为型模式的掌握为构建更加复杂和高效的系统奠定基础。
- 上一篇: 做网站要学什么wordpress外链缩略图
- 下一篇: 做网站要用到数据库吗广州建设银行官方网站
相关文章
-
做网站要学什么wordpress外链缩略图
做网站要学什么wordpress外链缩略图
- 技术栈
- 2026年04月18日
-
做网站要素搞笑图片网站源码
做网站要素搞笑图片网站源码
- 技术栈
- 2026年04月18日
-
做网站要什么专业音乐网站排名
做网站要什么专业音乐网站排名
- 技术栈
- 2026年04月18日
-
做网站要用到数据库吗广州建设银行官方网站
做网站要用到数据库吗广州建设银行官方网站
- 技术栈
- 2026年04月18日
-
做网站要用什么编程语言做徽章标牌的企业网站
做网站要用什么编程语言做徽章标牌的企业网站
- 技术栈
- 2026年04月18日
-
做网站要找什么人高校网站建设说明书
做网站要找什么人高校网站建设说明书
- 技术栈
- 2026年04月18日

