I have a general strategic question about when to create subclasses versus when to do something else. As a motivating example, let’s say I have three boxes that I’d like to model. Boxes have various properties like material
and mass
, height
, length
, and width
. Additionally, the boxes that I have are moving in space, with this motion given by a parametric function.
There are three very unsatisfying ways I can think of to solve this problem. I’m requesting help on thinking about whether there is a better way. Please understand that this is a very silly illustrative example!
Method 1: AbstractBox
It would be easy to define something like
abstract type AbstractBox end
function position(b::AbstractBox, t)::Tuple(AbstractFloat, AbstractFloat) # returns the (x, y) position
throw("not yet implemented")
end
To do this, I need to define
struct Box1 <: AbstractBox
material::String
mass::AbstractFloat
height::AbstractFloat
length::AbstractFloat
width::AbstractFloat
end
position(b::Box1, t) = (t, t^2) # returns the (x, y) position
and similarly for Box2
, Box3
, etc even though the only thing that should differ is the position
method. This is unsatisfying because I have to copy out the material
, mass
, height
, length
, width
, etc fields
Option 2: BaseBox
An alternative approach is to define a BaseBox
object that I can then embed in Box1
, Box2
, Box3
, etc.
struct BaseBox
material::String
mass::AbstractFloat
height::AbstractFloat
length::AbstractFloat
width::AbstractFloat
end
struct Box1
b::BaseBox
end
position(b::Box1, t) = (t, t^2) # returns the (x, y) position
This works OK, but it’s pretty clunky and requires that accessing things like Box1.b.height
.
Option 3: Box
The third option is just to create a single Box
type, and to treat Box1
, Box2
, and Box3
as separate instances. To make this work, position
needs to be a field
struct Box
material::String
mass::AbstractFloat
height::AbstractFloat
length::AbstractFloat
width::AbstractFloat
position_fn::Function
end
position(b::Box, t) = b.position_fn(t)
box1_pos_fn(t) = (t, t^2)
Box1 = Box(..., box1_pos_fn)
This works fine and is perhaps the cleanest, but adding functions as fields doesn’t feel very clean to me.
Any advice / suggestions appreciated! Performance is definitely of interest.