设计模式:以桥接模式和访问者模式为例,看设计模式在微信小游戏版本迭代中的应用(上)

简介: 去年9月份,微信小游戏《羊了个羊》火爆全网,由于同时在线玩家过多,开发商服务器2天之内竟然出现了3次宕机。这在云开发时代是极少出现的,若不是火爆程度大大超出了预期,程序员怎么可能来不及扩容服务器呢

image.png

去年9月份,微信小游戏《羊了个羊》火爆全网,由于同时在线玩家过多,开发商服务器2天之内竟然出现了3次宕机。这在云开发时代是极少出现的,若不是火爆程度大大超出了预期,程序员怎么可能来不及扩容服务器呢?


微信小游戏开发整体来讲简单、独立、易上手,即使是一个人,也可以开发,不少程序员还是独立的微信小游戏开发者,仅靠游戏收入就远远超过了一般程序员的上班收入。《羊了个羊》小游戏的火爆,更加刺激了程序员,尤其是前端程序员向这个领域转行。


为什么要在游戏开发中使用设计模式呢?


对于游戏开发,一般人认为这是一个创意行业,不仅要有过硬的技术,更要有新奇的创意。这个认知没有错,但是,创意是不受法律保护的,任何一个创意火爆以后,马上就可能有N个开发商跟风抄袭。在游戏行业的开发史上,已经出现过多次,第一个想出创意的老大被后来居上的老二反超了。


怎么应对这种情况呢?


如果别人跑得快,就要想办法比别人跑得更快,跑得更久。游戏开发和其他所有软件产品的开发一样,并不是一锤子买卖,在第一个版本上线以后,后续根据玩家反馈和竞品功能的升级,需要不断研发和推出新版本。


在版本迭代的过程中,怎么样让新功能更快地开发出来,同时老功能还能更大范围地保持稳定,这是最考验游戏架构师能力的。架构师在项目启动的时候,就要为后续可能的变化预留方案,让后面游戏版本的迭代进行得又快、又稳。这涉及游戏架构师的一项核心能力:渐进式模块化重构与面向对象重构的能力。


软件开发是有成熟的套路的,前辈大牛经过实践总结的设计模式便是套路的结晶,有意识地在游戏开发中运用成熟的设计模式,不仅可以彰显程序员的内功水平,还能在一定程度上保证版本迭代的快速与稳定。


当前的小游戏项目分析


接下来作者分享的,是来自《微信小游戏开发》这本书中的一个小游戏实战案例,项目进行到第11章,基本功能已经开发完了,为了方便读者锤炼渐进式模块化重构与面向对象重构的能力,特意在这个阶段安排了设计模式实战。


在目前的项目中(以《微信小游戏开发》前端篇随书源码第11章/11.1/11.1.1的源码为基础),有两类碰撞检测:一类发生在球与挡板之间;另一类发生在球与屏幕边界之间。在游戏中,碰撞检测是非常常见一种功能,为了应对可能增加的碰撞检测需求,我们使用设计模式将两类碰撞的耦合性降低,方便后续加入新的碰撞与被碰撞对象。


具体从实现上来讲,接下来我们准备应用桥接模式,将发生碰撞的双方,分别定义为两个可以独立变化的抽象对象(HitObjectRectangle与HitedObjectRectangle),然后再让它们的具体实现部分独立变化,以此完成对桥接模式的应用。


目前球(Ball)与挡板(Panel)还没有基类,我们可以让它们继承于新创建的抽象基类,但这样并不是很合理,它们都属于可视化对象,如果要继承,更应该继承于Component基类。在JS中一个类的继承只能实现单继续,不能让一个类同时继承于多个基类,在这种情况下我们怎么实现桥接模式中的抽象部分呢?对象能力的扩展形式,除了继承,还有复合,我们可以将定义好的桥接模式中的具体实现部分,以类属性的方式放在球和挡板对象中。


什么是桥接模式?


在应用桥接模式之前,我们先从概念上简单了解一下什么是桥接模式。

桥接模式是一种结构型设计模式, 可将一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。


换一个说法,桥接模式将对象的抽象部分与它的具体实现部分分离,使它们都可以独立的变化。在桥接模式中,一般包括两个抽象部分和两个具体实现的部分,一个抽象部分和一个具体实现部分为一组,一共有两组,两组通过中间的抽象部分进行桥接,从而让两组的具体实现部分可以相对独立自由的变化。


为了更好地理解这个模式,我们通过一张图看一个应用示例,如图11-1所示:

image.png

图11-1桥接模式示例示意图


在这张图中,中间是一个跨平台开发框架,它为开发者抽离出一套通用接口(抽象部分B),这些接口是通用的、系统无关的,借此开发框架实现了跨平台特性。在开发框架中,具体到每个系统(Mac、Windows和Linux),每个接口及UI有不同的实现(具体实现部分B1、B2、B3)。左边,在应用程序中,开发者在软件中定义了一套抽象部分A,在每个系统上有不同的具体实现(具体实现部分A1、A2、A3)。应用程序面向抽象部分B编程,不必关心开发框架在每个系统下的具体实现;应用程序的具体实现部分A1、A2、A3是基于抽象部分A编程的,它们也不需要知道抽象部分B。抽象部分A与抽象部分B之间仿佛有一个桥连接了起来,这两套抽象部分与其具体实现部分呈现的模式便是桥接模式。


试想一下,如果我们不使用桥接模式,没有中间这一层跨平台开发框架,没有抽象部分B和抽象部分A,这时候我们想实现具体实现部分A1、A2、A3,怎么做呢?直接在各个系统的基础类库上实现呢?让A1与B1耦合、A2与B2耦合、A3与B3耦合吗?每次在应用程序中添加一个新功能,都要在三个地方分别实现。而有了桥接模式之后,B1、B2、B3都不需要关心了,只需要知道抽象部分B就可以了;添加新功能时,只需要在抽象部分A中定义并基于抽象部分B实现核心功能就可以了,在具体实现部分A1、A2、A3中只是UI和交互方式不同而已。这是使用桥接模式的价值。


桥接模式的具体实现


接下来开始实践,我们先定义桥接模式当中的抽象部分,一个是主动撞击对象的抽象部分(HitObjectRectangle),一个是被动撞击对象的抽象部分(HitedObjectRectangle)。由于两个部分的抽象部分具有相似性,我们可以先定义一个抽象部分的基类Rectangle,如代码清单11-7所示:

代码清单11-7矩形基类


1.// JS:src\views\hitTest\rectangle.js
2./** 对象的矩形描述,默认将注册点放在左上角 */
3.class Rectangle {
4.  constructor(x, y, width, height) {
5.    this.x = x
6.    this.y = y
7.    this.width = width
8.    this.height = height
9.  }
10.
11.  /** X坐标 */
12.  x = 0
13.  /** Y坐标 */
14.  y = 0
15.  /** X轴方向上所占区域 */
16.  width = 0
17.  /** Y轴方向上所占区域 */
18.  height = 0
19.
20.  /** 顶部边界 */
21.  get top() {
22.    return this.y
23.  }
24.  /** 底部边界 */
25.  get bottom() {
26.    return this.y + this.height
27.  }
28.  /** 左边界 */
29.  get left() {
30.    return this.x
31.  }
32.  /** 右边界 */
33.  get right() {
34.    return this.x + this.width
35.  }
36.}
37.
38.export default Rectangle


上面代码做了什么事?


❑ 第12行至第18行,这是4个属性,x、y决定注册点,width、height决定尺寸。

❑ 第21行至第35行,这是4个getter访问器,分别代表对象在4个方向上的边界值。这4个属性不是实际存在的,而是通过注册点与尺寸计算出来的。根据注册点位置的不同,这4个getter的值也不同。默认注册点,即(0,0)坐标点在左上角,这时候top等于y;如果注册点在左下角,这时候top则等于y减去height。


Rectangle描述了一个对象的距形范围,关于4个边界属性top、bottom、left、right与注册点的关系,可以参见图11-2:


image.png


图11-2注册点与边界值的关系


接下来我们开始定义两个抽象部分:一个是撞击对象的,另一个是受撞击对象的。先看受撞击对象的,它比较简单:


1.// JS:src\views\hitTest\hited_object_rectangle.js
2.import Rectangle from "rectangle.js"
3.
4./** 被碰撞对象的抽象部分,屏幕及左右挡板的注册点默认在左上角 */
5.class HitedObjectRectangle extends Rectangle{
6.  constructor(x, y, width, height){
7.    super(x, y, width, height)
8.  }
9.}
10.
11.export default HitedObjectRectangle


HitedObjectRectangle类它没有新增属性或方法,所有特征都是从基类继承的。它的主要作用是被继承,稍后有3个子类继承它。


再看一下撞击对象的定义,如代码清单11-8所示:


代码清单11-8创建撞击对象基类


1.// JS:src\views\hitTest\hit_object_rectangle.js
2.import Rectangle from "rectangle.js"
3.import LeftPanelRectangle from "left_panel_rectangle.js"
4.import RightPanelRectangle from "right_panel_rectangle.js"
5.import ScreenRectangle from "screen_rectangle.js"
6.
7./** 碰撞对象的抽象部分,球与方块的注册点在中心,不在左上角 */
8.class HitObjectRectangle extends Rectangle {
9.  constructor(width, height) {
10.    super(GameGlobal.CANVAS_WIDTH / 2, GameGlobal.CANVAS_HEIGHT / 2, width, height)
11.  }
12.
13.  get top() {
14.    return this.y - this.height / 2
15.  }
16.  get bottom() {
17.    return this.y + this.height / 2
18.  }
19.  get left() {
20.    return this.x - this.width / 2
21.  }
22.  get right() {
23.    return this.x + this.width / 2
24.  }
25.
26.  /** 与被撞对象的碰撞检测 */
27.  hitTest(hitedObject) {
28.    let res = 0
29.    if (hitedObject instanceof LeftPanelRectangle) { // 碰撞到左挡板返回1
30.      if (this.left < hitedObject.right && this.top > hitedObject.top && this.bottom < hitedObject.bottom) {
31.        res = 1 << 0
32.      }
33.    } else if (hitedObject instanceof RightPanelRectangle) { // 碰撞到右挡板返回2
34.      if (this.right > hitedObject.left && this.top > hitedObject.top && this.bottom < hitedObject.bottom) {
35.        res = 1 << 1
36.      }
37.    } else if (hitedObject instanceof ScreenRectangle) {
38.      if (this.right > hitedObject.right) { // 触达右边界返回4
39.        res = 1 << 2
40.      } else if (this.left < hitedObject.left) { // 触达左边界返回8
41.        res = 1 << 3
42.      }
43.      if (this.top < hitedObject.top) { // 触达上边界返回16
44.        res = 1 << 4
45.      } else if (this.bottom > hitedObject.bottom) { // 触达下边界返回32
46.        res = 1 << 5
47.      }
48.    }
49.    return res
50.  }
51.}
52.
53.export default HitObjectRectangle

在上面代码中:


❑ HitObjectRectangle也是作为基类存在的,稍后有一个子类继承它。在这个基类中,第13行至第24行,我们通过重写getter访问器属性,将注册点由左上角移到了中心。


❑ 第10行,在构造器函数中我们看到,默认的起始x、y是屏幕中心的坐标。


❑ 第27行至第50行,hitTest方法的实现是核心代码,碰撞到左挡板与碰撞到右挡板返回的数字与之前定义的一样,碰撞四周墙壁返回的数字是4个新增的数字。


