工厂模式
我们将使用FunctionFactory作为GeoTools插件系统的介绍。
在体系结构中,我们看到了核心GeoTools库和提供功能的插件之间的区别。使插件工作的一个因素是“工厂SPI”插件系统(这实际上是Java的一部分,而不是我们编造的)。
参考:
- http://download.oracle.com/javase/tutorial/sound/SPI-intro.html
- http://c2.com/cgi/wiki?ObserverPattern
- http://gsraj.tripod.com/design/creational/factory/factory.html
- http://www.eclipse.org/articles/Article-Plug-in-architecture/plugin_architecture.html
回顾
每个插件jar有:
- meta - inf / services文件夹
该文件夹包含文件列表(每个接口名一个) - 这些文件包含实现该接口的类的列表
这一页是我们解释这一切如何运作的地方。
GeoTools使用Factory和FactoryRegistry类进行扩展。标准的工厂模式为我们提供了关于将要发生什么的线索:
工厂模式工厂是创建其他对象的对象。
这就是乐趣开始的地方……
步骤1的接口
第一步是定义你的接口;在本例中,我们将使用GeoTools已经提供的函数接口。
image.png
GeoTools喜欢使用接口,这就是为什么(这是创建基于插件的库的第一步)。
我们将以函数为例进行讨论:
public interface Function extends Expression {
String getName();
List<Expression> getParameters();
Literal getFallbackValue();
}
public interface Expression {
Object evaluate(Object object);
<T> T evaluate(Object object, Class<T> context);
Object accept(ExpressionVisitor visitor, Object extraData);
}
1.下面是一个快速实现从一个点到一行:
/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2019, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
*/
package org.geotools.tutorial.function;
import java.util.List;
import org.geotools.filter.capability.FunctionNameImpl;
import org.geotools.util.Converters;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.linearref.LinearLocation;
import org.locationtech.jts.linearref.LocationIndexedLine;
import org.opengis.filter.capability.FunctionName;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.ExpressionVisitor;
import org.opengis.filter.expression.Function;
import org.opengis.filter.expression.Literal;
/**
* Quick function that illustrates snapping to a line.
*
* @author jody
*/
public class SnapFunction implements Function {
static FunctionName NAME =
new FunctionNameImpl(
"snap",
Point.class,
FunctionNameImpl.parameter("point", Point.class),
FunctionNameImpl.parameter("line", Geometry.class));
private final List<Expression> parameters;
private final Literal fallback;
public SnapFunction(List<Expression> parameters, Literal fallback) {
if (parameters == null) {
throw new NullPointerException("parameters required");
}
if (parameters.size() != 2) {
throw new IllegalArgumentException("snap( point, line) requires two parameters only");
}
this.parameters = parameters;
this.fallback = fallback;
}
public Object evaluate(Object object) {
return evaluate(object, Point.class);
}
public <T> T evaluate(Object object, Class<T> context) {
Expression pointExpression = parameters.get(0);
Point point = pointExpression.evaluate(object, Point.class);
Expression lineExpression = parameters.get(1);
Geometry line = lineExpression.evaluate(object, Geometry.class);
LocationIndexedLine index = new LocationIndexedLine(line);
LinearLocation location = index.project(point.getCoordinate());
Coordinate snap = index.extractPoint(location);
Point pt = point.getFactory().createPoint(snap);
return Converters.convert(pt, context); // convert to requested format
}
public Object accept(ExpressionVisitor visitor, Object extraData) {
return visitor.visit(this, extraData);
}
public String getName() {
return NAME.getName();
}
public FunctionName getFunctionName() {
return NAME;
}
public List<Expression> getParameters() {
return parameters;
}
public Literal getFallbackValue() {
return fallback;
}
}
2.如果您感兴趣,可以在“从点到线”单元中了解使用LocationIndexLine的机制。
- 需要注意的一点是,FunctionName的定义用于向新函数的用户描述有效的参数。
按照惯例,我们将其定义为一个静态的final SnapFunction。但是,这只是一个约定(它将有助于实现下一节)。
步骤2工厂
接口不允许有构造函数,因此任何构造函数都被定义为工厂接口。
继续我们的例子:
public interface FunctionFactory {
List<FunctionName> getFunctionNames();
Function function(String name, List<Expression> args, Literal fallback);
}
上面的工厂描述了哪些函数可用,并允许创建函数。到目前为止一切正常。以上就是普通的“工厂模式”通常的工作方式—希望您熟悉它?
为了继续我们的实现,我们将定义ExampleFunctionFactory:
1.创建实现FunctionFactory的ExampleFunctionFactory
2.填写以下资料:
/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2019, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
*/
package org.geotools.tutorial.function;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.geotools.feature.NameImpl;
import org.geotools.filter.FunctionFactory;
import org.opengis.feature.type.Name;
import org.opengis.filter.capability.FunctionName;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Function;
import org.opengis.filter.expression.Literal;
public class ExampleFunctionFactory implements FunctionFactory {
public List<FunctionName> getFunctionNames() {
List<FunctionName> functionList = new ArrayList<>();
functionList.add(SnapFunction.NAME);
return Collections.unmodifiableList(functionList);
}
public Function function(String name, List<Expression> args, Literal fallback) {
return function(new NameImpl(name), args, fallback);
}
public Function function(Name name, List<Expression> args, Literal fallback) {
if (SnapFunction.NAME.getFunctionName().equals(name)) {
return new SnapFunction(args, fallback);
}
return null; // we do not implement that function
}
}
3.我们引用静态的final SnapFunction.NAME。
虽然我们提到这只是一个约定,但是您可以自由地创建一个新的FunctionNameImpl(“snap”、“point”、“line”)作为getFunctionNames()方法的一部分。这样做的好处是避免在用户按名称请求SnapFunction之前加载它。
4.我们现在可以注册我们的工厂了。
创建文件:META_INF/services/org.geotools.filter.FunctionFactory
5.填写以下内容(每行一个实现类):
org.geotools.tutorial.function.ExampleFunctionFactory
6.那就是SnapFunction现在发布了!
步骤3 FactoryRegistry
GeoTools 2.2使用javax.imageio.ServiceRegistry魔力(此插件系统起源的地方)。请注意,FactoryRegistry将缓存已经找到的工厂。由于工厂是无状态的,这应该不是问题。
直接使用FactoryRegistry
1.你可以直接使用FactoryRegistry在你自己的代码:
Set categories = Collections.singleton(new Class[] {FunctionFactory.class,});
FactoryRegistry registry = new FactoryRegistry(categories);
Iterator iterator = registry.getProviders(FunctionFactory.class);
2.在内部,FactoryRegistry将在系统属性中查找键。
- 如果密钥不存在或抛出SecurityException,则失败。
- 否则,尝试实例化给定的类。
- 然后FactoryRegistry将搜索元inf /services中的键的资源路径。
- 如果找到资源,就读取文件并实例化类。
- 如果资源不存在,则失败。
- 这意味着FactoryRegistry将能够找到类路径中提供的任何FunctionFactory。
定义自己的FactoryFinder
注意,FactoryRegistry不是同步的,为了保护这一点,您可以在FactoryFinder中包装直接使用,它还提供了类型安全性。
下面是使用FactoryRegistry作为FactoryFinder的一部分:
- 以惰性的方式创建FactoryRegistry,列出您感兴趣的接口(称为类别)。
- GeoTools通常在“Finder”类中保存一个FactoryRegistry:
- 创建ExampleFinder
- 填写以下资料:
/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2019, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
*/
package org.geotools.tutorial.function;
import java.util.Set;
import java.util.stream.Stream;
import org.geotools.filter.FunctionFactory;
import org.geotools.util.LazySet;
import org.geotools.util.factory.FactoryCreator;
import org.geotools.util.factory.FactoryFinder;
import org.geotools.util.factory.FactoryRegistry;
import org.geotools.util.factory.Hints;
import org.opengis.filter.FilterFactory;
public class ExampleFinder extends FactoryFinder {
private static volatile FactoryCreator registry;
private static FactoryRegistry getServiceRegistry() {
assert Thread.holdsLock(ExampleFinder.class);
if (registry == null) {
Class<?> categories[] = new Class<?>[] {FunctionFactory.class};
registry = new FactoryCreator(categories);
}
return registry;
}
/**
* Returns a set of all available implementations for the {@link FilterFactory} interface.
*
* @param hints An optional map of hints, or {@code null} if none.
* @return Set of available filter factory implementations.
*/
public static synchronized Set<FunctionFactory> getFilterFactories(Hints hints) {
hints = mergeSystemHints(hints);
Stream<FunctionFactory> serviceProviders =
getServiceRegistry().getFactories(FunctionFactory.class, null, hints);
return new LazySet<>(serviceProviders);
}
/** Allow the classpath to be rescanned */
public static synchronized void scanForPlugins() {
if (registry != null) {
registry.scanForPlugins();
}
}
}
- 以上只是一个例子,请使用FunctionFinder
实现自己的FactoryFinder的提示:
- 代码示例使用了LazySet,这使我们不必每次都检查类路径。
- 实用程序方法addDefaultHints用于将全局地理工具配置应用于用户提供的提示。
- 如上所示,您可以为客户端代码添加一些帮助方法。这通常用于根据某些条件执行搜索,或用于定位给定任务的“最佳”工厂。
FactoryIteratorProviders
factoryiteratorprovider用于支持其他插件机制。
默认情况下,“工厂SPI”机制用于定位FactoryFinder(和FactoryRegistry)提供的工厂。但是为了支持其他插件机制,工厂有一个addFactoryIteratorProvider(…)方法。该方法允许开发人员添加一个迭代器,该迭代器知道如何处理另一个扩展机制。例如,在Eclipse中,可以添加一个FactoryIteratorProvider,它返回一个知道如何处理Eclipse扩展点并可以从Eclipse扩展创建工厂的提供者
Abstract
现在我们已经帮助客户端代码使用了我们的接口,下一步是提供一个抽象类来帮助开发实现。
大多数GeoTools工厂都提供了一个抽象的超类来开始您的实现工作。当你制造自己的工厂时,这是一个很好的例子。
- 下面是一个AbstractFunction示例,以了解它所涉及的内容。
这不是GeoTools的一部分(还没有)——它只是显示了使用的方法:
/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2019, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
*/
package org.geotools.tutorial.function;
import java.util.Collections;
import java.util.List;
import org.geotools.util.Converters;
import org.opengis.filter.capability.FunctionName;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.ExpressionVisitor;
import org.opengis.filter.expression.Function;
import org.opengis.filter.expression.Literal;
public abstract class AbstractFunction implements Function {
protected final FunctionName name;
protected final List<Expression> params;
protected final Literal fallback;
protected AbstractFunction(FunctionName name, List<Expression> args, Literal fallback) {
this.name = name;
this.params = args;
this.fallback = fallback;
}
public abstract Object evaluate(Object object);
public <T> T evaluate(Object object, Class<T> context) {
Object value = evaluate(object);
return Converters.convert(value, context);
}
public Object accept(ExpressionVisitor visitor, Object extraData) {
return visitor.visit(this, extraData);
}
public String getName() {
return name.getName();
}
public FunctionName getFunctionName() {
return name;
}
public List<Expression> getParameters() {
return Collections.unmodifiableList(params);
}
public Literal getFallbackValue() {
return fallback;
}
// helper methods
<T> T eval(Object feature, int index, Class<T> type) {
Expression expr = params.get(index);
Object value = expr.evaluate(feature, type);
return type.cast(value);
}
}
- 以下是使用示例。
注意,我们已经减少了开发人员需要填写的方法的数量,并且我们提供了一个助手方法来避免一些与参数评估相关的“锅炉板”剪切和粘贴代码:
/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2019, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
*/
package org.geotools.tutorial.function;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.geotools.feature.NameImpl;
import org.geotools.filter.FunctionFactory;
import org.geotools.filter.capability.FunctionNameImpl;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.opengis.feature.type.Name;
import org.opengis.filter.capability.FunctionName;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Function;
import org.opengis.filter.expression.Literal;
public class ExampleFunctionFactory2 implements FunctionFactory {
private ArrayList<FunctionName> functionList;
private static FunctionName FIRST = new FunctionNameImpl("first", "geometry");
public synchronized List<FunctionName> getFunctionNames() {
if (functionList == null) {
functionList = new ArrayList<>();
functionList.add(FIRST);
}
return Collections.unmodifiableList(functionList);
}
public Function function(String name, List<Expression> args, Literal fallback) {
return function(new NameImpl(name), args, fallback);
}
public Function function(Name name, List<Expression> args, Literal fallback) {
if (new NameImpl("first").equals(name)) {
return new AbstractFunction(FIRST, args, fallback) {
public Geometry evaluate(Object object) {
Geometry geom = eval(object, 0, Geometry.class);
Coordinate coordinate = geom.getCoordinate();
return geom.getFactory().createPoint(coordinate);
}
};
}
return null; // we do not implement that function
}
}
- 您可以看到这将如何帮助快速地获得一组函数。
插件清单
允许客户端贡献一个插件
- 定义一个接口
Example: Foo - 定义工厂接口
Example: FooFactory - 定义FactoryFinder
Example: FooFactoryFinder - 为实现者定义一个抽象类
Example: AbstractFoo
允许客户端代码访问插件
1.将您的FactoryFinder公开
Example: FooFinder
在实现插件时
- 创建您的实现
Example: MyFoo - 创建您的扩展工厂
Example: MyFooFactory
Example: META-INF/services/Foo - 在META-INF/services注册
网友评论