Processing 也需要设计模式—— 适配器模式【Java】|8月更文挑战

1,107 阅读3分钟

效果图

彩色效果

黑白效果

特效设计(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的接口。