这是我参与8月更文挑战的第4天,活动详情查看: 8月更文挑战
什么是设计模式
什么是设计模式?设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计程序精英们的经验总结
为什么使用设计模式?使用设计模式是为了可重用代码(不修改或者少修改代码来解决新的问题),减少重复工作、让代码更容易被他人理解、保证代码可靠性。
设计模式是编程过程中的一种中间层面的技巧或者经验(微观层面的如算法、数据结构、语法,宏观层面的如框架、工具箱、系统架构),是许多优秀的程序员通过大量实际项目的磨练所总结出来的 “内功秘籍”, 通过学习他们的高效的方法、总结出经验技巧,那么在我们遇到一个具体问题时,也更加容易想到优秀合理的解决方案。
什么是桥接模式
桥接模式(Bridge Pattern)是属于结构型的设计模式,它将抽象部分与它的实现部分相分离,使它们都可以独立地变化。
-
Abstraction(抽象类):用于定义抽象类的接口,并且维护一个指向 Implementor 实现类的指针。它与 Implementor 之间具有关联关系(本立的 Dot.java)。
-
RefinedAbstraction(扩充抽象类):扩充由 Abstraction 定义的接口,在 RefinedAbstraction 中可以调用在 Implementor 中定义的业务方法(本例的 SphereDot.java)。
-
Implementor(实现类接口):定义实现类的接口,这个接口不一定要与 Abstraction 的接口完全一致,事实上这两个接口可以完全不同(本例的 IEase.java)。
-
ConcreteImplementor(具体实现类):实现了 Implementor 定义的接口,在不同的 ConcreteImplementor 中提供基本操作的不同实现。在程序运行时,ConcreteImplementor 对象将替换其父类对象,提供给 Abstraction 具体的业务操作方法(本例的各种缓动类)。
具体实例和代码注释
Bridge 接口的定义 IEase.java
interface IEase {
float ease(float x);
}
接着就是一些缓动函数类(*.Java)的定义
import processing.core.*;
public class EasingEaseInOutCubic implements IEase {
static PApplet app;
public EasingEaseInOutCubic(PApplet papp) {
app = papp;
}
public float ease(float x) {
if (x < 0.5f)
return 0.5f * app.pow(2f * x, 3f);
else
return 0.5f * app.pow(2f * (x - 1f), 3f) + 1f;
}
}
import processing.core.*;
public class EasingEaseInOutQuint implements IEase {
static PApplet app;
public EasingEaseInOutQuint(PApplet papp) {
app = papp;
}
public float ease(float x) {
return x<.5f ? 16f*x*x*x*x*x : 1f+16f*(--x)*x*x*x*x;
}
}
抽象 Dot 类的定义,注意我们里头定义了桥接器 Bridge 的成员变量 iease,在真正调用的时候它会负责桥接具体的缓动函数
public abstract class Dot {
static PApplet app;
float center;
PVector pos;
float velocity;
float counter;
float size;
float step;
float ease;
boolean organize;
/// @note Bridge
IEase iease;
Dot(PApplet papp) {
app = papp;
}
void setup(float c) {
counter = app.random(1000);
this.center = c;
pos = new PVector(center, center, center);
velocity = app.random(0.005f, 0.01f);
step = app.random(1000.f);
ease = 0.06f;
organize = false;
}
void update() {
float width = app.width;
float height = app.height;
if (organize) {
pos.x += ((app.round(pos.x / width * 10) * width / 10) - pos.x) * velocity;
pos.y += ((app.round(pos.y / height * 10) * height / 10) - pos.y) * ease;
pos.z += ((app.round(pos.z / height * 10) * height / 10) - pos.z) * ease;
} else {
counter += velocity;
pos.x += ((app.noise(counter * 0.5f + step) - 0.25f) * width * 2 - pos.x) * ease;
pos.y += ((app.noise(counter * 0.3f + step) - 0.25f) * height * 2 - pos.y) * ease;
pos.z += ((app.noise(counter * 0.1f + step) - 0.25f) * height * 2 - pos.z) * ease;
}
size = app.map(pos.z / width, 0.0f, 0.5f, 3f, 10f);
}
abstract void draw();
}
具体的 Dot 类的定义,这里是球形为例,它主要实现了基类中未实现的 draw 方法
import processing.core.*;
public class SphereDot extends Dot {
public float prevRotateAngle = 0.f;
public float destRotateAngle = 0.f;
public float rotateAngle = 0.f;
public float changeTime = 60.f;
public float lastChangeFrameCount = 0.f;
SphereDot(PApplet papp) {
super(papp);
}
void draw() {
float frameCount = app.frameCount;
boolean tempOrganize = frameCount % (changeTime * 2) > changeTime;
if (organize != tempOrganize) {
lastChangeFrameCount = frameCount;
organize = tempOrganize;
prevRotateAngle = rotateAngle;
destRotateAngle += app.PI / 2;
}
app.push();
app.translate(center, center, center);
float easeLerp = iease.ease((frameCount - lastChangeFrameCount) / changeTime);
rotateAngle = app.lerp(prevRotateAngle, destRotateAngle, easeLerp);
app.rotateY(rotateAngle);
prevRotateAngle = rotateAngle;
destRotateAngle += app.PI / 2;
app.translate(-center, -center, -center);
app.pop();
app.push();
app.translate(pos.x, pos.y, pos.z);
app.sphere(size);
app.pop();
}
}
这样一来,我们的客户端代码将非常的清爽
ArrayList<Dot> dots = new ArrayList<Dot>();
void setup() {
size(512, 512, P3D);
float center = width/2;
ortho(-center, center, -center, center, 0, 1000);
int totalDots = 100;
for(int i=0; i<totalDots; i++){
Dot dot = new SphereDot(this);
/// @note 切换不同的缓动类
//dot.iease = new EasingEaseInCubic(this);
//dot.iease = new EasingEaseOutCubic(this);
//dot.iease = new EasingEaseInOutCubic(this);
dot.iease = new EasingEaseInOutQuint(this);
dot.setup(center);
dots.add(dot);
}
noStroke();
//fill(255);
frameRate(30);
}
void draw() {
background(0);
for(int i=0; i<dots.size(); i++){
dots.get(i).update();
dots.get(i).draw();
}
}
通过桥接模式也很容易修改为方形粒子的效果 ,直接继承 Dot 类得到 BoxDot 类,然后还可以根据自己的喜好选择合适的缓动函数类,并不会影响(修改)之前定义的那些类【开放封闭原则】
效果图
球形粒子的缓动特效
-
EasingEaseInCubic
-
EasingEaseOutCubic
- EasingEaseInOutCubic
- EasingEaseInOutQuint(修改缓动函数的同时,改为方形粒子)
桥接模式的优缺点
优点:
-
分离抽象和实现部分。桥接模式使用“对象间的关联关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。即抽象和实现不再在同一个继承层次结构中,而是“子类化”它们,使它们各自都具有自己的子类,以便可以进行任意组合,从而获得多维度的组合对象。
-
在很多情况下,桥接模式可以取代多层继承方案(Java 是单继承,C++ 是支持多继承)。多层继承违背了“单一职责原则”,复用性较差,且类的个数非常多。所以相比起来,桥接模式更好,它极大地减少了子类的个数。
-
提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合“开闭原则”。
缺点:
-
增加了系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程。
-
需要能正确识别出系统中两个独立变化的维度,因此使用范围具有一定的局限性,如何正确识别两个独立维度也需要一定的经验积累。