您的位置:首页 > 其它

耦合与内聚的应用 —— 事件驱动模式与解耦

2014-07-21 22:03 716 查看

关于解耦

解耦的概念很早就有,也知道什么叫解耦,但一直没有进行一些系统的学习,缺乏思考,很难准确的说出为何解耦、如何解耦以及解耦的应用场景。

前两天在看《架构之美》这本书时突然看到了关于解耦与内聚的描述,想要系统的了解一下却发现网上相关的总结与资料甚少,于是激发了我想要写一些关于解耦应用、提高内聚相关的知识总结。

本人知识面有限所以就简单的定了一个《耦合与内聚的应用》这样的题目,如果内容有不妥的地方望各位看官指点。

关于事件驱动模式

事件驱动模式其实接触的很早,最开始学习Java Swing时就已经接触到事件的概念,之后在做ExtJS开发的时候事件驱动也被大量运用于框架以及自定义组件中。

本文主要内容为解耦,所以将讲述该模式的解耦使用场景,具体的实现也将不在此累述。目前有 Google 的 Guava 框架有现成的事件模型代码,本文也将以此为基础作讲解。如有不知道的同学可以自行找度娘问一下。

解耦场景

谈到耦合性必然聊及内聚性,如果需要脑补的同学也可先前往脑补,一般较低耦合的情况下必然各组件的内聚性较高。

下面思考这样一个场景:我们需要实现一款下载软件,不光需要实现下载,同时需要对下载过程进行实时反馈,提供给使用者查看,不光要在主要界面上查看,还需要在桌面小图标上显示(这个场景也很常见,如果不理解可以参照迅雷、Flashget等下载软件的功能)。

从传统的多层架构来说,我们并不希望下载功能的代码与显示内容的代码有任何关联,一旦出现两个功能的代码出现关联,显示内容的任何修改都将影响下载功能的代码,这将导致软件可维护性变得非常低。下面我来看一下这个下载功能的最基础的实现代码。

public class DownloadComponent {

public void download(String url) throws Exception {
// 这里下载的代码。
InputStream is = new URL(url).openConnection().getInputStream();
int total = is.available(), data = -1, current = 1;
while ((data = is.read()) != -1) {
// 这里是界面用的代码
System.out.println("current / total = " + (current++) + "/" + total);
}
}


解耦方法

上面只是一些简单的实例代码,用于模拟一个下载的场景,真实的下载器会更加复杂。如果我们需要将打印语句处的变化为显示百分比,参数定义都会需要修改。两个功能的内聚性非常高,修改起来非常困难。那么我需要对这个代码进行一些改造。我们默认采用Guava工具包作为事件总线的实现,当然你也可以使用JavaSDK自带的EventListenerList自行实现一套事件驱动功能。

我们首先将两个功能独立开,创建两个组件,一个叫DownloadView,一个叫DownloadComponent。并分别提供两个方法,一个用于显示,一个用于下载。

public class DownloadView {

public void display(int current, int total, int data) {
System.out.println("current / total = " + (current++) + "/" + total);
}

}


DownloadView:将决定如何显示current、total、data的内容。

public class DownloadComponent {

private EventBus bus;

public DownloadComponent(EventBus bus) {
this.bus = bus;
}

public void download(String url) throws Exception {
InputStream is = new URL(url).openConnection().getInputStream();
int total = is.available(), data = -1, current = 1;
while ((data = is.read()) != -1) {
bus.post(new DownloadEvent(current++, total, data));
}
}

}


DownloadComponent:将决定如何下载,并通过EventBus进行解耦,该DownloadComponent将不实现如何展示data、current、total参数,并通过DownloadEvent将参数传递出去,具体由谁在处理、如何处理,DownloadComponent是不清楚的。这样提高了DownloadComponent的内聚性,明确了该组件功能的单一性。

注意DownloadEvent事件类型,该对象将作为一个消息对象在两个组件中传递。但消息对象其实并没有完全解除DownloadComponent与DownloadView的耦合(也有朋友通过Map进行消息传递,其实该方法也无法完全解除耦合),实质上DownloadView还是会对DownloadComponent有一定程度上的耦合,但是已经降至非常低可以忽略。

public class DownloadEvent {

private int current;
private int total;
private int data;

public DownloadEvent(int current, int total, int data) {
this.current = current;
this.total = total;
this.data = data;
}

public int getCurrent() {
return current;
}

public int getTotal() {
return total;
}

public int getData() {
return data;
}
}


完成了消息对象,我们需要定义一个处理消息对象的处理器,用来连接DownloadView与DownloadEvent之间的关系。

public class DownloadViewSubscriber {

private DownloadView view;

public DownloadViewSubscriber(DownloadView view) {
this.view = view;
}

@Subscribe
public void displayView(DownloadEvent event) {
view.display(event.getCurrent(), event.getTotal(), event.getData());
}

}


最后提供main调用方法。

public static void main(String[] args) throws Exception {
// 创建代码一般交给 IOC 管理。
EventBus bus = new EventBus();
DownloadComponent component = new DownloadComponent(bus);// 下载组件。
DownloadView view = new DownloadView();// 显示组件
DownloadViewSubscriber subscriber = new DownloadViewSubscriber(view);
bus.register(subscriber);// 注册监听者。
component.download("http://baidu.com");
}


平时我们更多的查看设计模式时,并没有太多的关注具体的应用场景,本文提供了一种解耦的方式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: