geometry/doc/quickbook/strategy_rationale.qbk
2012-01-09 21:56:08 +00:00

149 lines
8.7 KiB
Plaintext

[/==============================================================================
Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
Copyright (c) 2009-2012 Mateusz Loskot, London, UK., London, UK
Use, modification and distribution is subject to the Boost Software License,
Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)
===============================================================================/]
[section:strategy Strategy Rationale]
[@http://en.wikipedia.org/wiki/Strategy_pattern Strategy] is a [@http://en.wikipedia.org/wiki/Design_pattern_(computer_science) software design pattern] used to
achieve algorithms slection at runtime. An idiomatic variation of the Strategy pattern specific
to C++ programming language is known as Policy pattern with related technique of [@http://en.wikipedia.org/wiki/Policy-based_design Policy-based design]. Thus, in the C++ template
contexts, policy is usually meant as selection of algorithms at compile time.
['TODO: the two sentences below need to be clarified, what is "another context", etc. --mloskot]
__boost_geometry__ uses (sparsely) the term policy in a broader, or in another, context. The term
Strategy is used specificly for a computation method targeted to a specific coordinate system.
In __boost_geometry__ strategies do have the following purposes:
* for each coordinate system, a default strategy is selected by compile time, using the coordinate
system tag. This is effectively tag dispatching.
* users can override the default choice by using the overloaded function, which has a strategy
as an extra parameter, and take another strategy
* users can override the default choice by using the overloaded function, to use the default
strategy, but constructed with specific parameters
* users can override the default choice by using the overloaded function, to use the default
strategy (which can be a templated structure), with other template parameters then the default ones
* users can override the default choice by using the overloaded function, to use the default
strategy, which can be a class template, with other template parameters then the default ones,
with the specific purpose as to select another calculation type (e.g. __gmp__ or another
arbitrary precision number type)
All these decisions and choices listed above occur at compile-time.
Let's discuss a short example to explaining how strategies work in __boost_geometry__.
Note, it is also explained in the design rationale section.
Considering calculation of distance in three common coordinate systems, for each of the coordinate
systems a default strategy implementing distance calculation has been assigned:
* __wiki_cs_cartesian__ uses Pythagoras as default strategy for point-point distance calculations
* __wiki_cs_spherical__ uses Haversine strategy a default
* __wiki_cs_geographic__ uses Andoyer.
The Haversine works on the unit sphere, radius `1`. Library users can use the distance function,
specifying haversine strategy constructed with a radius of `2`. Or, for example, they can
use the distance function, specifying the more precise Vincenty strategy
(for __wiki_cs_geographic__, but that might not even be checked there).
Specifying strategies is useful, even if not point-point distance is to be calculated, for
example, point-polygon distance. In the end, it will call the elementary specified functionality.
Note, that for this example, the point-segment distance strategy is also considered as "elementary".
Note also, that it can have an optional template parameter defining the underlying
point-point-distance-strategy.
[section:properties Properties of Strategies]
Strategies can be constructed outside a calling function, thus they can be specified as an
optional parameter (implemented as an overloaded function), and not only as a template parameter.
Furthermore, strategies can be reused number of times, in subsequent function calls.
Therefore strategies as function parameter are declared as reference to `const` and they
should be stateless, except that they can be provided with some initialization details
during construction.
The strategy needs to access construction information (member variables), its calculation method
is therefore usually not a `static` method but a non-static `const` method. It can then access
member variables, while still being `const`, non-mutable, stateless and read-only object, and
being able to be called across several function calls.
['TODO: The next 2-3 paragraphs might need some refactoring, to rephrase what is said in
clearer and simple manner --mloskot]
For some algorithms, strategies may need to store some intermediate results of calculation
(a state). For this purpose, strategies should declare a `state_type`. An instance of the `state_type`
is created before first call is performed and specified in each call.
The calculation method is always called apply (as convention in __boost_geometry__) and gets the
most elementary information as a parameter: a point, a segment, a range. It depends on the
algorithm and, sometimes, on the source geometry passed. That should actually be the case as
least as possisble.
In most cases, there is an additional method result which returns the calculated result.
That result-method is a also non-static const method, and the state is passed. Note that the
methods might be non-static const, but they might also be static. That is not checked by the
concept-checker.
A strategy for a specific algorithm has a concept. The distance-strategy should follow the
distance-strategy-concept. The point-in-polygon strategy should follow the
point-in-polygon-strategy-concept. Those concepts are not modelled as traits classes (contrary
to the geometries). The reason for this is that it seems not necessary to use legacy classes
as concepts, without modification. A wrapper can be built. So the strategies should have a method
apply and should define some types.
Which types, and which additional methods (often a method result), is dependant on the algorithm
and type of strategy.
Strategies are checked by a strategy-concept-checker. For this checker (and sometimes for
checking alone), they should define some types. Because if no types are defined, the
cannot be checked at compile time... The strategy-concept-checkers are thus implemented per
algorithm and they use the __boost_concept__ for concepts checking.
According to what was explained above, the essentials of the design are:
* function determines default-strategy, or is called with specified strategy
* function calls dispatch (dispatching is done on geometry tag)
* dispatch calls implementation (in namespace detail), which can be shared for different geometry
types, for single as well as for aggregate geometries (multi-geometries)
* implementation calls strategy (if applicable), with the smallest common (geometric)
element applicable for all calculation method, the common denominator.
[endsect] [/ end of section Properties of Strategies]
[section:alternative Alternative Design]
Some calculations (strategies) might need to take the whole geometry, instead of working on
point-per-point or segment-per-segment base. This would bypass the dispatch functionality.
Because all strategies would take the whole geometry, it is not necessary to dispatch per
geometry type. In fact this dispatching on geometry-type is moved to the strategy_traits class
(which are specialized per coordinate system in the current design). So in this alternative design,
the strategy traits class specializes on both geometry-tag and coordinate-system-tag, to select
the default strategy. For the default strategy, this move from "dispatch" to another dispatch
called `strategy_XXX` (XXX is the algorithm) might make sense. However, if library users would
call the overloaded function and specify a strategy, the only thing what would happen is that
that specified strategy is called.
For example:
template <typename G1, typename G2, typename S>
bool within(G1 const& g1, G2 const& g2, S& const strategy)
{
return strategy.apply(g1, g2);
}
The library user could call just this `strategy.apply()` method directly. If more strategies are
provided by the library or its extensions, it would still make sense: the user can still call
within and does not have to call the apply method of the strategy.
The convex hull strategy currently works on a whole geometry. However, it is possible that it will
be reshaped to work per range (the algorithm internally works per range), plus a mechanism to
pass ranges multiple times (it currently is two-pass).
[endsect] [/ end of section Alternative Design]
[endsect] [/ end of section Strategies]