效果图
特效设计(Processing)为什么也需要设计模式?
什么是设计模式?设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计程序精英们的经验总结
为什么使用设计模式?使用设计模式是为了可重用代码(不修改或者少修改代码来解决新的问题),减少重复工作、让代码更容易被他人理解、保证代码可靠性。
设计模式是编程过程中的一种中间层面的技巧或者经验(微观层面的如算法、数据结构、语法,宏观层面的如框架、工具箱、系统架构),是许多优秀的程序员通过大量实际项目的磨练所总结出来的 “内功秘籍”, 通过学习他们的高效的方法、总结出经验技巧,那么在我们遇到一个具体问题时,也更加容易想到优秀合理的解决方案。
什么是适配器模式
在开发过程中,使用一个已经存在的类,而他的接口不符合我们的需求。这个时候我们本着开闭原则,要创建一个既符合我们需求又实现了已存在的接口的类,这个类可以把其他不相关或不可预见的类协同起来一起工作。我们创建的这个类就是适配器类,起到了一个转换的作用。
完整代码和解释
假设我们原有一个接口的定义 IMove.java
public interface IMove {
void move();
}
但是它不满足我们当前的需求(直接修改的话,违反了开闭原则),因为我们想增加一个缓动的 Move 函数,而刚好隔壁有一个定义好的缓动的接口和具体的实现类,如下
缓动接口 IEase.java
interface IEase {
float ease(float x);
}
具体实现了该接口的类 EasingEaseInOutQuint.java
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;
}
}
那么我们该如何将它们适配起来呢?我们接着往下看
我们的胶囊体类(适配器类)的定义,注意其中注入了缓动类的适配对象,通过它我们实现了 easeMove (间接实现了 IEase 接口)
import processing.core.*;
class Capsule implements IMove{
static PApplet app = null;
public float x;
public float y;
public float prevX;
public float prevY;
public float destX;
public float destY;
public float diameter;
public float speed;// 0.3
/// @note Adapter 对象
public EasingEaseInOutQuint ioquint;
Capsule(PApplet papp) {
if (app == null)
app = papp;
this.x = app.random(app.width);
this.y = app.random(app.height);
this.prevX = this.x;
this.prevY = this.y;
this.destX = this.x;
this.destY = this.y;
this.diameter = app.random(10f, 30f);
this.speed = 1 - (this.diameter / 40);// 0.3
}
public void move() {
/// @note 之前的位置逐渐变成当前的位置
this.prevX += (this.x - this.prevX) * (this.speed / 2); ///< 速度慢一点
this.prevY += (this.y - this.prevY) * (this.speed / 2);
/// @note 当前的位置逐渐变成目标的位置
this.x += (this.destX - this.x) * this.speed;
this.y += (this.destY - this.y) * this.speed;
};
/// @note 适配缓动函数
public void moveEase(float t) {
this.prevX += (this.x - this.prevX) * ioquint.ease(t) * this.speed/2; ///< 速度慢一点
this.prevY += (this.y - this.prevY) * ioquint.ease(t) * this.speed/2;
this.x += (this.destX - this.x) * ioquint.ease(t) * this.speed;
this.y += (this.destY - this.y) * ioquint.ease(t) * this.speed;
}
void display() {
float midX = (this.x + this.prevX) / 2;
float midY = (this.y + this.prevY) / 2;
float d = app.dist(this.x, this.y, this.prevX, this.prevY);
float angle = (float)Math.atan2(
this.prevY - this.y,
this.prevX - this.x);
/// @note 之前位置
app.ellipse(this.prevX, this.prevY, this.diameter, this.diameter);
/// @note 画一个矩形连接这两个位置
app.push();
app.translate(midX, midY);
app.rotate(angle);
app.rect(-d / 2, -this.diameter / 2, d, this.diameter);
app.pop();
/// @note 当前位置
app.ellipse(this.x, this.y, this.diameter, this.diameter);
};
}
那么客户端则可以这么调用
ArrayList<Capsule> peoples = new ArrayList<Capsule>();
int totalPeople = 30;
int tiktok = 0;
void setup() {
size(640, 480);
noStroke();
//frameRate(10);
EasingEaseInOutQuint ioquint = new EasingEaseInOutQuint(this);
for(int i=0; i<totalPeople; i++){
Capsule cap = new Capsule(this);
cap.ioquint = ioquint;
peoples.add(cap);
}
}
void draw() {
background(0);
for(int i=0; i<totalPeople; i++){
//peoples.get(i).move(); ///< 原提供的接口
peoples.get(i).moveEase((tiktok++ % 100)/ 100.f); ///< 新适配的接口
peoples.get(i).display();
}
}
void mouseClicked() {
tiktok = 0;
for(int i=0; i<totalPeople; i++){
//peoples.get(i).destX = mouseX + random(-320, 320);
//peoples.get(i).destY = mouseY + random(-240, 240);
peoples.get(i).destX = mouseX + random(-160, 160);
peoples.get(i).destY = mouseY + random(-120, 120);
}
}
总结
但是,适配器模式是一种没有办法的办法。如果可能的话,尽量在程序设计之初就考虑到这样的问题,编写出一种更合理的继承关系。或者在程序完成度还不高的时候,及时重构。只有在不得已的情况下(例如程序编写已经进入后期,重构代价会很大)才会考虑适配器模式。适配器模式另外一种用途,就是统一外部API的接口。