如何从组件开始构建一座城市?

为什么越来越多的企业应用开发正在转向组件框架和解决方案?组件架构是否有前途?我相信答案是肯定的,而且很快所有开发框架都将会是基于组件的——这是近在眼前的事情。下面让我来向你揭示这一切的原因。

你怎么来建设你的房子?一般你会从砌块开始。我们可以将构建Web应用与构建你的乡间小屋进行对比。你能够快速构建一个非常好看的应用,而且它具有所有必需的功能。同样,在你的房子里面,每一间房间都是针对具体的需求来创建的,例如厨房、起居室、卧室或浴室。房子的布局使你能够通过走廊和楼梯很方便地在房间之间移动。

现在你能够做得更好,而且能够承担建设一座更大更好的房子的投入——你也许希望拥有桑拿房、游泳池、影院以及一座满是爬行动物的巨大的水族馆?。但要想改变房子的设计却是件非常困难的事情。若要添加额外的设施,房子最终看起来也许就不那么漂亮了。此外,由于你添加的这些设施必须放在不太方便的位置,它们也会影响房子使用的便利性,例如你必须穿过主卧室才能进入台球室。

最后,你那漂亮又整洁的房子将拥有一堆不同的功能,但它会变得笨拙又不舒适。同样的道理也适用于应用开发。

问题是,有没有可能设计一款应用,能够根据你的需求成长和改变?

组件是应用的积木式构件

组件是扩展应用功能的首要方法。创建组件的过程,与基于组件创建应用的过程<a name="_GoBack">有一些差异。组件不止应该提供有用的功能,还应该从一开始就设计成可复用的。

组件复用

组件应该采用松耦合方式设计以便于复用。为实现这一目标,不同的框架往往基于观察者模式实现其事件模型。该模式允许多个接收者订阅同一事件。

观察者模式的实现最早出现在Smalltalk中。Smalltalk是一个基于MVC的用户界面框架,现在它已经成为MVC框架的关键部分。我希望你能注意到,自Java 1.0版本起,观察者模式就已经在Java中存在。下面让我们深入了解它。

下面的UML图展现了观察者模式:

以下则是一段基本的Java实现:

public class ObservableX extends Observable {
  ...
  public void setAmount(double amount) {
    this.amount = amount;
    super.setChanged();
    super.notifyObservers();
}
}
  public class ObserverA implements Observer {
  public void public void update(Observable o) {
  // gets updated amount
}
}
  public class ObserverB implements Observer {
  public void public void update(Observable o) {
  // gets updated amount
}
}
//instantiate concrete observableX
ObservableX observableX = new ObservableX();
//somewhere in code
observableX.addObserver(new ObserverA());
observableX.addObserver(new ObserverB());
//much later

observableX.setAmount(amount);

它是这样工作的:

首先我们创建一个ObservableX类的实例,将ObserverA和ObserverB实例添加到observableX对象中,然后在代码中的某个位置,我们使用setAmount方法设定“一定数量”的值。被观察者(observable)类的功能将接收到的数量通知所有注册的观察者。

观察者担当着中介的角色,维持接收者的列表。当组件里有事件发生时,该事件将被发送到列表上的所有接收者。

由于这个中介角色的存在,组件并不知道其接收者。而接收者可以订阅某个特定类型的不同组件中的事件。

当一个类使用事件将自身的变化通知观察者时,该类就可以成为一个组件。而这可以通过观察者模式来实现。

使用组件比创建组件容易

通过使用组件,你能够快速创建各种窗体、面板、窗口以及界面中的其他合成元素。不过,为了能够复用新的由组件创建的合成部分,应该将它们转化为组件。

为了实现这一目标,你需要决定组件所要生成的外部事件,以及消息传递机制。例如,你至少需要创建新的事件类并且定义接口或回调方法以接收这些事件。

这个方式让实现可复用的应用组件变得更复杂。当系统只是由少量合成元素组成时没什么问题——这时合成元素最多不超过10个。然而当系统包含数以百计的此类元素时,又当如何?

与之相反,不遵从这一方式将导致元素间的紧耦合,并且会把复用的机会降低到0。这反过来会导致代码复制,从而让未来的代码维护变得更复杂,并将导致系统中的bug数量上升。

由于组件使用者往往不了解如何定义和传递他们自己的新事件,问题将变得更为严重。但他们可以轻松地使用组件框架提供的现成的事件。他们知道如何接收但不知道如何发送事件。

为了解决这个问题,让我们考虑如何简化应用中使用的事件模型。

太多的事件监听者

在Java Swing、GWT、JSF和Vaadin中,观察者模式被用于实现多用户能够订阅同一事件的模型,并将用于添加事件监听者的列表作为其实现方式。相关事件一旦发生,将被发送到列表上的全部接收者。

每个组件为一个或多个事件创建自己的事件监听者集合。这将导致应用中类的数量不断增多。反过来,这也会使系统的支持和开发变得更复杂。

借助注解机制(annotation),Java找到了一条让单个方法订阅特定事件的道路。例如,考虑Java EE 6里的CDI(Contexts and Dependency Injection,上下文和依赖注入)中对事件模型的实现。

public class PaymentHandler {
      public void creditPayment(@Observes @Credit PaymentEvent event) {
        ...
      }
}
public class PaymentBean {
    @Inject
    @Credit
    Event<<paymentevent> creditEvent;
   public String pay() {
     PaymentEvent creditPayload = new PaymentEvent();
            // populate payload ... 
            creditEvent.fire(creditPayload);
      }
}

你可以看到,当PaymentBean对象的pay()方法被调用时,PaymentEvent被触发。接下来PaymentHandler对象的creditPayment()方法接收了它。

另一个例子是Guava类库中事件总线的实现:

// Class is typically registered by the container.
class EventBusChangeRecorder {
  @Subscribe public void recordCustomerChange(ChangeEvent e) {
    recordChange(e.getChange());
  }
}
// somewhere during initialization
eventBus.register(new EventBusChangeRecorder());
// much later
public void changeCustomer() {
  ChangeEvent event = getChangeEvent();
  eventBus.post(event);
}

EventBus注册了EventBusChangeRecorder类的对象。接下来对changeCustomer()方法的调用会使EventBus接收ChangeEvent对象并调用EventBusChangeRecorde对象的recordCustomerChange ()方法。

现在你不需要为你的组件实现若干事件监听者,在应用中使用事件也变得更简单了。

当所有组件都同时在屏幕上展现时,使用事件总线是很方便的。如下图所示,它们使用事件总线进行消息交换。

这里,所有元素——标题、左侧的菜单、内容、右侧的面板——都是组件。

订阅事件——别忘记取消订阅

通过将事件监听者替换为注解,我们在简化事件模型使用的道路上前进了一大步。但即使如此,系统中的每个组件依旧需要连接到事件总线,然后,必须订阅上面的事件并在正确的时间取消订阅。

当相同的接收者多次订阅同一个事件时,将会出现许多重复提醒,这种情况很容易出现。而相似的情况还会在多个系统组件订阅同一事件时发生,这将会触发一系列级联事件。

为了能更好地控制事件模型,将工作与事件一起迁移到配置中,并让应用容器负责事件管理是很有意义的。由于特定的事件仅在特定条件下有效,将这些事件的状态管理迁移到配置中也是合理的。

下面是一段配置的例子:

<?xml version="1.0"?>
<application initial="A">
    <view id="A">
        <on event="next" to="B"/>
    </view>
    <view id="B">
        <on event="previous" to="A"/>
        <on event="next" to="C"/>
    </view>
    <view id="C">
        <on event="previous" to="B"/>
        <on event="next" to="D"/>
    </view>
    <view id="D">
        <on event="previous" to="C"/>
        <on event="finish" to="finish"/>
    </view>
    <final id="finish" /> 
</application>

视图A中的“下一个(next)”事件触发了向视图B的转变。在视图B中,用户可以通过“前一个(previous)”事件回到A,或是通过“下一个(next)”事件进入C。D视图中的结束事件将转入“最终(final)”状态,将通知应用结束其中的工作流。

有限状态机是专为这样的需求设计的。状态机是一种数学计算模型。它被设想为一种抽象的机器,可以处于有限数量的状态中的一个,并且在同一时间里只会处于一个状态——这被称为当前状态。事件或条件将触发向另一个状态的转变。使用这一方式,你能够轻松地定义活动画面,并让某事件来触发向另一个画面的转变。

使用有限状态机来配置应用的好处

大部分情况下,应用配置是静态定义的。使用依赖注入配置应用,我们在启动时定义应用结构。但我们忘记了在探索应用的过程中它的状态可能会改变。在应用的代码中,状态改变往往属于硬编码,它让未来的调整和维护变得复杂。

将状态间的转变迁移到配置中可以提高灵活性。而且这正是为什么我们在创建诸如窗体、窗口或面板等复杂应用元素时,无需为了应用应该进入哪个状态而操心。你可以稍后来处理它们,在配置中设定其行为。

所有组件都可以使用标准的事件发送机制进行交流——即通过事件总线。同时,状态机能够控制组件事件到事件总线的订阅。这一方式将应用的全部组件(窗体、窗口、面板)变为可复用组件,可以通过外部配置轻松地管理它们。

如果有兴趣,你可以看一下Enterprise Sampler中一些配置的例子。

你也可以将状态配置看作城市的公路图,把事件看作递送商品的汽车,而将城市里的人看作目的地。

