Java23种设计模式
本文主要是对Java中一些常用的设计模式进行讲解 后期会进行不断的更新,欢迎浏览
23种设计模式
- 创建型模式,共五种:工厂方法模式、抽象工厂模式、建造者模式、原型模式、单例模式。
- 结构型模式,共七种:适配器模式、桥接模式、组合模式、装饰器模式、外观模式、享元模式、代理模式。
- 行为型模式,共十一种:责任链模式、命令模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式、访问者模式、解释器模式。
六大设计原则
单一职责原则
- 强调一个类或模块应该只负责一项功能或职责(高内聚 低耦合)
开闭原则
- 在设计和构建软件实体时,应该尽量通过扩展(继承)现有的代码来实现新的功能,而不是修改已有的代码
里氏替原则
- 里氏替换原则要求子类能够替换父类并在不影响程序正确性的前提下扩展父类的功能。这意味着子类应该保持父类的行为和约束,并且可以在不违反父类定义的前提下进行功能扩展。
- 子类必须实现父类的所有抽象方法。
- 子类可以有自己的个性化方法,但不能修改父类已有的方法。
- 子类的前置条件(输入参数)要比父类的更宽松。
- 子类的后置条件(输出结果)要比父类的更严格。
- 子类不能抛出比父类更多或更宽泛的异常。
迪米特法则
- 一个对象应该对其他对象有尽可能少的了解。具体而言,一个对象应该尽量减少对其他对象的依赖,只与直接的朋友通信,不与陌生的对象通信。
接口隔离原则
- 通过合理设计接口,使接口的使用者不依赖于它们不需要的方法(允许实现多个接口)
依赖倒置原则
- 强调高层模块不应该依赖于低层模块的具体实现,而是应该依赖于抽象接口。同时,抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
创建型模式
工厂方法模式(重点)
- 在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型
- 1.新建一个
ICommodity
接口,他拥有三个实现类CardCommodityService、CouponCommodityService、GoodsCommodityService
- 2.创建一个统一的工厂类
StoreFactory
,用工厂类去创建上面的三个业务对象
public class StoreFactory {private Map<Integer, ICommodity> factoryMap = new HashMap<>();/*** 工厂初始化*/{factoryMap.put(1, new CouponCommodityService());factoryMap.put(2, new GoodsCommodityService());factoryMap.put(3, new CardCommodityService());}/*** 奖品类型方式实例化* @param commodityType 奖品类型* @return 实例化对象*/public ICommodity getCommodityService(Integer commodityType) {if (null == commodityType) return null;ICommodity iCommodity = factoryMap.get(commodityType);if(iCommodity != null){return iCommodity;}throw new RuntimeException("不存在的奖品服务类型");}/*** 奖品类信息方式实例化* @param clazz 奖品类* @return 实例化对象*/public ICommodity getCommodityService(Class<? extends ICommodity> clazz) throws IllegalAccessException, InstantiationException {if (null == clazz) return null;return clazz.newInstance();}
}
- 3.使用时通过工厂类来创建对象
@Testpublic void test_awardToUser() throws Exception {// 1. 优惠券ICommodity commodityService_1 = storeFactory.getCommodityService(1);commodityService_1.sendCommodity("10001", "EGM1023938910232121323432", "791098764902132", null);// 2. 实物商品ICommodity commodityService_2 = storeFactory.getCommodityService(GoodsCommodityService.class);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);}
抽象工厂模式
- 抽象工厂模式与工厂方法模式虽然主要意图都是为了解决,接口选择问题。但在实现上,抽象工厂是一个中心工厂,创建其他工厂的模式。
- 1.
ICacheAdapter
接口下有EGMCacheAdapter、IIRCacheAdapter
两个服务实现类 - 2.
InvocationHandler
是针对ICacheAdapter
接口的动态代理类
public class JDKInvocationHandler implements InvocationHandler {private ICacheAdapter cacheAdapter;public JDKInvocationHandler(ICacheAdapter cacheAdapter) {this.cacheAdapter = cacheAdapter;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 获取处理的指定方法return ICacheAdapter.class.getMethod(method.getName(), ClassLoaderUtils.getClazzByArgs(args)).invoke(cacheAdapter, args);}
}
- 3.抽象工厂创建代理类
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();// 动态代理方法:Proxy.newProxyInstance(类加载器,需要代理的接口,执行handler.invoke方法)return (T) Proxy.newProxyInstance(classLoader, new Class[]{cacheClazz}, handler);}
}
- 4.测试使用
@Testpublic void test_cacheService() throws Exception {CacheService proxy_Gm = JDKProxyFactory.getProxy(CacheService.class, EGMCacheAdapter.class);proxy_Gm.set("user_name_01", "imwj");String val01 = proxy_Gm.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", "imwj");String val02 = proxy_IIR.get("user_name_01");logger.info("缓存服务 IIR 测试,proxy_IIR.get 测试结果:{}", val02);}
建造者模式
- 通过将多个简单对象通过一步步的组装构建出一个复杂对象的过程。
- 1.建造模式接口
public interface IMenu {/*** 吊顶*/IMenu appendCeiling(Matter matter);/*** 涂料*/IMenu appendCoat(Matter matter);/*** 地板*/IMenu appendFloor(Matter matter);/*** 地砖*/IMenu appendTile(Matter matter);/*** 明细*/String getDetail();
}
- 2.接口实现类
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;}@Overridepublic IMenu appendCeiling(Matter matter) {list.add(matter);price = price.add(area.multiply(new BigDecimal("0.2")).multiply(matter.price()));return this;}@Overridepublic 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();}
}
- 3.建造模式构造器
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()); // 地砖,东鹏}}
- 4.测试使用
@Testpublic void test_Builder(){IMenu one = builder.levelOne(100D);System.out.println(one.getDetail());IMenu two = builder.levelTwo(100D);System.out.println(two.getDetail());IMenu three = builder.levelThree(100D);System.out.println(three.getDetail());}
原型模式
- 主要解决的问题就是创建重复对象,而这部分对象内容本身比较复杂,生成过程可能从库或者RPC接口中获取数据的耗时较长,因此采用克隆的方式节省时间
- 使用频率不高 可以参考
step04-00
相关代码
单例模式
- 单例对象的类只能允许一个实例存在
- 0.静态类
public class Singleton_00 {private static Map<String, String> cache = new ConcurrentHashMap<String, String>();}
- 1.懒汉模式(线程不安全)
public class Singleton_01 {private static Singleton_01 singleton_01;private Singleton_01() {}public static Singleton_01 getSingleton_01() {if (singleton_01 == null) {singleton_01 = new Singleton_01();}return singleton_01;}
}
- 2.懒汉模式(线程安全)
public class Singleton_02 {private static Singleton_02 instance;private Singleton_02() {}public static synchronized Singleton_02 getInstance(){if (null != instance) return instance;instance = new Singleton_02();return instance;}
}
- 3.饿汉模式(线程安全)
public class Singleton_03 {private static Singleton_03 instance = new Singleton_03();private Singleton_03() {}public static Singleton_03 getInstance() {return instance;}
}
- 4.使用类的内部类(线程安全)
public class Singleton_04 {private static class SingletonHolder {private static Singleton_04 instance = new Singleton_04();}private Singleton_04() {}public static Singleton_04 getInstance() {return SingletonHolder.instance;}}
- 5.双重锁校验(线程安全)
public class Singleton_05 {private static volatile Singleton_05 instance;private Singleton_05() {}public static Singleton_05 getInstance(){if(null != instance) return instance;synchronized (Singleton_05.class){if (null == instance){instance = new Singleton_05();}}return instance;}
}
- 6.CAS「AtomicReference」(线程安全)
public class Singleton_06 {private static final AtomicReference<Singleton_06> INSTANCE = new AtomicReference<Singleton_06>();private Singleton_06() {}public static final Singleton_06 getInstance() {for (; ; ) {Singleton_06 instance = INSTANCE.get();if (null != instance) return instance;INSTANCE.compareAndSet(null, new Singleton_06());return INSTANCE.get();}}
- 7.Effective Java作者推荐的枚举单例(线程安全)
public enum Singleton_07 {INSTANCE;public void test(){System.out.println("hi~");}
}
结构型模式
适配器模式
- 适配器模式的主要作用就是把原本不兼容的接口,通过适配修改做到统一
- 1.实体对象适配,service服务适配(实现同一个接口即可)
- 2.现有一个
create_account
对象需要转换为RebateInfo
对象 - 3.字段转换适配器
public class MQAdapter {public static RebateInfo filter(String strJson, Map<String, String> link) throws Exception {return filter(JSON.parseObject(strJson, Map.class), link);}/*** 消息过滤转换** @param obj 数据实体* @param link key对应关系(key:rebateInfo字段名 value:原先字段名)* @return*/public static RebateInfo filter(Map obj, Map<String, String> link) throws Exception {RebateInfo rebateInfo = new RebateInfo();for (String key : link.keySet()) {Object val = obj.get(link.get(key));RebateInfo.class.getMethod("set" + key.substring(0, 1).toUpperCase() + key.substring(1),String.class).invoke(rebateInfo, val);}return rebateInfo;}
}
- 4.测试使用
@Testpublic void test() throws Exception {create_account create_account = new create_account();create_account.setNumber("100001");create_account.setAddress("河北省.廊坊市.广阳区.大学里职业技术学院");create_account.setAccountDate(new Date());create_account.setDesc("在校开户");HashMap<String, String> link01 = new HashMap<String, String>();link01.put("userId", "number");link01.put("bizId", "number");link01.put("desc", "desc");RebateInfo rebateInfo01 = MQAdapter.filter(JSON.toJSONString(create_account), link01);System.out.println("mq.create_account(适配前)" + create_account.toString());System.out.println("mq.create_account(适配后)" + JSON.toJSONString(rebateInfo01));System.out.println("======service适配(实现同一个接口即可)===========");OrderAdapterService popOrderAdapterService = new POPOrderAdapterServiceImpl();System.out.println("判断首单,接口适配(POP):" + popOrderAdapterService.isFirst("100001"));OrderAdapterService insideOrderService = new InsideOrderServiceImpl();System.out.println("判断首单,接口适配(自营):" + insideOrderService.isFirst("100001"));}
桥接模式
- 桥接模式的主要作用就是通过将抽象部分与实现部分分离,把多种可匹配的使用进行组合。说白了核心实现也就是在A类中含有B类接口,通过构造函数传递B类的实现,这个B类就是设计的桥
- 1.定义一个支付方式接口
IPayMode
,下面有n个实现类PayCypher
、PayFaceMode
、PayFingerprintMode
public interface IPayMode {/*** 支付是否安全* @param uId* @return*/boolean security(String uId);}
public class PayFaceMode implements IPayMode{protected Logger logger = LoggerFactory.getLogger(IPayMode.class);@Overridepublic boolean security(String uId) {logger.info("人脸支付,风控校验脸部识别");return true;}
}
- 2.抽象一个支付类
Pay
,下面有两个支付实体WxPay
、ZfbPay
,抽象类的构造方法会将支付方式作为参数传递进来,ZfbPay
实体就能根据传递进来的支付方式进行支付了
public abstract class Pay {protected Logger logger = LoggerFactory.getLogger(Pay.class);protected IPayMode payMode;Pay(IPayMode payMode){this.payMode = payMode;}public abstract String transfer(String uId, String tradeId, BigDecimal amount);}
public class ZfbPay extends Pay{public ZfbPay(IPayMode payMode) {super(payMode);}@Overridepublic String transfer(String uId, String tradeId, BigDecimal amount) {logger.info("模拟支付宝渠道支付划账开始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);boolean security = payMode.security(uId);logger.info("模拟支付宝渠道支付风控校验。uId:{} tradeId:{} security:{}", uId, tradeId, security);if (!security) {logger.info("模拟支付宝渠道支付划账拦截。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);return "0001";}logger.info("模拟支付宝渠道支付划账成功。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);return "0000";}
}
- 3.测试使用
public class ApiTest {@Testpublic void test(){System.out.println("\r\n模拟测试场景;微信支付、人脸方式。");Pay wxPay = new WxPay(new PayFaceMode());wxPay.transfer("weixin_1092033111", "100000109893", new BigDecimal(100));System.out.println("\r\n模拟测试场景;支付宝支付、指纹方式。");Pay zfbPay = new ZfbPay(new PayFingerprintMode());zfbPay.transfer("jlu19dlxo111", "100000109894", new BigDecimal(100));}
}
组合模式
- 通过把相似对象(也可以称作是方法)组合成一组可被调用的结构树对象的设计思路叫做组合模式,并且能够以统一的方式处理单个对象以及组合对象
- 1.一个相对简单的例子
// 组件接口
interface Component {void operation();
}// 叶子类
class Leaf implements Component {public void operation() {System.out.println("Leaf operation");}
}// 容器类
class Composite implements Component {private List<Component> components = new ArrayList<>();public void addComponent(Component component) {components.add(component);}public void removeComponent(Component component) {components.remove(component);}public void operation() {System.out.println("Composite operation");// 遍历子组件并调用其操作方法for (Component component : components) {component.operation();}}
}// 客户端代码
public class Client {public static void main(String[] args) {Component leaf1 = new Leaf();Component leaf2 = new Leaf();Component composite = new Composite();composite.addComponent(leaf1);composite.addComponent(leaf2);composite.operation();}
}
- 2.输出结果
Composite operation
Leaf operation
Leaf operation
装饰器模式
- 通过创建包装器对象来包裹原始对象,从而在不修改原始对象的情况下动态地扩展其功能。
- 1.原有一个拦截器接口
HandlerInterceptor
,以及一个实现类SsoInterceptor
,在不改变原有类的情况下 我们对其做扩展
public interface HandlerInterceptor {boolean preHandle(String request, String response, Object handler);
}
public class SsoInterceptor implements HandlerInterceptor{@Overridepublic boolean preHandle(String request, String response, Object handler) {// 模拟获取cookieString ticket = request.substring(1, 8);// 模拟校验return ticket.equals("success");}
}
- 2.抽象类装饰角色(将原有的逻辑接口接入)
SsoDecorator
,并针对抽象类进行继承增强LoginSsoDecorator
public abstract class SsoDecorator {private HandlerInterceptor handlerInterceptor;/*** 将原有的逻辑方法传入* @param handlerInterceptor*/public SsoDecorator(HandlerInterceptor handlerInterceptor){this.handlerInterceptor = handlerInterceptor;}/*** 继承原有的逻辑方法* @param request* @param response* @param handler* @return*/public boolean preHandle(String request, String response, Object handler) {return handlerInterceptor.preHandle(request, response, handler);}
}
public class LoginSsoDecorator extends SsoDecorator{private Logger logger = LoggerFactory.getLogger(LoginSsoDecorator.class);private static Map<String, String> authMap = new ConcurrentHashMap<String, String>();static {authMap.put("huahua", "queryUserInfo");authMap.put("doudou", "queryUserInfo");}/*** 将原有的逻辑方法传入** @param handlerInterceptor*/public LoginSsoDecorator(HandlerInterceptor handlerInterceptor) {super(handlerInterceptor);}@Overridepublic boolean preHandle(String request, String response, Object handler) {// 先调用原有的逻辑方法boolean success = super.preHandle(request, response, handler);// 自己新增的逻辑if (!success) return false;String userId = request.substring(8);String method = authMap.get(userId);logger.info("模拟单点登录方法访问拦截校验:{} {}", userId, method);// 模拟方法校验return "queryUserInfo".equals(method);}
}
- 3.测试使用
public class ApiTest {@Testpublic void test_LoginSsoDecorator() {LoginSsoDecorator ssoDecorator = new LoginSsoDecorator(new SsoInterceptor());String request = "1successhuahua";boolean success = ssoDecorator.preHandle(request, "ewcdqwt40liuiu", "t");System.out.println("登录校验:" + request + (success ? " 放行" : " 拦截"));}
}
外观模式
- 将一组复杂的子系统封装起来,对外提供一个简单的接口,以简化客户端与子系统的交互
- 1.在实际开发中我们会有多个controller,每个controller都需要拦截处理 校验用户是否能够访问(白名单),这个时候我们可以用自定义注解
DoDoor
+ aop来进行处理
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoDoor {/*** 拦截字段值* @return*/String key() default "";/*** 拦截时返回json* @return*/String returnJson() default "";}
- 2.aop实现自定义注解逻辑
@Aspect
@Component
public class DoJoinPoint {private Logger logger = LoggerFactory.getLogger(DoJoinPoint.class);@Value("${userIdStr}")public String userIdStr;@Pointcut("@annotation(com.imwj.design.door.annotation.DoDoor)")public void aopPoint(){}@Around("aopPoint()")public Object doRouter(ProceedingJoinPoint jp) throws Throwable {// 获取方法内容Method method = getMethod(jp);// 获取注解中的字段值DoDoor door = method.getAnnotation(DoDoor.class);String keyValue = getFiledValue(door.key(), jp.getArgs());logger.info("door handler method:{} value:{}", method.getName(), keyValue);// 获取不到值直接放行if (null == keyValue || "".equals(keyValue)) return jp.proceed();// 白名单放行if(checkUserIdIntercept(keyValue))return jp.proceed();// 拦截并返回return returnObject(door, method);}/*** 获取方法* @param jp* @return* @throws NoSuchMethodException*/private Method getMethod(JoinPoint jp) throws NoSuchMethodException {Signature sig = jp.getSignature();MethodSignature methodSignature = (MethodSignature) sig;return getClass(jp).getMethod(methodSignature.getName(), methodSignature.getParameterTypes());}private Class<? extends Object> getClass(JoinPoint jp) throws NoSuchMethodException {return jp.getTarget().getClass();}/*** 校验用户id是否需要拦截* @param userId* @return*/private Boolean checkUserIdIntercept(String userId){return userIdStr.contains(userId);}/*** 返回对象* @param doGate* @param method* @return* @throws IllegalAccessException* @throws InstantiationException*/private Object returnObject(DoDoor doGate, Method method) throws IllegalAccessException, InstantiationException {Class<?> returnType = method.getReturnType();String returnJson = doGate.returnJson();if ("".equals(returnJson)) {return returnType.newInstance();}return JSON.parseObject(returnJson, returnType);}/*** 获取属性值* @param filed* @param args* @return*/private String getFiledValue(String filed, Object[] args) {String filedValue = null;for (Object arg : args) {try {if (null == filedValue || "".equals(filedValue)) {filedValue = BeanUtils.getProperty(arg, filed);} else {break;}} catch (Exception e) {if (args.length == 1) {return args[0].toString();}}}return filedValue;}
}
- 3.controller层使用
@RestController
public class HelloController {@DoDoor(key = "userId", returnJson = "{\"code\":\"1111\",\"info\":\"非白名单用户拦截!\"}")@RequestMapping(path = "/api/queryUserInfo", method = RequestMethod.GET)public UserInfo queryUserInfo(@RequestParam String userId) {return new UserInfo("团团:" + userId, 19, "天津市南开区旮旯胡同100号");}
}
享元模式
- 享元模式,主要在于共享通用对象,减少内存的使用,提升系统的访问效率
- 1.有一个活动商品类
Activity
,里面有商品基础信息以及库存信息Stock
,我们希望通过数据库存储商品信息 redis里存储商品库存
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Activity {/** 活动ID */private Long id;/** 活动名称 */private String name;/** 活动描述 */private String desc;/** 开始时间 */private Date startTime;/** 结束时间 */private Date stopTime;/** 活动库存 */private Stock stock;
}
@Data
@AllArgsConstructor
public class Stock {/** 库存总量 */private int total;/** 库存已用 */private int used;
}
- 2.
RedisUtils
存储库存信息,获取时通过redis工具类获取
public class RedisUtils {private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);private AtomicInteger stock = new AtomicInteger(0);public RedisUtils() {scheduledExecutorService.scheduleAtFixedRate(() -> {// 模拟库存消耗stock.addAndGet(1);}, 0, 100000, TimeUnit.MICROSECONDS);}public int getStockUsed() {return stock.get();}
}
- 3.controller层获取库存
public class ActivityController {private RedisUtils redisUtils = new RedisUtils();public Activity queryActivityInfo(Long id) {Activity activity = ActivityFactory.queryInfo(id);// 模拟从Redis中获取库存变化信息Stock stock = new Stock(1000, redisUtils.getStockUsed());activity.setStock(stock);return activity;}
}
代理模式
- 它允许通过代理对象控制对另一个对象的访问。代理模式创建了一个代理对象,代理对象与原始对象具有相同的接口,客户端通过代理对象间接地访问原始对象,从而可以在不改变原始对象的情况下增加额外的功能或控制访问
- 常见的如:mybatis中只需要定义一个接口 然后在注解或xml中配置sql即能执行sql查询
- 1.定义一个
IUserDao
接口,一个@Select
注解
public interface IUserDao {@Select("select userName from user where id = #{uId}")String queryUserInfo(String uId);}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Select {/*** sql语句* @return*/String value() default "";
}
- 2.代理类定义
MapperFactoryBean
public class MapperFactoryBean<T> implements FactoryBean<T> {private Logger logger = LoggerFactory.getLogger(MapperFactoryBean.class);private Class<T> mapperInterface;public MapperFactoryBean(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}/*** 创建代理对象* @return* @throws Exception*/@Overridepublic T getObject() throws Exception {InvocationHandler handler = (proxy, method, args) ->{Select select = method.getAnnotation(Select.class);logger.info("SQL:{}", select.value().replace("#{uId}", args[0].toString()));return args[0] + ",乐于分享!";};return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperInterface}, handler);}@Overridepublic Class<?> getObjectType() {return mapperInterface;}@Overridepublic boolean isSingleton() {return true;}
}
- 3.注册beanFactory
public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {GenericBeanDefinition beanDefinition = new GenericBeanDefinition();beanDefinition.setBeanClass(MapperFactoryBean.class);beanDefinition.setScope("singleton");beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(IUserDao.class);BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, "userDao");BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {}
}
- 4.
spring-config.xml
中配置扫描;路径
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"default-autowire="byName"><bean id="userDao" class="com.imwj.design.agent.RegisterBeanFactory"/></beans>
- 5.注入使用
@Testpublic void test_IUserDao() {BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class);String res = userDao.queryUserInfo("100001");logger.info("测试结果:{}", res);}
行为型模式
责任链模式
- 用来处理相关事务责任的一条执行链,执行链上有多个节点,每个节点都有机会(条件匹配)处理请求事务,如果某个节点处理完了就可以根据实际业务需求传递给下一个节点继续处理或者返回处理完毕。
- Spring拦截器链、servlet过滤器链等都采用了责任链设计模式。
/*** 责任链抽象处理类* @author langao_q* @since 2021-12-29 15:37*/
public class AbstractLeaveHandler {/**三级领导处理*/protected int MIN = 10;/**二级领导处理*/protected int MIDDLE = 20;/**一级级领导处理*/protected int MAX = 30;/**领导名称*/protected String handlerName;/**下一个处理节点(即更高级别的领导)*/protected AbstractLeaveHandler nextHandler;/**设置下一节点*/protected void setNextHandler(AbstractLeaveHandler handler){this.nextHandler = handler;}/**处理请求,子类实现*/protected void handlerRequest(LeaveRequest request){}
}
/*** 请求实体* @author langao_q* @since 2021-12-29 15:37*/
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class LeaveRequest {/**天数*/private int leaveDays;/**姓名*/private String name;
}
/*** 一级领导* @author langao_q* @since 2021-12-29 15:39*/
public class OneLeaveHandler extends AbstractLeaveHandler {public OneLeaveHandler(String name) {this.handlerName = name;}@Overrideprotected void handlerRequest(LeaveRequest request) {if(request.getLeaveDays() > this.MIDDLE && request.getLeaveDays() <= this.MAX){System.out.println(handlerName + ",已经处理;流程结束。");return;}if(null != this.nextHandler){this.nextHandler.handlerRequest(request);}else{System.out.println("审批拒绝!");}}
}
/*** 二级领导* @author langao_q* @since 2021-12-29 15:38*/
public class TwoLeaveHandler extends AbstractLeaveHandler {public TwoLeaveHandler(String name) {this.handlerName = name;}@Overrideprotected void handlerRequest(LeaveRequest request) {if(request.getLeaveDays() >this.MIN && request.getLeaveDays() <= this.MIDDLE){System.out.println(handlerName + ",已经处理;流程结束。");return;}if(null != this.nextHandler){this.nextHandler.handlerRequest(request);}else{System.out.println("审批拒绝!");}}
}
/*** 三级领导* @author langao_q* @since 2021-12-29 15:38*/
public class ThreeLeaveHandler extends AbstractLeaveHandler{public ThreeLeaveHandler(String name) {this.handlerName = name;}@Overrideprotected void handlerRequest(LeaveRequest request) {if(request.getLeaveDays() <= this.MIN){System.out.println(handlerName + ",已经处理;流程结束。");return;}if(null != this.nextHandler){this.nextHandler.handlerRequest(request);}else{System.out.println("审批拒绝!");}}
}
/*** 测试类* @author langao_q* @since 2021-12-29 15:39*/
public class MainTest {public static void main(String[] args) {//根据leaveDays的值来决定是哪一级别的领导处理LeaveRequest request = LeaveRequest.builder().leaveDays(50).name("测试").build();/*** 三级(10) < 二级(20) < 一级(30);三级领导能处理就不往上走了 三级处理不了再抛给二级领导*/AbstractLeaveHandler directLeaderLeaveHandler = new ThreeLeaveHandler("三级领导");TwoLeaveHandler deptManagerLeaveHandler = new TwoLeaveHandler("二级领导");OneLeaveHandler gManagerLeaveHandler = new OneLeaveHandler("一级领导");//将各个处理类串联起来directLeaderLeaveHandler.setNextHandler(deptManagerLeaveHandler);deptManagerLeaveHandler.setNextHandler(gManagerLeaveHandler);//处理方法directLeaderLeaveHandler.handlerRequest(request);}
}
命令模式
- 将请求和操作进行解耦,以便能够将请求封装成独立的对象,并在需要时进行参数化
- 1.新建一个
ICook
厨师接口、一个ICuisine
菜系接口(菜系实现类中将对应的厨师实现类作为构造参数传入),两个接口各自有各自的n个实现类
public class GuangDongCook implements ICook{private Logger logger = LoggerFactory.getLogger(ICook.class);@Overridepublic void doCooking() {logger.info("广东厨师,烹饪粤菜,宫廷菜系,以孔府风味为龙头");}
}public class GuangDoneCuisine implements ICuisine {private ICook cook;private GuangDoneCuisine() {}public GuangDoneCuisine(ICook cook) {this.cook = cook;}@Overridepublic void cook() {cook.doCooking();}}
- 2.新建一个
XiaoEr
小二类,将对应的菜系作为构造参数传入
public class XiaoEr {private Logger logger = LoggerFactory.getLogger(XiaoEr.class);private List<ICuisine> cuisineList = new ArrayList<ICuisine>();public void order(ICuisine cuisine) {cuisineList.add(cuisine);}public synchronized void placeOrder() {for (ICuisine cuisine : cuisineList) {cuisine.cook();}cuisineList.clear();}
}
- 3.测试使用:构建厨师、菜系将菜系放入小二类的订单中
public class ApiTest {@Testpublic void test_xiaoEr(){// 菜系 + 厨师;广东(粤菜)、江苏(苏菜)、山东(鲁菜)、四川(川菜)ICuisine guangDoneCuisine = new GuangDoneCuisine(new GuangDongCook());JiangSuCuisine jiangSuCuisine = new JiangSuCuisine(new JiangSuCook());// 点单XiaoEr xiaoEr = new XiaoEr();xiaoEr.order(guangDoneCuisine);xiaoEr.order(jiangSuCuisine);xiaoEr.placeOrder();}
}
迭代器模式
- 略.
中介者模式
- 解决的就是复杂功能应用之间的重复调用,在这中间添加一层中介者包装服务,对外提供简单、通用、易扩展的服务能力。
- 1.新建一个
SqlSession
接口,以及一个对应实现类DefaultSqlSession
public class DefaultSqlSession implements SqlSession{private Connection connection;/*** key:dao路径 + 方法名,value:xml中的sql信息*/private Map<String, XNode> mapperElement;public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) {this.connection = connection;this.mapperElement = mapperElement;}@Overridepublic <T> T selectOne(String statement) {try {XNode xNode = mapperElement.get(statement);PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());ResultSet resultSet = preparedStatement.executeQuery();List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));return objects.get(0);} catch (Exception e) {e.printStackTrace();}return null;}@Overridepublic <T> T selectOne(String statement, Object parameter) {XNode xNode = mapperElement.get(statement);Map<Integer, String> parameterMap = xNode.getParameter();try {PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());buildParameter(preparedStatement, parameter, parameterMap);ResultSet resultSet = preparedStatement.executeQuery();List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));return objects.get(0);} catch (Exception e) {e.printStackTrace();}return null;}@Overridepublic <T> List<T> selectList(String statement) {try {XNode xNode = mapperElement.get(statement);PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());ResultSet resultSet = preparedStatement.executeQuery();List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));return objects;} catch (Exception e) {e.printStackTrace();}return null;}@Overridepublic <T> List<T> selectList(String statement, Object parameter) {XNode xNode = mapperElement.get(statement);Map<Integer, String> parameterMap = xNode.getParameter();try {PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());buildParameter(preparedStatement, parameter, parameterMap);ResultSet resultSet = preparedStatement.executeQuery();List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));return objects;} catch (Exception e) {e.printStackTrace();}return null;}@Overridepublic void close() {if (null == connection) return;try {connection.close();} catch (SQLException e) {e.printStackTrace();}}/*** jdbc查询结果转换为指定对象* @param resultSet* @param clazz* @return* @param <T>*/private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {List<T> list = new ArrayList<>();try {ResultSetMetaData metaData = resultSet.getMetaData();int columnCount = metaData.getColumnCount();// 每次遍历行值while (resultSet.next()) {T obj = (T) clazz.newInstance();for (int i = 1; i <= columnCount; i++) {Object value = resultSet.getObject(i);String columnName = metaData.getColumnName(i);String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);Method method;if (value instanceof Timestamp) {method = clazz.getMethod(setMethod, Date.class);} else {method = clazz.getMethod(setMethod, value.getClass());}method.invoke(obj, value);}list.add(obj);}} catch (Exception e) {e.printStackTrace();}return list;}private void buildParameter(PreparedStatement preparedStatement, Object parameter, Map<Integer, String> parameterMap) throws SQLException, IllegalAccessException {int size = parameterMap.size();// 单个参数if (parameter instanceof Long) {for (int i = 1; i <= size; i++) {preparedStatement.setLong(i, Long.parseLong(parameter.toString()));}return;}if (parameter instanceof Integer) {for (int i = 1; i <= size; i++) {preparedStatement.setInt(i, Integer.parseInt(parameter.toString()));}return;}if (parameter instanceof String) {for (int i = 1; i <= size; i++) {preparedStatement.setString(i, parameter.toString());}return;}Map<String, Object> fieldMap = new HashMap<>();// 对象参数Field[] declaredFields = parameter.getClass().getDeclaredFields();for (Field field : declaredFields) {String name = field.getName();field.setAccessible(true);Object obj = field.get(parameter);field.setAccessible(false);fieldMap.put(name, obj);}for (int i = 1; i <= size; i++) {String parameterDefine = parameterMap.get(i);Object obj = fieldMap.get(parameterDefine);if (obj instanceof Short) {preparedStatement.setShort(i, Short.parseShort(obj.toString()));continue;}if (obj instanceof Integer) {preparedStatement.setInt(i, Integer.parseInt(obj.toString()));continue;}if (obj instanceof Long) {preparedStatement.setLong(i, Long.parseLong(obj.toString()));continue;}if (obj instanceof String) {preparedStatement.setString(i, obj.toString());continue;}if (obj instanceof Date) {preparedStatement.setDate(i, (java.sql.Date) obj);}}}
}
- 2.新建一个
SqlSessionFactory
接口,以及对应实现类DefaultSqlSessionFactory
,其作用主要是配置和获取DefaultSqlSession
public class DefaultSqlSessionFactory implements SqlSessionFactory{private final Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic SqlSession openSession() {return new DefaultSqlSession(configuration.connection, configuration.mapperElement);}
}
- 3.新建一个
SqlSessionFactoryBuilder
,主要用于读取User_Mapper.xml
构建DefaultSqlSessionFactory
public class SqlSessionFactoryBuilder {public DefaultSqlSessionFactory build(Reader reader) {SAXReader saxReader = new SAXReader();try {saxReader.setEntityResolver(new XMLMapperEntityResolver());Document document = saxReader.read(new InputSource(reader));Configuration configuration = parseConfiguration(document.getRootElement());return new DefaultSqlSessionFactory(configuration);} catch (DocumentException e) {e.printStackTrace();}return null;}private Configuration parseConfiguration(Element root) {Configuration configuration = new Configuration();configuration.setDataSource(dataSource(root.selectNodes("//dataSource")));configuration.setConnection(connection(configuration.dataSource));configuration.setMapperElement(mapperElement(root.selectNodes("mappers")));return configuration;}// 获取数据源配置信息private Map<String, String> dataSource(List<Element> list) {Map<String, String> dataSource = new HashMap<>(4);Element element = list.get(0);List content = element.content();for (Object o : content) {Element e = (Element) o;String name = e.attributeValue("name");String value = e.attributeValue("value");dataSource.put(name, value);}return dataSource;}private Connection connection(Map<String, String> dataSource) {try {Class.forName(dataSource.get("driver"));return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));} catch (ClassNotFoundException | SQLException e) {e.printStackTrace();}return null;}// 获取SQL语句信息private Map<String, XNode> mapperElement(List<Element> list) {Map<String, XNode> map = new HashMap<>();Element element = list.get(0);List content = element.content();for (Object o : content) {Element e = (Element) o;String resource = e.attributeValue("resource");try {Reader reader = Resources.getResourceAsReader(resource);SAXReader saxReader = new SAXReader();Document document = saxReader.read(new InputSource(reader));Element root = document.getRootElement();//命名空间String namespace = root.attributeValue("namespace");// SELECTList<Element> selectNodes = root.selectNodes("select");for (Element node : selectNodes) {String id = node.attributeValue("id");String parameterType = node.attributeValue("parameterType");String resultType = node.attributeValue("resultType");String sql = node.getText();// ? 匹配Map<Integer, String> parameter = new HashMap<>();Pattern pattern = Pattern.compile("(#\\{(.*?)})");Matcher matcher = pattern.matcher(sql);for (int i = 1; matcher.find(); i++) {String g1 = matcher.group(1);String g2 = matcher.group(2);parameter.put(i, g2);sql = sql.replace(g1, "?");}XNode xNode = new XNode();xNode.setNamespace(namespace);xNode.setId(id);xNode.setParameterType(parameterType);xNode.setResultType(resultType);xNode.setSql(sql);xNode.setParameter(parameter);map.put(namespace + "." + id, xNode);}} catch (Exception ex) {ex.printStackTrace();}}return map;}}
- 4.测试使用,读取
mybatis-config-datasource.xml
配置 然后构建SqlSessionFactory
对象,最后获取到SqlSession
对象用于执行sql
@Testpublic void test_queryUserInfoById() {String resource = "mybatis-config-datasource.xml";Reader reader;try {reader = Resources.getResourceAsReader(resource);SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);SqlSession session = sqlMapper.openSession();try {User user = session.selectOne("com.imwj.design.dao.IUserDao.queryUserInfoById", "62baae12-41cb-11eb-8d21-00163e0cd193");logger.info("测试结果:{}", JSON.toJSONString(user));} finally {session.close();reader.close();}} catch (Exception e) {e.printStackTrace();}}
备忘录模式
- 以回滚,配置、版本、悔棋为核心功能的设计模式,在功能实现上是以不破坏原对象为基础增加备忘录操作类,记录原对象的行为从而实现备忘录模式。
- 1.新建一个配置文件类
ConfigFile
,主要用于记录版本的相关信息
@Data
@AllArgsConstructor
public class ConfigFile {private String versionNo; // 版本号private String content; // 内容private Date dateTime; // 时间private String operator; // 操作人
}
- 2.新建一个备忘录类
ConfigMemento
,对原有配置类的扩展,可以设置和获取配置信息
public class ConfigMemento {private ConfigFile configFile;public ConfigMemento(ConfigFile configFile) {this.configFile = configFile;}public ConfigFile getConfigFile() {return configFile;}public void setConfigFile(ConfigFile configFile) {this.configFile = configFile;}
}
- 3.新建一个记录者类
ConfigOriginator
,保存操作的相关信息
public class ConfigOriginator {private ConfigFile configFile;public ConfigFile getConfigFile() {return configFile;}public void setConfigFile(ConfigFile configFile) {this.configFile = configFile;}public ConfigMemento saveMemento(){return new ConfigMemento(configFile);}public void getMemento(ConfigMemento memento){this.configFile = memento.getConfigFile();}
}
- 4.创建一个管理员类
Admin
,主要操作回滚、前进、跳转到指定版本
public class Admin {private int cursorIdx = 0;private List<ConfigMemento> mementoList = new ArrayList<>();private Map<String, ConfigMemento> mementoMap = new ConcurrentHashMap<>();public void append(ConfigMemento memento){mementoList.add(memento);mementoMap.put(memento.getConfigFile().getVersionNo(), memento);cursorIdx ++;}public ConfigMemento undo(){if(--cursorIdx <= 0){return mementoList.get(0);}return mementoList.get(cursorIdx);}public ConfigMemento redo(){if(++cursorIdx > mementoList.size()){return mementoList.get(mementoList.size() - 1);}return mementoList.get(cursorIdx);}public ConfigMemento get(String versionNo){return mementoMap.get(versionNo);}}
- 5.测试使用
@Testpublic void test() {Admin admin = new Admin();ConfigOriginator configOriginator = new ConfigOriginator();configOriginator.setConfigFile(new ConfigFile("1000001", "配置内容A=哈哈", new Date(), "imwj"));admin.append(configOriginator.saveMemento()); // 保存配置configOriginator.setConfigFile(new ConfigFile("1000002", "配置内容A=嘻嘻", new Date(), "imwj"));admin.append(configOriginator.saveMemento()); // 保存配置configOriginator.setConfigFile(new ConfigFile("1000003", "配置内容A=么么", new Date(), "imwj"));admin.append(configOriginator.saveMemento()); // 保存配置configOriginator.setConfigFile(new ConfigFile("1000004", "配置内容A=嘿嘿", new Date(), "imwj"));admin.append(configOriginator.saveMemento()); // 保存配置 // 历史配置(回滚)configOriginator.getMemento(admin.undo());logger.info("历史配置(回滚)undo:{}", JSON.toJSONString(configOriginator.getConfigFile()));// 历史配置(回滚)configOriginator.getMemento(admin.undo());logger.info("历史配置(回滚)undo:{}", JSON.toJSONString(configOriginator.getConfigFile()));// 历史配置(前进)configOriginator.getMemento(admin.redo());logger.info("历史配置(前进)redo:{}", JSON.toJSONString(configOriginator.getConfigFile()));// 历史配置(获取)configOriginator.getMemento(admin.get("1000002"));logger.info("历史配置(获取)get:{}", JSON.toJSONString(configOriginator.getConfigFile()));}
观察者模式
- 当一个行为发生时传递信息给另外一个用户接收做出相应的处理,两者之间没有直接的耦合关联。
- 1.新建一个监听接口
EventListener
,对应的有两个实现类MessageEventListener
和MQEventListener
public class MessageEventListener implements EventListener{private Logger logger = LoggerFactory.getLogger(MessageEventListener.class);@Overridepublic void doEvent(LotteryResult result) {logger.info("给用户 {} 发送短信通知(短信):{}", result.getUId(), result.getMsg());}
}
- 2.新建一个监听事件处理类
EventManager
public class EventManager {Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>();public enum EventType {MQ, Message}public EventManager(Enum<EventType>... operations) {for (Enum<EventType> operation : operations) {this.listeners.put(operation, new ArrayList<>());}}/*** 订阅* @param eventType* @param listener*/public void subscribe(Enum<EventType> eventType, EventListener listener){List<EventListener> users = listeners.get(eventType);users.add(listener);}/*** 取消订阅* @param eventType* @param listener*/public void unsubscribe(Enum<EventType> eventType, EventListener listener){List<EventListener> users = listeners.get(eventType);users.remove(listener);}/*** 消息通知* @param eventType* @param result*/public void notify(Enum<EventType> eventType, LotteryResult result){List<EventListener> users = listeners.get(eventType);for(EventListener listener : users){listener.doEvent(result);}}
}
- 3.抽取业务类接口
LotteryService
(抽象类)
public abstract class LotteryService {private EventManager eventManager;public LotteryService(){eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);eventManager.subscribe(EventManager.EventType.MQ, new MQEventListener());eventManager.subscribe(EventManager.EventType.Message, new MessageEventListener());}public LotteryResult draw(String uId){LotteryResult lotteryResult = doDraw(uId);// 通知方法eventManager.notify(EventManager.EventType.MQ, lotteryResult);eventManager.notify(EventManager.EventType.Message, lotteryResult);return lotteryResult;}/*** 真正的业务方法* @param uId* @return*/protected abstract LotteryResult doDraw(String uId);}
- 4.测试使用
@Testpublic void test() {LotteryService lotteryService = new LotteryServiceImpl();LotteryResult result = lotteryService.draw("2765789109876");logger.info("测试结果:{}", JSON.toJSONString(result));}
状态模式
- 描述的是一个行为下的多种状态变更,比如我们最常见的一个网站的页面,在你登录与不登录下展示的内容是略有差异的(不登录不能展示个人信息),而这种登录与不登录就是我们通过改变状态,而让整个行为发生了变化
- 1.新建一个状态抽象类
State
,定义各个状态变更的方法
public abstract class State {/*** 活动提审** @param activityId 活动ID* @param currentStatus 当前状态* @return 执行结果*/public abstract Result arraignment(String activityId, Enum<Status> currentStatus);/*** 审核通过** @param activityId 活动ID* @param currentStatus 当前状态* @return 执行结果*/public abstract Result checkPass(String activityId, Enum<Status> currentStatus);/*** 审核拒绝** @param activityId 活动ID* @param currentStatus 当前状态* @return 执行结果*/public abstract Result checkRefuse(String activityId, Enum<Status> currentStatus);/*** 撤审撤销** @param activityId 活动ID* @param currentStatus 当前状态* @return 执行结果*/public abstract Result checkRevoke(String activityId, Enum<Status> currentStatus);/*** 活动关闭** @param activityId 活动ID* @param currentStatus 当前状态* @return 执行结果*/public abstract Result close(String activityId, Enum<Status> currentStatus);/*** 活动开启** @param activityId 活动ID* @param currentStatus 当前状态* @return 执行结果*/public abstract Result open(String activityId, Enum<Status> currentStatus);/*** 活动执行** @param activityId 活动ID* @param currentStatus 当前状态* @return 执行结果*/public abstract Result doing(String activityId, Enum<Status> currentStatus);}
- 2.新建两个状态实现类:编辑状态
EditingState
,提审状态CheckState
,同时在各自对应的方法中实现相应逻辑
public class EditingState extends State {public Result arraignment(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Check);return new Result("0000", "活动提审成功");}public Result checkPass(String activityId, Enum<Status> currentStatus) {return new Result("0001", "编辑中不可审核通过");}public Result checkRefuse(String activityId, Enum<Status> currentStatus) {return new Result("0001", "编辑中不可审核拒绝");}@Overridepublic Result checkRevoke(String activityId, Enum<Status> currentStatus) {return new Result("0001", "编辑中不可撤销审核");}public Result close(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Close);return new Result("0000", "活动关闭成功");}public Result open(String activityId, Enum<Status> currentStatus) {return new Result("0001", "非关闭活动不可开启");}public Result doing(String activityId, Enum<Status> currentStatus) {return new Result("0001", "编辑中活动不可执行活动中变更");}
}
public class CheckState extends State {public Result arraignment(String activityId, Enum<Status> currentStatus) {return new Result("0001", "待审核状态不可重复提审");}public Result checkPass(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Pass);return new Result("0000", "活动审核通过完成");}public Result checkRefuse(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Refuse);return new Result("0000", "活动审核拒绝完成");}@Overridepublic Result checkRevoke(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Editing);return new Result("0000", "活动审核撤销回到编辑中");}public Result close(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Close);return new Result("0000", "活动审核关闭完成");}public Result open(String activityId, Enum<Status> currentStatus) {return new Result("0001", "非关闭活动不可开启");}public Result doing(String activityId, Enum<Status> currentStatus) {return new Result("0001", "待审核活动不可执行活动中变更");}
}
- 3.新建一个状态控制类
StateHandler
,用来控制状态变更
public class StateHandler {private Map<Enum<Status>, State> stateMap = new ConcurrentHashMap<Enum<Status>, State>();public StateHandler() {stateMap.put(Status.Check, new CheckState()); // stateMap.put(Status.Close, new CloseState()); // stateMap.put(Status.Doing, new DoingState()); stateMap.put(Status.Editing, new EditingState()); // stateMap.put(Status.Open, new OpenState()); // stateMap.put(Status.Pass, new PassState()); // stateMap.put(Status.Refuse, new RefuseState()); }public Result arraignment(String activityId, Enum<Status> currentStatus) {return stateMap.get(currentStatus).arraignment(activityId, currentStatus);}public Result checkPass(String activityId, Enum<Status> currentStatus) {return stateMap.get(currentStatus).checkPass(activityId, currentStatus);}public Result checkRefuse(String activityId, Enum<Status> currentStatus) {return stateMap.get(currentStatus).checkRefuse(activityId, currentStatus);}public Result checkRevoke(String activityId, Enum<Status> currentStatus) {return stateMap.get(currentStatus).checkRevoke(activityId, currentStatus);}public Result close(String activityId, Enum<Status> currentStatus) {return stateMap.get(currentStatus).close(activityId, currentStatus);}public Result open(String activityId, Enum<Status> currentStatus) {return stateMap.get(currentStatus).open(activityId, currentStatus);}public Result doing(String activityId, Enum<Status> currentStatus) {return stateMap.get(currentStatus).doing(activityId, currentStatus);}}
- 4.测试使用
@Testpublic void test_Editing2Arraignment() {String activityId = "100001";// 初始化状态ActivityService.init(activityId, Status.Editing);StateHandler stateHandler = new StateHandler();// 获取对应状态操作类 并执行其中的方法Result result = stateHandler.arraignment(activityId, Status.Editing);logger.info("测试结果(编辑中To提审活动):{}", JSON.toJSONString(result));logger.info("活动信息:{} 状态:{}", JSON.toJSONString(ActivityService.queryActivityInfo(activityId)), JSON.toJSONString(ActivityService.queryActivityInfo(activityId).getStatus()));}
策略模式(重点)
- 允许在运行时选择算法的行为。它定义了一系列算法,并将每个算法封装在独立的类中,使它们可以互相替换。通过使用策略模式,可以使算法的变化独立于使用算法的客户端。 策略模式的主要目的是将算法的定义与使用分离,
- 1.新建一个优惠券接口
ICouponDiscount
,该接口定义了一个优惠方法
public interface ICouponDiscount<T> {/*** 优惠券金额计算* @param couponInfo 券折扣信息;直减、满减、折扣、N元购* @param skuPrice sku金额* @return 优惠后金额*/BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice);
}
- 2.优惠券接口有n个对应实现类:满减
MJCouponDiscount
,直减ZJCouponDiscount
,每个实现类有自己的计算方式 此处还用到了泛型
public class MJCouponDiscount implements ICouponDiscount<Map<String,String>> {/*** 满减计算* 1. 判断满足x元后-n元,否则不减* 2. 最低支付金额1元*/@Overridepublic BigDecimal discountAmount(Map<String,String> couponInfo, BigDecimal skuPrice) {String x = couponInfo.get("x");String o = couponInfo.get("n");// 小于商品金额条件的,直接返回商品原价if (skuPrice.compareTo(new BigDecimal(x)) < 0) return skuPrice;// 减去优惠金额判断BigDecimal discountAmount = skuPrice.subtract(new BigDecimal(o));if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE;return discountAmount;}
}
public class ZJCouponDiscount implements ICouponDiscount<Double > {/*** 直减计算* 1. 使用商品价格减去优惠价格* 2. 最低支付金额1元*/@Overridepublic BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {BigDecimal discountAmount = skuPrice.subtract(new BigDecimal(couponInfo));if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE;return discountAmount;}
}
- 3.新建一个控制类,用于控制控制使用不同优惠方式
Context
public class Context<T>{private ICouponDiscount<T> couponDiscount;public Context(ICouponDiscount<T> couponDiscount) {this.couponDiscount = couponDiscount;}public BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice) {return couponDiscount.discountAmount(couponInfo, skuPrice);}}
- 4.测试使用
@Testpublic void test_zj() {Context<Double> context = new Context<Double>(new ZJCouponDiscount());BigDecimal discountAmount = context.discountAmount(10D, new BigDecimal(100));logger.info("测试结果:直减优惠后金额 {}", discountAmount);Context<Map<String, String>> context1 = new Context<Map<String, String>>(new MJCouponDiscount());HashMap<String, String> map = new HashMap<>();map.put("x", "100");map.put("n", "30");BigDecimal discountAmount1 = context1.discountAmount(map, new BigDecimal(100));logger.info("测试结果:满减优惠后金额 {}", discountAmount1);}
模板方法模式(重点)
- 模板模式的核心设计思路是通过在,抽象类中定义抽象方法的执行顺序,并将抽象方法设定为只有子类实现,但不设计独立访问的方法
- 1.新建一个抽象父类
NetMall
,在父类中定义一个可对外调用的方法generateGoodsPoster
,同时定义三个待实现的方法(由后续子类实现[模板])
public abstract class NetMall {protected Logger logger = LoggerFactory.getLogger(NetMall.class);String uId; // 用户IDString uPwd; // 用户密码public NetMall(String uId, String uPwd) {this.uId = uId;this.uPwd = uPwd;}/*** 生成商品推广海报** @param skuUrl 商品地址(京东、淘宝、当当)* @return 海报图片base64位信息*/public String generateGoodsPoster(String skuUrl) {if (!login(uId, uPwd)) return null; // 1. 验证登录Map<String, String> reptile = reptile(skuUrl); // 2. 爬虫商品return createBase64(reptile); // 3. 组装海报}// 模拟登录protected abstract Boolean login(String uId, String uPwd);// 爬虫提取商品信息(登录后的优惠价格)protected abstract Map<String, String> reptile(String skuUrl);// 生成商品海报信息protected abstract String createBase64(Map<String, String> goodsInfo);
}
- 2.创建实现类
JDNetMall
,针对上面待实现的三个方法实现
public class JDNetMall extends NetMall {public JDNetMall(String uId, String uPwd) {super(uId, uPwd);}public Boolean login(String uId, String uPwd) {logger.info("模拟京东用户登录 uId:{} uPwd:{}", uId, uPwd);return true;}public Map<String, String> reptile(String skuUrl) {String str = HttpClient.doGet(skuUrl);Pattern p9 = Pattern.compile("(?<=title\\>).*(?=</title)");Matcher m9 = p9.matcher(str);Map<String, String> map = new ConcurrentHashMap<String, String>();if (m9.find()) {map.put("name", m9.group());}map.put("price", "5999.00");logger.info("模拟京东商品爬虫解析:{} | {} 元 {}", map.get("name"), map.get("price"), skuUrl);return map;}public String createBase64(Map<String, String> goodsInfo) {BASE64Encoder encoder = new BASE64Encoder();logger.info("模拟生成京东商品base64海报");return encoder.encode(JSON.toJSONString(goodsInfo).getBytes());}
}
- 3.测试使用
@Testpublic void test_NetMall() {NetMall netMall = new JDNetMall("1000001","*******");String base64 = netMall.generateGoodsPoster("https://item.jd.com/100008348542.html");logger.info("测试结果:{}", base64);}
访问者模式
- 访问者要解决的核心事项是,在一个稳定的数据结构下,例如用户信息、雇员信息等,增加易变的业务访问逻辑
- 1.新建一个用户抽象类
user
,和两个对应实现类老师Teacher
、学生Student
public abstract class User {public String name; // 姓名public String identity; // 身份;重点班、普通班 | 特级教师、普通教师、实习教师public String clazz; // 班级public User(String name, String identity, String clazz) {this.name = name;this.identity = identity;this.clazz = clazz;}// 核心访问方法public abstract void accept(Visitor visitor);
}public class Teacher extends User {public Teacher(String name, String identity, String clazz) {super(name, identity, clazz);}public void accept(Visitor visitor) {visitor.visit(this);}/*** 升本率* @return*/public double entranceRatio() {return BigDecimal.valueOf(Math.random() * 100).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();}
}public class Student extends User {public Student(String name, String identity, String clazz) {super(name, identity, clazz);}@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}/*** 排名* @return*/public int ranking() {return (int) (Math.random() * 100);}
}
- 2.新建一个访问者接口
Visitor
,以及对应的两个实现类校长Principal
和家长Parent
public interface Visitor {// 访问学生信息void visit(Student student);// 访问老师信息void visit(Teacher teacher);}public class Principal implements Visitor {private Logger logger = LoggerFactory.getLogger(Principal.class);public void visit(Student student) {logger.info("学生信息 姓名:{} 班级:{}", student.name, student.clazz);}public void visit(Teacher teacher) {logger.info("学生信息 姓名:{} 班级:{} 升学率:{}", teacher.name, teacher.clazz, teacher.entranceRatio());}}public class Parent implements Visitor {private Logger logger = LoggerFactory.getLogger(Parent.class);public void visit(Student student) {logger.info("学生信息 姓名:{} 班级:{} 排名:{}", student.name, student.clazz, student.ranking());}public void visit(Teacher teacher) {logger.info("老师信息 姓名:{} 班级:{} 级别:{}", teacher.name, teacher.clazz, teacher.identity);}}
- 3.数据看板
DataView
public class DataView {List<User> userList = new ArrayList<User>();public DataView() {userList.add(new Student("谢飞机", "重点班", "一年一班"));userList.add(new Student("windy", "重点班", "一年一班"));userList.add(new Student("大毛", "普通班", "二年三班"));userList.add(new Student("Shing", "普通班", "三年四班"));userList.add(new Teacher("BK", "特级教师", "一年一班"));userList.add(new Teacher("娜娜Goddess", "特级教师", "一年一班"));userList.add(new Teacher("dangdang", "普通教师", "二年三班"));userList.add(new Teacher("泽东", "实习教师", "三年四班"));}// 展示public void show(Visitor visitor) {for (User user : userList) {user.accept(visitor);}}
}
- 4.测试验证,不同视角下展示的数据不一致
@Testpublic void test(){DataView dataView = new DataView();logger.info("\r\n家长视角访问:");dataView.show(new Parent()); // 家长logger.info("\r\n校长视角访问:");dataView.show(new Principal()); // 校长}
解释器模式。
最后大哥图片镇楼
相关文章:
Java23种设计模式
本文主要是对Java中一些常用的设计模式进行讲解 后期会进行不断的更新,欢迎浏览 23种设计模式 创建型模式,共五种:工厂方法模式、抽象工厂模式、建造者模式、原型模式、单例模式。结构型模式,共七种:适配器模式、桥接…...
pieces of cake concerning torchtorchvision
1. version match torchvision的版本对应关系 2. utilize tqdm to present process bar lay a pbar from tqdm import tqdm pbar tqdm(unit"batch", filesys.stdout,totallen(self.training_dataloader)) #处理单位为batch pbar2 tqdm(range(20), descIt\s a t…...
如何在Python中处理JSON数据?
如何在Python中处理JSON数据? 在Python中处理JSON数据是一个常见的任务,因为JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它易于人阅读和编写,同时也易于机器解析和生成。Python的内置库…...
站群服务器如何提高搜索引擎排名
站群服务器是一种专门为多个相关联的网站提供支持的服务器,旨在通过网站集合的形式提高搜索引擎排名和曝光度。那么站群服务器如何提高搜索引擎排名呢?Rak部落小编为您整理发布。 站群服务器提高搜索引擎排名的原理主要在于以下几个方面: - **提高网站…...
Redis安装-Docker
安装redis的docker容器 1、创建redis挂载目录 mkdir -p /liuchaoxu/redis/{data,conf}2、复制配置文件 在 /liuchaoxu/redis/conf 目录中创建文件 redis.conf,文件从 redis-6.2.7.tar.gz 中解压获取 修改默认配置(从上至下依次): #bind 127.0.0.1 …...
day16-二叉树part03
104.二叉树的最大深度 (优先掌握递归) 根节点的高度就是二叉树的最大深度,后序遍历到叶子节点,对遍历高度取最小 class solution {/*** 递归法*/public int maxDepth(TreeNode root) {if (root null) {return 0;}int leftDepth maxDepth(ro…...
上位机图像处理和嵌入式模块部署(qmacvisual亮度检测)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 前面我们说过,在机器视觉当中,对于光源的处理要非常小心。这里面不仅包括了选择什么样的光源,还取决于怎样使用…...
防止推特Twitter账号被冻结,应该选什么代理类型IP?
在处理多个 Twitter 帐号时,选择合适的代理IP对于避免大规模帐户暂停至关重要。现在,问题出现了:哪种类型的代理是满足您需求的最佳选择?下面文章将为你具体讲解推特账号冻结原因以及重点介绍如何选择代理IP。 一、推特账号被冻结…...
【二叉树】Leetcode 114. 二叉树展开为链表【中等】
二叉树展开为链表 给你二叉树的根结点 root ,请你将它展开为一个单链表: 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同…...
2024年150道高频Java面试题(二十)
39. 说一下 HashMap 的实现原理? HashMap 是 Java 中使用非常普遍的一种基于散列的映射数据结构,主要用于存储键值对。它允许使用任何非空对象作为键和值,主要实现原理如下: 数组 链表 红黑树:HashMap 内部主要由一…...
Docker-Compose容器编排
基本介绍 使用一个Dockerfile模板文件,可以很方便的定义一个适合自己使用的自定义镜像。但在工作中经常会碰到需要多个容器相互配合来完成某项任务或运行某个项目的情况。例如要运行一个django项目,除了django容器本身,往往还需要再加上…...
nvm 安装多个版本的Node npm
先安装nvm 管理工具 git安装地址 找到安装包 下载然后安装 https://github.com/coreybutler/nvm-windows/releases/tag/1.1.11nvm常用命令 命令说明nvm version查看nvm版本nvm ls查看所有已经安装的Nodejs版本nvm list installed查看所有已经安装的Nodejs版本nvm ls availab…...
RisingWave 在品高股份 Bingo IAM 中的应用
背景介绍 公司背景 品高股份,是国内专业的云计算及行业信息化服务提供商。公司成立于 2003 年,总部位于广州,下设多家子公司和分公司,目前员工总数近 900 人,其中 80 %以上是专业技术人员。 品高股份在 2008 年便开…...
.Net Core/.Net6/.Net8 ,启动配置/Program.cs 配置
.Net Core/.Net6/.Net8 ,启动配置/Program.cs 配置 没有废话,直接上代码调用 没有废话,直接上代码 /// <summary>/// 启动类/// </summary>public static class Mains{static IServiceCollection _services;static IMvcBuilder _…...
尚硅谷2024最新Git企业实战教程 | Git与GitLab的企业实战
这篇博客是尚硅谷2024最新Git企业实战教程,全方位学习git与gitlab的完整笔记。 这不仅仅是一套Git的入门教程,更是全方位的极狐GitLab企业任务流开发实战!作为一应俱全的一站式DevOps平台,极狐GitLab的高阶功能全面覆盖࿰…...
2024阿里云老用户服务器优惠价格99元和199元
阿里云服务器租用价格表2024年最新,云服务器ECS经济型e实例2核2G、3M固定带宽99元一年,轻量应用服务器2核2G3M带宽轻量服务器一年61元,ECS u1服务器2核4G5M固定带宽199元一年,2核4G4M带宽轻量服务器一年165元12个月,2核…...
【前端webpack5高级优化】提升打包构建速度几种优化方案
HotModuleReplacement(HMR/热模块替换) 开发时我们修改了其中一个模块代码,Webpack 默认会将所有模块全部重新打包编译,速度很慢 所以我们需要做到修改某个模块代码,就只有这个模块代码需要重新打包编译,…...
【第十一届大唐杯全国大学生新一代信息通信技术大赛】赛题分析
赛道一 一等奖 7% 二等奖 15% 三等奖 25% 赛道二 参考文档: 《第十一届大唐杯全国大学生新一代信息通信技术大赛(产教融合5G创新应用设计)专项赛说明.pdf》 一等奖:7% 二等奖:10% 三等奖:20% 赛项一&am…...
Java面试题:Java集合框架:请简述Java集合框架的主要组成部分,并解释它们之间的关系。
Java集合框架(Java Collections Framework)是一组用来表示和操作集合的类的集合,它提供了用于存储不同类型对象的标准化接口和类。Java集合框架的主要组成部分包括以下几个部分: 集合接口(Collection Interface&#…...
hadoop3.0高可用分布式集群安装
hadoop高可用,依赖于zookeeper。 用于生产环境, 企业部署必须的模式. 1. 部署环境规划 1.1. 虚拟机及hadoop角色划分 主机名称 namenode datanode resourcemanager nodemanager zkfc journalnode zookeeper master slave1 slave2 1.2. 软件版本 java …...
Flink SQL系列之:解析Debezium数据格式时间字段常用的函数
Flink SQL系列之:解析Debezium数据格式时间字段常用的函数 一、FROM_UNIXTIME二、DATE_FORMAT三、TO_DATE四、CAST五、TO_TIMESTAMP_LTZ六、CONVERT_TZ七、FROM_UNIXTIME八、TO_TIMESTAMP九、常见用法案例1.案例一2.案例二3.案例三4.案例四5.案例五...
Redis底层数据结构-Dict
1. Dict基本结构 Redis的键与值的映射关系是通过Dict来实现的。 Dict是由三部分组成,分别是哈希表(DictHashTable),哈希节点(DictEntry),字典(Dict) 哈希表结构如下图所…...
Python基于深度学习的人脸识别项目源码+演示视频,利用OpenCV进行人脸检测与识别 preview
一、原理介绍 该人脸识别实例是一个基于深度学习和计算机视觉技术的应用,主要利用OpenCV和Python作为开发工具。系统采用了一系列算法和技术,其中包括以下几个关键步骤: 图像预处理:首先,对输入图像进行预处理&am…...
CTF下加载CTFtraining题库以管理员身份导入 [HCTF 2018]WarmUp,之后以参赛者身份完成解题全过程
-------------------搭建CTFd------------------------------ 给大家介绍一个本地搭建比较好用的CTF比赛平台:CTFD。 CTFd是一个Capture The Flag框架,侧重于易用性和可定制性。它提供了运行CTF所需的一切,并且可以使用插件和主题轻松进行自…...
机器学习每周挑战——信用卡申请用户数据分析
数据集的截图 # 字段 说明 # Ind_ID 客户ID # Gender 性别信息 # Car_owner 是否有车 # Propert_owner 是否有房产 # Children 子女数量 # Annual_income 年收入 # Type_Income 收入类型 # Education 教育程度 # Marital_status 婚姻状况 # Housing_type 居住…...
Vulnhub:WESTWILD: 1.1
目录 信息收集 arp nmap nikto whatweb WEB web信息收集 dirmap enm4ulinux sumbclient get flag1 ssh登录 提权 横向移动 get root 信息收集 arp ┌──(root㉿ru)-[~/kali/vulnhub] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 0…...
[C#]winform使用OpenCvSharp实现透视变换功能支持自定义选位置和删除位置
【透视变换基本原理】 OpenCvSharp 是一个.NET环境下对OpenCV原生库的封装,它提供了大量的计算机视觉和图像处理的功能。要使用OpenCvSharp实现透视变换(Perspective Transformation),你首先需要理解透视变换的原理和它在图像处理…...
C++——list类及其模拟实现
前言:这篇文章我们继续进行C容器类的分享——list,也就是数据结构中的链表,而且是带头双向循环链表。 一.基本框架 namespace Mylist {template<class T>//定义节点struct ListNode{ListNode<T>* _next;ListNode<T>* _pre…...
https访问http的minio 图片展示不出来
问题描述:请求到的图片地址单独访问能显示,但是在网页中展示不出来 原因:https中直接访问http是不行的,需要用nginx再转发一下 nginx配置如下(注意:9000是minio默认端口,已经占用,…...
【Python整理】 Python知识点复习
1.Python中__init__()中声明变量必须都是self吗? 在Python中的类定义里,init() 方法是一个特殊的方法,称为类的构造器。在这个方法中,通常会初始化那些需要随着对象实例化而存在的实例变量。使用 self 是一种约定俗成的方式来引用实例本身。…...
杭州做网站五/seo网站优化培训怎么样
冒泡排序 通过相邻元素的比较和交换,使得每一趟循环都能找到未有序数组的最大值或最小值。 // 最好:O(n),只需要冒泡一次数组就有序了。 // 最坏:O(n) // 平均:O(n) function bubbleSort(arr){for(let i 0,len arr.l…...
网站建设与企业发展/注册城乡规划师报考条件
国内领先的Linux厂商中科红旗软件技术有限公司日前宣布,国家“金盾工程”项目采用了红旗Linux操作系统,为出入境管理局搭建起一个高效、稳定、安全的出入境管理信息系统平台。实现了出入境管理信息系统“加快验放速度、提高控制能力、强化内部管理”的目…...
网站是否必须做认证/国外广告联盟平台
摘要:单日总数据处理量超 10 万亿,峰值大概超过每秒 3 亿,OPPO 大数据平台研发负责人张俊揭秘 OPPO 基于 Apache Flink 构建实时数仓的实践,内容分为以下四个方面:建设背景顶层设计落地实践未来展望重要:公…...
泉州网站设计/搜索引擎营销特点是什么
目录 0. 相关文章链接 1. Elasticsearch简介 2. 应用场景 3. 工程化案例 4. 用户画像标签数据存储总结 注:此博文为根据 赵宏田 老师的 用户画像方法论与工程化解决方案 一书读后笔记而来,仅供学习使用 0. 相关文章链接 用户画像文章汇总 1. Ela…...
常州网站制作公司排名/怎样做企业推广
各位同学们大家好,我是雪山凌狐,欢迎学习python3小白课。上一节课咱们下载好了python3.8.2 64位的安装包到本地,本节课我们就来简单几步教会大家如何进行安装。实际操作,来啦~首先打开我们下载好的python安装包的目录,…...
电子工程网站大全/百度竞价排名软件
PPT from WGS...