❑ 第35行,这行出现的1<<0代表数值的二进制向左移0个位置。移0个位置没有意义,这样书写是为了与下面的第35行、第39行、第41行等保持格式一致。1<<0等于1,1<<1等于2,1<<2等于4,1<<3等于8,这些数值是按2的N次幂递增的。


接下来我们定义ScreenRectangle,它是被撞击部分的具体实现部分:


1.// JS:src\views\hitTest\screen_rectangle.js
2.import HitedObjectRectangle from "hited_object_rectangle.js"
3.
4./** 被碰撞对象屏幕的大小数据 */
5.class ScreenRectangle extends HitedObjectRectangle {
6.  constructor() {
7.    super(0, 0, GameGlobal.CANVAS_WIDTH, GameGlobal.CANVAS_HEIGHT)
8.  }
9.}
10.
export default ScreenRectangle


ScreenRectangle是屏幕的大小、位置数据对象,是一个继承于HitedObjectRectangle的具体实现。ScreenRectangle类作为一个具体的实现类,却没有添加额外的属性或方法,那我们为什么要定义它呢?它存在的意义,是由它本身作为一个对象成立的,参见HitObjectRectangle类中的hitTest方法。

接下来我们再看左挡板的大小、位置数据对象:


1.// JS:src\views\hitTest\left_panel_rectangle.js
2.import HitedObjectRectangle from "hited_object_rectangle.js"
3.
4./** 被碰撞对象左挡板的大小数据 */
5.class LeftPanelRectangle extends HitedObjectRectangle {
6.  constructor() {
7.    super(0, (GameGlobal.CANVAS_HEIGHT - GameGlobal.PANEL_HEIGHT) / 2, GameGlobal.PANEL_WIDTH, GameGlobal.PANEL_HEIGHT)
8.  }
9.}
10.
11.export default LeftPanelRectangle


LeftPanelRectangle与ScreenRectangle一样,是继承于HitedObjectRectangle的一个具体实现,仍然没有新增属性或方法,所有信息,包括大小和位置,都已经通过构造器参数传递进去了。

再看一下右挡板的大小、位置数据对象:


1.// JS:src\views\hitTest\right_panel_rectangle.js
2.import HitedObjectRectangle from "hited_object_rectangle.js"
3.
4./** 被碰撞对象右挡板的大小数据 */
5.class RightPanelRectangle extends HitedObjectRectangle {
6.  constructor() {
7.    super(GameGlobal.CANVAS_WIDTH - GameGlobal.PANEL_WIDTH, (GameGlobal.CANVAS_HEIGHT - GameGlobal.PANEL_HEIGHT) / 2, GameGlobal.PANEL_WIDTH, GameGlobal.PANEL_HEIGHT)
8.  }
9.}
10.
11.export default RightPanelRectangle


RightPanelRectangle也是继承于HitedObjectRectangle的一个具体实现,与LeftPanelRectangle不同的只是坐标位置。


接下来我们再看撞击对象这边的具体实现部分,只有一个BallRectangle类:


1.// JS:src\views\hitTest\ball_rectangle.js
2.import HitObjectRectangle from "hit_object_rectangle.js"
3.
4./** 碰撞对象的具体实现部分,球的大小及运动数据对象 */
5.class BallRectangle extends HitObjectRectangle {
6.  constructor() {
7.    super(GameGlobal.RADIUS * 2, GameGlobal.RADIUS * 2)
8.  }
9.}
10.
11.export default BallRectangle


BallRectangle是描述球的位置、大小的,所有信息在基类中都具备了,所以它不需要添加任何属性或方法了。


以上就是我们为应用桥接模式定义的所有类了,为了进一步明确它们之间的关系,看一张示意图,如图11-3所示:


image.png

图11-3桥接模式示例类关系图


第二层的HitObjectRectangle和HitedObjectRectangle是桥接模式中的抽象部分,第三层是具体实现部分。事实上如果我们需要的话,我们在HitObjectRectangle和HitedObjectRectangle两条支线上,还可以定义更多的具体实现类。


在项目中消费桥接模式


接下来看如何使用,先改造原来的Ball类,如代码清单11-9所示:

代码清单11-9改造Ball类


1.// JS:src/views/ball.js
2.import BallRectangle from "hitTest/ball_rectangle.js"
3.
4./** 小球 */
5.class Ball {
6.  ...
7.
8.  constructor() { }
9.
10.  get x() {
11.    // return this.#pos.x
12.    return this.rectangle.x
13.  }
14.  get y() {
15.    // return this.#pos.y
16.    return this.rectangle.y
17.  }
18.  /** 小于碰撞检测对象 */
19.  rectangle = new BallRectangle()
20.  // #pos // 球的起始位置
21.  #speedX = 4 // X方向分速度
22.  #speedY = 2 // Y方向分速度
23.
24.  /** 初始化 */
25.  init(options) {
26.    // this.#pos = options?.ballPos ?? { x: GameGlobal.CANVAS_WIDTH / 2, y: GameGlobal.CANVAS_HEIGHT / 2 } 
27.    // const defaultPos = { x: this.#pos.x, y: this.#pos.y }
28.    // this.reset = () => {
29.    //   this.#pos.x = defaultPos.x
30.    //   this.#pos.y = defaultPos.y
31.    // }
32.    this.rectangle.x = options?.x ?? GameGlobal.CANVAS_WIDTH / 2
33.    this.rectangle.y = options?.y ?? GameGlobal.CANVAS_HEIGHT / 2
34.    this.#speedX = options?.speedX ?? 4
35.    this.#speedY = options?.speedY ?? 2
36.    const defaultArgs = Object.assign({}, this.rectangle)
37.    this.reset = () => {
38.      this.rectangle.x = defaultArgs.x
39.      this.rectangle.y = defaultArgs.y
40.      this.#speedX = 4
41.      this.#speedY = 2
42.    }
43.  }
44.
45.  /** 重设 */
46.  reset() { }
47.
48.  /** 渲染 */
49.  render(context) {
50.    ...
51.  }
52.
53.  /** 运行 */
54.  run() {
55.    // 小球运动数据计算
56.    // this.#pos.x += this.#speedX
57.    // this.#pos.y += this.#speedY
58.    this.rectangle.x += this.#speedX
59.    this.rectangle.y += this.#speedY
60.  }
61.
62.  /** 小球与墙壁的四周碰撞检查 */
63.  // testHitWall() {
64.  //   if (this.#pos.x > GameGlobal.CANVAS_WIDTH - GameGlobal.RADIUS) { // 触达右边界
65.  //     this.#speedX = -this.#speedX
66.  //   } else if (this.#pos.x < GameGlobal.RADIUS) { // 触达左边界
67.  //     this.#speedX = -this.#speedX
68.  //   }
69.  //   if (this.#pos.y > GameGlobal.CANVAS_HEIGHT - GameGlobal.RADIUS) { // 触达右边界
70.  //     this.#speedY = -this.#speedY
71.  //   } else if (this.#pos.y < GameGlobal.RADIUS) { // 触达左边界
72.  //     this.#speedY = -this.#speedY
73.  //   }
74.  // }
75.  testHitWall(hitedObject) {
76.    const res = this.rectangle.hitTest(hitedObject)
77.    if (res === 4 || res === 8) {
78.      this.#speedX = -this.#speedX
79.    } else if (res === 16 || res === 32) {
80.      this.#speedY = -this.#speedY
81.    }
82.  }
83.
84.  ...
85.}
86.
87.export default Ball.getInstance()


在Ball类中发生了什么变化?