我确信采用这样的方式,不仅能够轻松地设计和构建一间规模虽小却做好了成长准备的房子,还能够建设拥有摩天大楼、公路和高速公路的城市。

   
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1 目标检测的定义 目标检测(Object Detection)的任务是找出图像中所有感兴趣的目标(物体),确定它们的类别和位置,是计算机视觉领域的核心问题之一。由于各类物体有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具有挑战性的问题。 目标检测任务可分为两个关键的子任务,目标定位和目标分类。首先检测图像中目标的位置(目标定位),然后给出每个目标的具体类别(目标分类)。输出结果是一个边界框(称为Bounding-box,一般形式为(x1,y1,x2,y2),表示框的左上角坐标和右下角坐标),一个置信度分数(Confidence Score),表示边界框中是否包含检测对象的概率和各个类别的概率(首先得到类别概率,经过Softmax可得到类别标签)。 1.1 Two stage方法 目前主流的基于深度学习的目标检测算法主要分为两类:Two stage和One stage。Two stage方法将目标检测过程分为两个阶段。第一个阶段是 Region Proposal 生成阶段,主要用于生成潜在的目标候选框(Bounding-box proposals)。这个阶段通常使用卷积神经网络(CNN)从输入图像中提取特征,然后通过一些技巧(如选择性搜索)来生成候选框。第二个阶段是分类和位置精修阶段,将第一个阶段生成的候选框输入到另一个 CNN 中进行分类,并根据分类结果对候选框的位置进行微调。Two stage 方法的优点是准确度较高,缺点是速度相对较慢。 常见Tow stage目标检测算法有:R-CNN系列、SPPNet等。 1.2 One stage方法 One stage方法直接利用模型提取特征值,并利用这些特征值进行目标的分类和定位,不需要生成Region Proposal。这种方法的优点是速度快,因为省略了Region Proposal生成的过程。One stage方法的缺点是准确度相对较低,因为它没有对潜在的目标进行预先筛选。 常见的One stage目标检测算法有:YOLO系列、SSD系列和RetinaNet等。 2 常见名词解释 2.1 NMS(Non-Maximum Suppression) 目标检测模型一般会给出目标的多个预测边界框,对成百上千的预测边界框都进行调整肯定是不可行的,需要对这些结果先进行一个大体的挑选。NMS称为非极大值抑制,作用是从众多预测边界框中挑选出最具代表性的结果,这样可以加快算法效率,其主要流程如下: 设定一个置信度分数阈值,将置信度分数小于阈值的直接过滤掉 将剩下框的置信度分数从大到小排序,选中值最大的框 遍历其余的框,如果和当前框的重叠面积(IOU)大于设定的阈值(一般为0.7),就将框删除(超过设定阈值,认为两个框的里面的物体属于同一个类别) 从未处理的框中继续选一个置信度分数最大的,重复上述过程,直至所有框处理完毕 2.2 IoU(Intersection over Union) 定义了两个边界框的重叠度,当预测边界框和真实边界框差异很小时,或重叠度很大时,表示模型产生的预测边界框很准确。边界框A、B的IOU计算公式为: 2.3 mAP(mean Average Precision) mAP即均值平均精度,是评估目标检测模型效果的最重要指标,这个值介于0到1之间,且越大越好。mAP是AP(Average Precision)的平均值,那么首先需要了解AP的概念。想要了解AP的概念,还要首先了解目标检测中Precision和Recall的概念。 首先我们设置置信度阈值(Confidence Threshold)和IoU阈值(一般设置为0.5,也会衡量0.75以及0.9的mAP值): 当一个预测边界框被认为是True Positive(TP)时,需要同时满足下面三个条件: Confidence Score > Confidence Threshold 预测类别匹配真实值(Ground truth)的类别 预测边界框的IoU大于设定的IoU阈值 不满足条件2或条件3,则认为是False Positive(FP)。当对应同一个真值有多个预测结果时,只有最高置信度分数的预测结果被认为是True Positive,其余被认为是False Positive。 Precision和Recall的概念如下图所示: Precision表示TP与预测边界框数量的比值 Recall表示TP与真实边界框数量的比值 改变不同的置信度阈值,可以获得多组Precision和Recall,Recall放X轴,Precision放Y轴,可以画出一个Precision-Recall曲线,简称P-R
图像识别技术在病虫害检测中的应用是一个快速发展的领域,它结合了计算机视觉和机器学习算法来自动识别和分类植物上的病虫害。以下是这一技术的一些关键步骤和组成部分: 1. **数据收集**:首先需要收集大量的植物图像数据,这些数据包括健康植物的图像以及受不同病虫害影响的植物图像。 2. **图像预处理**:对收集到的图像进行处理,以提高后续分析的准确性。这可能包括调整亮度、对比度、去噪、裁剪、缩放等。 3. **特征提取**:从图像中提取有助于识别病虫害的特征。这些特征可能包括颜色、纹理、形状、边缘等。 4. **模型训练**:使用机器学习算法(如支持向量机、随机森林、卷积神经网络等)来训练模型。训练过程中,算法会学习如何根据提取的特征来识别不同的病虫害。 5. **模型验证和测试**:在独立的测试集上验证模型的性能,以确保其准确性和泛化能力。 6. **部署和应用**:将训练好的模型部署到实际的病虫害检测系统中,可以是移动应用、网页服务或集成到智能农业设备中。 7. **实时监测**:在实际应用中,系统可以实时接收植物图像,并快速给出病虫害的检测结果。 8. **持续学习**:随着时间的推移,系统可以不断学习新的病虫害样本,以提高其识别能力。 9. **用户界面**:为了方便用户使用,通常会有一个用户友好的界面,显示检测结果,并提供进一步的指导或建议。 这项技术的优势在于它可以快速、准确地识别出病虫害,甚至在早期阶段就能发现问题,从而及时采取措施。此外,它还可以减少对化学农药的依赖,支持可持续农业发展。随着技术的不断进步,图像识别在病虫害检测中的应用将越来越广泛。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值