The SPOT Architecture Applied to Class ARRAYED_LIST
- Tags:
- arrayed_list
- initializing attributes
- make routines
- initializing objects
- default attribute values
- SPOT
Introduction
(From Wikipedia) In computer science, the single point of truth (SPOT) architecture, is the practice of structuring information models such that every data element is mastered (or edited) in only one place.
The Vision-2 libraries do a nice job of applying this principle to the job of assigning default values to attributes in a descendant of EV_ANY. Any call to default_create
results in a call to initialize
. By redefining initialize
in descendants you can guard against a common problem. If instead of having a SPOT for initializing attribute, they are duplicated over a number of different make-routines, it can lead to a situation where the developer forgets to assign a default value for one particular attribute with undesirable results. To a certain extent, void-safety protects the developer from the worst possible outcome of a void-target call, but what if the correct default value is a once-constant or some other non-standard value ?
A Descendant of ARRAYED_LIST
In Eiffel-Loop there is a class EL_ARRAYED_LIST extending ARRAYED_LIST that has very many initialization routines compared to the parent. This proved problematic for a class EL_TUPLE_TYPE_LIST inheriting EL_ARRAYED_LIST as the routine initialize
needs to be called no matter which of the 7 possible make-routines are called. After adding some extra make-routines I found that non_conforming_list
had not been initialized.
class
EL_TUPLE_TYPE_LIST [T]
inherit
EL_ARRAYED_LIST [TYPE [T]]
rename
make as make_list,
make_from_tuple as make_list_from_tuple
redefine
initialize
end
create
make, make_from_static, make_from_tuple, make_from_array, make_from_if, make_from_list,
make_from_special
feature {NONE} -- Initialization
make (type_array: EL_TUPLE_TYPE_ARRAY)
do
make_list (type_array.count)
across type_array as type loop
-- skip non-conforming types
if attached {like item} type.item as l_type then
extend (l_type)
elseif not all_conform then
non_conforming_list.extend (type.item)
else
create non_conforming_list.make (1)
non_conforming_list.extend (type.item)
end
end
end
make_from_static (static_type: INTEGER)
do
make (create {EL_TUPLE_TYPE_ARRAY}.make_from_static (static_type))
end
initialize
do
Precursor
compare_objects
non_conforming_list := Empty_list
end
make_from_tuple (tuple: TUPLE)
do
make (create {EL_TUPLE_TYPE_ARRAY}.make_from_tuple (tuple))
end
feature -- Access
non_conforming_list: like Empty_list
-- items in `type_array' argument to `make' routine that do not conform to type `T'
SPOT Applied to EL_ARRAYED_LIST
Rather then solve the problem at the level of EL_TUPLE_TYPE_LIST I decided for posterity to solve the issue at the level of EL_ARRAYED_LIST by redefining some of the routines found in the ELKS base class ARRAYED_LIST.
As you can see, every possible call to a make routine results in a call to initialize
which just initializes index
to zero. Redefining this routine in descendants is sufficient to ensure that all attributes have their proper default value no matter which make-routine is called.
class
EL_ARRAYED_LIST [G]
inherit
ARRAYED_LIST [G]
rename
make_filled as make_default_filled,
append as append_sequence
undefine
index_of
redefine
make, make_from_array, make_default_filled
end
EL_CHAIN [G]
rename
accommodate as grow
undefine
off, occurrences, has, do_all, do_if, there_exists, for_all, is_equal, search, copy,
i_th, at, last, first, valid_index, is_inserted, move, start, finish, go_i_th, put_i_th,
force, append_sequence, prune, prune_all, remove, swap, new_cursor, to_array, order_by
redefine
find_next_item, joined, push_cursor, pop_cursor
end
EL_MODULE_EIFFEL
create
make, make_empty, make_default_filled, make_filled,
make_from_for, make_from, make_from_if,
make_joined, make_from_special, make_from_array,
make_from_sub_list, make_from_tuple
convert
make_from_array ({ARRAY [G]}), to_array: {ARRAY [G]}
feature {NONE} -- Initialization
make (n: INTEGER)
-- Allocate list with `n' items.
-- (`n' may be zero for empty list.)
do
make_from_special (create {like area}.make_empty (n))
end
make_default_filled (n: INTEGER)
-- Allocate list with `n' items.
-- (`n' may be zero for empty list.)
-- This list will be full.
do
make_from_special (create {like area}.make_filled (({G}).default, n))
end
make_from_array (a: ARRAY [G])
-- Create list from array `a'.
do
make_from_special (a.area)
end
make_empty
do
make (0)
end
make_filled (n: INTEGER; new_item: FUNCTION [INTEGER, G])
do
make (n)
from until full loop
extend (new_item (count + 1))
end
end
make_from (container: CONTAINER [G])
-- initialize from `container' items
do
make_from_for (container, create {EL_ANY_QUERY_CONDITION [G]})
end
make_from_for (container: CONTAINER [G]; condition: EL_QUERY_CONDITION [G])
-- initialize from `container' of items for all items meeting `condition'
local
wrapper: EL_CONTAINER_WRAPPER [G]
do
create wrapper.make (container)
if attached wrapper.query (condition) as result_list then
make_from_special (result_list.area)
object_comparison := container.object_comparison
else
make_empty
end
end
make_from_if (container: CONTAINER [G]; condition: PREDICATE [G])
-- initialize from `container' of items if `condition (item)' is true
do
make_from_for (container, create {EL_PREDICATE_QUERY_CONDITION [G]}.make (condition))
end
make_from_list (list: ITERABLE [G])
do
if attached {ARRAYED_LIST [G]} list as arrayed_list then
make_from_special (arrayed_list.to_array.area)
else
make (Iterable.count (list))
across list as l_path loop
extend (l_path.item)
end
end
end
make_from_special (a_area: like area)
do
area_v2 := a_area
initialize
end
make_from_sub_list (list: READABLE_INDEXABLE [G]; start_index, end_index: INTEGER)
require
valid_range: start_index <= end_index implies list.lower <= start_index and list.upper <= end_index
local
i: INTEGER
do
if end_index < start_index then
make_empty
else
make (end_index - start_index + 1)
from i := start_index until i > end_index loop
extend (list [i])
i := i + 1
end
end
end
make_from_tuple (tuple: TUPLE)
-- extend Current with items in `tuple' of type conforming to `G'
local
i: INTEGER
do
make (tuple.count)
from i := 1 until i > tuple.count loop
if attached {G} tuple.item (i) as l_item then
extend (l_item)
end
i := i + 1
end
end
make_joined (array_1, array_2: ARRAY [G])
do
make (array_1.count + array_2.count)
append (array_1); append (array_2)
end
initialize
-- initialize default attribute values
do
index := 0
end