❑ 第19行,我们添加了新的类属性rectangle,它是BallRectangle的实例。所有关于球的位置、大小等信息都移到了rectangle中,所以原来的类属性#pos(第20行)不再需要了,同时原来调用它的代码(例如第58行、第59行)都需要使用rectangle改写。

❑ 第32行至第42行,这是初始化代码,原来#pos是一个坐标,包括x、y两个值,现在将这两个值分别以rectangle中的x、y代替。

❑ 方法testHitWall用于屏幕边缘碰撞检测的,第63行至第74行的是旧代码,第75行至第82行是新代码。hitedObject是新增的参数,它是HitedObjectRectangle子类的实例。

小球属于撞击对象,它的rectangle是一个HitObjectRectangle的子类实例(BallRectangle)。

看一下对Panel类的改造,它是LeftPanel和RightPanel的基类,如代码清单11-10所示:

代码清单11-10改造Panel类


1.// JS:src/views/panel.js
2./** 挡板基类 */
3.class Panel {
4.  constructor() { }
5.
6.  // x // 挡板的起点X坐标
7.  // y // 挡板的起点Y坐标
8.  get x() {
9.    return this.rectangle.x
10.  }
11.  set x(val) {
12.    this.rectangle.x = val
13.  }
14.  get y() {
15.    return this.rectangle.y
16.  }
17.  set y(val) {
18.    this.rectangle.y = val
19.  }
20.  /** 挡板碰撞检测对象 */
21.  rectangle
22.  ...
23.}
24.
25.export default Panel


这个基类发生了什么变化?


❑ 第21行,rectangle是新增的HitedObjectRectangle的子类实例,具体是哪个实现,要在子类中决定。

❑ 第6行、第7行将x、y去掉,代之以第8行至第19行的getter访问器和setter设置器,对x、y属性的访问和设置,将转变为对rectangle中x、y的访问和设置。

为什么要在Panel基类中新增一个rectangle属性?因为要在它的子类LeftPanel、RightPanel中新增这个属性,挡板是被撞击对象,rectangle是HitedObjectRectangle的子类实例。与其在子类中分别设置,不如在基类中一个地方统一设置;另外,基类中render方法渲染挡板时要使用x、y属性,x、y属性需要重写,这也要求rectangle必须定义在基类中定义。

看一下对LeftPanel类的改造,如代码清单11-11所示:

代码清单11-11改造LeftPanel类



1.// JS:src/views/left_panel.js
2....
3.import LeftPanelRectangle from "hitTest/left_panel_rectangle.js"
4.
5./** 左挡板 */
6.class LeftPanel extends Panel {
7.  constructor() {
8.    super()
9.    this.rectangle = new LeftPanelRectangle()
10.  }
11.
12.  ...
13.
14.  /** 小球碰撞到左挡板返回1 */
15.  testHitBall(ball) {
16.    return ball.rectangle.hitTest(this.rectangle)
17.    // if (ball.x < GameGlobal.RADIUS + GameGlobal.PANEL_WIDTH) { // 触达左挡板
18.    //   if (ball.y > this.y && ball.y < (this.y + GameGlobal.PANEL_HEIGHT)) {
19.    //     return 1
20.    //   }
21.    // }
22.    // return 0
23.  }
24.}
25.
26.export default new LeftPanel()


上面发生了什么?只有两处改动:


❑ 第9行,这里决定了基类中的rectangle是LeftPanelRectangle实例。LeftPanelRectangle是HitedObjectRectangle的子类。

❑ 第16行,碰撞检测代码修改为:由小球的rectangle与当前对象的rectangle做碰撞测试。

接下来是对RightPanel类的改写,如代码清单11-12所示:


代码清单11-12改造RightPanel类


