Java 中的 Twin 模式:通过同步孪生类实现双倍功能
Twin 设计模式的意图
Java 中的 Twin 设计模式提供了一种处理多个相关类的方法,使它们能够协同工作,而无需继承自共同的基类。
Twin 模式的详细解释,以及实际应用案例
现实世界中的例子
Twin 设计模式在现实世界中的类似例子可以从驾驶员和驾驶模拟器之间的关系中找到。想象一个驾驶员(第一个类)和一个驾驶模拟器(第二个类),它们都需要与同一组车辆控制装置(转向、加速、制动)进行交互,并接收相同的反馈(速度、发动机状态)。
尽管执行类似的功能,但驾驶员和模拟器无法共享一个共同的基类,因为它们在根本上不同的环境中运行——一个在物理世界,另一个在虚拟环境中。相反,它们被“孪生”以确保与车辆控制和反馈机制的一致交互。这种设置允许对模拟器进行改进或更改,而不会影响驾驶员,反之亦然,从而保持系统的整体灵活性和弹性。
通俗地说
它提供了一种方法来形成两个紧密耦合的子类,它们可以作为具有两个端点的孪生类。
维基百科说
Twin 模式是一种软件设计模式,允许开发人员在不支持多重继承的语言中模拟多重继承。与其创建一个继承自多个父类的单个类,不如创建两个紧密相关的子类,每个子类都继承自一个父类。这些子类是相互依赖的,作为一个对来共同实现所需的功能。这种方法避免了与多重继承相关的复杂性和低效率,同时仍然允许重用来自不同类的功能。
Twin 模式在 Java 中的编程示例
考虑一个游戏,其中一个球需要同时充当 GameItem
和 Thread
。与其继承自两者,不如使用 Twin 模式,创建两个紧密相关的对象:BallItem
和 BallThread
。
这是 GameItem
类
@Slf4j
public abstract class GameItem {
public void draw() {
LOGGER.info("draw");
doDraw();
}
public abstract void doDraw();
public abstract void click();
}
BallItem
和 BallThread
子类
@Slf4j
public class BallItem extends GameItem {
private boolean isSuspended;
@Setter
private BallThread twin;
@Override
public void doDraw() {
LOGGER.info("doDraw");
}
public void move() {
LOGGER.info("move");
}
@Override
public void click() {
isSuspended = !isSuspended;
if (isSuspended) {
twin.suspendMe();
} else {
twin.resumeMe();
}
}
}
@Slf4j
public class BallThread extends Thread {
@Setter
private BallItem twin;
private volatile boolean isSuspended;
private volatile boolean isRunning = true;
public void run() {
while (isRunning) {
if (!isSuspended) {
twin.draw();
twin.move();
}
try {
Thread.sleep(250);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public void suspendMe() {
isSuspended = true;
LOGGER.info("Begin to suspend BallThread");
}
public void resumeMe() {
isSuspended = false;
LOGGER.info("Begin to resume BallThread");
}
public void stopMe() {
this.isRunning = false;
this.isSuspended = true;
}
}
要一起使用这些类
public class App {
public static void main(String[] args) throws Exception {
var ballItem = new BallItem();
var ballThread = new BallThread();
ballItem.setTwin(ballThread);
ballThread.setTwin(ballItem);
ballThread.start();
waiting();
ballItem.click();
waiting();
ballItem.click();
waiting();
// exit
ballThread.stopMe();
}
private static void waiting() throws Exception {
Thread.sleep(750);
}
}
让我们分解一下 App
中发生了什么。
- 创建了一个
BallItem
和BallThread
实例。 BallItem
和BallThread
实例被设置为彼此的孪生类。这意味着每个实例都包含对另一个实例的引用。BallThread
被启动。这开始执行BallThread
类中的run
方法,该方法不断调用BallItem
(它的孪生类)的draw
和move
方法,只要BallThread
没有被挂起。- 程序等待 750 毫秒。这是为了允许
BallThread
执行其run
方法几次。 - 调用
BallItem
的click
方法。这将切换BallItem
及其孪生类BallThread
的isSuspended
状态。如果BallThread
正在运行,则它会被挂起。如果它被挂起,则它将恢复运行。 - 步骤 4 和 5 重复两次。这意味着
BallThread
被挂起并恢复一次。 - 最后,调用
BallThread
的stopMe
方法来停止其执行。
控制台输出
14:29:33.778 [Thread-0] INFO com.iluwatar.twin.GameItem -- draw
14:29:33.780 [Thread-0] INFO com.iluwatar.twin.BallItem -- doDraw
14:29:33.780 [Thread-0] INFO com.iluwatar.twin.BallItem -- move
14:29:34.035 [Thread-0] INFO com.iluwatar.twin.GameItem -- draw
14:29:34.035 [Thread-0] INFO com.iluwatar.twin.BallItem -- doDraw
14:29:34.035 [Thread-0] INFO com.iluwatar.twin.BallItem -- move
14:29:34.291 [Thread-0] INFO com.iluwatar.twin.GameItem -- draw
14:29:34.291 [Thread-0] INFO com.iluwatar.twin.BallItem -- doDraw
14:29:34.291 [Thread-0] INFO com.iluwatar.twin.BallItem -- move
14:29:34.533 [main] INFO com.iluwatar.twin.BallThread -- Begin to suspend BallThread
14:29:35.285 [main] INFO com.iluwatar.twin.BallThread -- Begin to resume BallThread
14:29:35.308 [Thread-0] INFO com.iluwatar.twin.GameItem -- draw
14:29:35.308 [Thread-0] INFO com.iluwatar.twin.BallItem -- doDraw
14:29:35.308 [Thread-0] INFO com.iluwatar.twin.BallItem -- move
14:29:35.564 [Thread-0] INFO com.iluwatar.twin.GameItem -- draw
14:29:35.564 [Thread-0] INFO com.iluwatar.twin.BallItem -- doDraw
14:29:35.565 [Thread-0] INFO com.iluwatar.twin.BallItem -- move
14:29:35.817 [Thread-0] INFO com.iluwatar.twin.GameItem -- draw
14:29:35.817 [Thread-0] INFO com.iluwatar.twin.BallItem -- doDraw
14:29:35.817 [Thread-0] INFO com.iluwatar.twin.BallItem -- move
这种设置允许 BallItem
和 BallThread
在游戏中作为一个单一的凝聚单元共同行动,利用 GameItem
和 Thread
的功能,而无需多重继承。
何时在 Java 中使用 Twin 模式
- 当您需要解耦共享共同功能但由于各种原因(例如使用不同的框架或语言)无法继承自共同基类的类时,请使用。
- 在性能关键的应用程序中很有用,因为继承可能会引入不必要的开销。
- 适用于需要通过能够替换或更新一个孪生类而不影响另一个孪生类来实现弹性的系统。
Twin 模式 Java 教程
Twin 模式在 Java 中的实际应用
- 用户界面,其中不同的框架用于渲染和逻辑。
- 将遗留代码与新实现集成在一起的系统,其中直接继承不可行。
Twin 模式的优点和权衡
优点
- 减少了类之间的耦合,促进了模块化和更轻松的维护。
- 提高了跨不同框架或语言的类的灵活性和可重用性。
- 通过避免与继承相关的开销来提高性能。
权衡
- 如果管理不当,会导致代码重复。
- 管理孪生类之间交互的复杂性增加。
相关的 Java 设计模式
- 适配器:两种模式都处理兼容性问题,但适配器侧重于转换接口,而 Twin 侧重于类协作而无需继承。
- 桥接:在解耦抽象与实现方面类似,但 Twin 特别避免了继承。
- 代理:管理对象访问,类似于 Twin 处理交互的方式,但代理通常侧重于控制和日志记录。