1.// JS:src/views/right_panel.js
2....
3.import RightPanelRectangle from "hitTest/right_panel_rectangle.js"
4.
5./** 右挡板 */
6.class RightPanel extends Panel {
7.  constructor() {
8.    super()
9.    this.rectangle = new RightPanelRectangle()
10.  }
11.
12.  ...
13.
14.  /** 小球碰撞到左挡板返回2 */
15.  testHitBall(ball) {
16.    return ball.rectangle.hitTest(this.rectangle)
17.    // if (ball.x > (GameGlobal.CANVAS_WIDTH - GameGlobal.RADIUS - GameGlobal.PANEL_WIDTH)) { // 碰撞右挡板
18.    //   if (ball.y > this.y && ball.y < (this.y + GameGlobal.PANEL_HEIGHT)) {
19.    //     return 2
20.    //   }
21.    // }
22.    // return 0
23.  }
24.}
25.
26.export default new RightPanel()


与LeftPanel类似,在这个RightPanel类中也只有两处修改,见第9行与第16行。

最后,我们开始改造GameIndexPage,它是我们应用桥接模式的最后一站了,如代码清单11-13所示:


代码清单11-13改造游戏主页对象


1.// JS:src\views\game_index_page.js
2....
3.import ScreenRectangle from "hitTest/screen_rectangle.js"
4.
5./** 游戏主页页面 */
6.class GameIndexPage extends Page {
7.  ...
8.  /** 墙壁碰撞检测对象 */
9.  #rectangle = new ScreenRectangle()
10.
11.  ...
12.
13.  /** 运行 */
14.  run() {
15.    ...
16.    // 小球碰撞检测
17.    // ball.testHitWall()
18.    ball.testHitWall(this.#rectangle)
19.    ...
20.  }
21.
22.  ...
23.}
24.
export default GameIndexPage


在GameIndexPage类中,只有两处修改:


❑ 第9行,添加了一个私有属性#rectangle,它是一个碰撞检测数据对象,是HitedObjectRectangle的子类实例。

❑ 第18行,在调用小球的testHitWall方法,将#rectangle作为参数传递了进去。


现在代码修改完了,重新编译测试,运行效果与之前一致,如下所示。


image.png

目录
相关文章
|
11天前
|
设计模式 Java 机器人
Java设计模式之访问者模式详解
Java设计模式之访问者模式详解
|
7天前
|
设计模式 Java
设计模式在Java项目中的实际应用
设计模式在Java项目中的实际应用
|
6天前
|
设计模式 JavaScript
js设计模式【详解】—— 桥接模式
js设计模式【详解】—— 桥接模式
22 6
|
3天前
|
设计模式 算法 搜索推荐
Java中的设计模式及其在实际项目中的应用
Java中的设计模式及其在实际项目中的应用
|
12天前
|
设计模式 数据库连接 PHP
PHP中的面向对象编程与设计模式应用
传统的PHP编程模式在面向对象的趋势下逐渐演进,本文探讨了面向对象编程在PHP中的应用,并深入分析了常用的设计模式如何优化代码结构和可维护性。
|
12天前
|
设计模式
桥接模式-大话设计模式
桥接模式-大话设计模式
10 1
|
2天前
|
XML 关系型数据库 MySQL
支付系统----微信支付19---集成MyBatis-plus,数据库驱动对应的依赖版本设置问题,5没版本没有cj这个依赖,mysql驱动默认的是版本8,这里是一个父类,数据库都有,写个父类,继承就行
支付系统----微信支付19---集成MyBatis-plus,数据库驱动对应的依赖版本设置问题,5没版本没有cj这个依赖,mysql驱动默认的是版本8,这里是一个父类,数据库都有,写个父类,继承就行
|
3天前
|
缓存 小程序
【微信小程序-原生开发】启动时自动升级更新到最新版本
【微信小程序-原生开发】启动时自动升级更新到最新版本
7 0
|
5天前
|
设计模式 Java 开发者
Java中设计模式的应用与实现详解
Java中设计模式的应用与实现详解
|
6天前
|
设计模式 Java
设计模式在Java项目中的实际应用
设计模式在Java项目中的实际应用