Nested structs and objects - do or don't?

29 views (last 30 days)
I have a question on nested objects. Take for example an object array of class 'house' which have a fixed number of floors whose properties are defined by a class 'story' (to avoid use of builtin 'floor'):
classdef house
properties
number uint32
location string
story_1 story
story_2 story
% story_3 story etc...
end
methods % Construct array of n houses
function obj = house(n)
obj.story_1 = story;
obj.store_2 = story;
if nargin ~= 0
obj(n,1) = obj;
end
end
end
end
where
classdef story
properties
size_in_square_ft double
number_of_rooms uint8
% etc...
end
end
This would be an intuitive way of structuring the data since each 'house' object in the array holds the data for one house. On the other hand, I'm struggling to assign values for floors of multiple houses simultaneously. While this works:
Houses = house(3);
numbers = num2cell(1:3);
[Houses(:).number] = numbers{:};
This does not (since Matlab does not allow simultaneous assignment to subfields):
Houses = house(3);
numbers_of_rooms_story_1 = num2cell([2 1 3]);
[Houses(:).story_1.number_of_rooms] = numbers_of_rooms_story_1{:};
I know I could do the assignments in a loop, but that would likely be slower than vectorized assignment (which may become an issue when dealing with large object arrays and multiple properties). Hence my question: Are "nested objects" bad practice (if so, what should I use instead)? (And more generally, is it bad practice to use an OOP approach for processing large amounts of data?)
  4 Comments
broken_arrow
broken_arrow on 15 Apr 2021
Edited: broken_arrow on 15 Apr 2021
Guess it must be because of the num2cell conversion and the fact that
[s.a]=bcell{:}
is not just a simple assignment, but requires gathering multiple variables.
So are nested objects ok or is there a better way?
broken_arrow
broken_arrow on 15 Apr 2021
Edited: broken_arrow on 16 Apr 2021
In the end, it comes down to this question: Given a number of objects with the same properties, the level at which the separation is introduced is arbitrary. For instance, one could write
Houses(1).story_1.number_of_rooms = 2;
Houses(2).story_1.number_of_rooms = 1;
Houses(3).story_1.number_of_rooms = 3;
% or alternative 1
Houses.story_1(1).number_of_rooms = 2;
Houses.story_1(2).number_of_rooms = 1;
Houses.story_1(3).number_of_rooms = 3;
% or alternative 2
Houses.story_1.number_of_rooms = [2 1 3];
% which equals Houses_story_1_number_of_rooms = [2 1 3];
Structuring on the "left side" of the separation level is optional and is just for convenience. In the most extreme case (alternative 2), no objects or structures are needed at all (which should mean this style is the most efficient). But if that's true, what is the point of OOP after all?

Sign in to comment.

Accepted Answer

Matt J
Matt J on 15 Apr 2021
Edited: Matt J on 15 Apr 2021
Ultimately, it depends on the situation and the data access patterns that you are going to need. As a rule of thumb, I don't think there is anything wrong with nested objects. However, I do try for OOP designs where you have scalar parent objects with array-valued property objects. That tends to make the property access options the neatest and most efficient.
Whether this rule will always be applicable I cannot say, but I think it does apply well to the house application and will work more smoothly than what you've attempted. In particular, it doesn't make much sense to me that you would want to assign number_of_rooms values to the same floor across multiple houses, like in your example. What if different houses have different numbers of floors? I think it more likely that you would want to assign number_of_rooms values across multiple stories in the same house, and so I would design the classes as follows:
classdef story
properties
size_in_square_ft double
number_of_rooms uint8
% etc...
end
end
classdef house
properties
number uint32
location string
stories story
end
methods
function obj=house(numFloors)
obj.stories(numFloors)=story;
end
end
end
This allows you to do things like,
House=house(3);
[House.stories(1:3).number_of_rooms]=deal(4,4,3)
If you need to then consider arrays of house objects, I think it would be natural to process them using for-loops. because I would expect the contents of houses to be rather inhomogeneous.
  2 Comments
broken_arrow
broken_arrow on 15 Apr 2021
Right, it would indeed be better to use true indices for the stories (rather than pseudo-indices as I did). So I conclude that in principle it's ok to use nested objects/structures to match the structure of the real world counterpart. In case a significant share of runtime is spent on retrieving variables from the structure, it would be more efficient to introduce an array as you mentioned here: https://mathworks.com/matlabcentral/answers/436593-efficient-indexing-with-nested-object-and-object-arrays )
broken_arrow
broken_arrow on 15 Apr 2021
Edited: broken_arrow on 15 Apr 2021
Regarding your edits: When assigning number_of_rooms values to the same floor across multiple houses we'd have to work with nan or [] values if a house doesn't have said floor (a bit crude, but could be done in principle). I agree it's best to have a scalar parent object with array valued property objects, i. e.
House.stories(1).number_of_rooms = 3;
House.stories(2).number_of_rooms = 4; % etc.
since that allows direct assignment like
[House.stories(1:3).number_of_rooms]=deal(4,4,3)
as you mentioned, but as soon as we're dealing with multiple "houses" (to speak in terms of the example), that wouldn't be possible anymore - unless we shift the house number to the stories property by making it a multidimensional array like
House.stories(1,1).number_of_rooms = 3; % etc.
but as you say, it should be fine to introduce a house object array if necessary.

Sign in to comment.

More Answers (1)

Steven Lord
Steven Lord on 15 Apr 2021
Off the top of my head I'd probably use a three tier approach, something along the lines of the following untested code:
classdef room
properties
squareFootage double;
description % kitchen, bedroom, bathroom, etc.
end
end
Then each story object would contain an array of room objects. If we want to know the square footage of a story, modulo having very thick walls that aren't counted as part of any room, it would be the sum of the square footage of the rooms.
classdef story
properties
theRooms (1, :) room;
end
properties(Dependent)
squareFootage
end
methods
function sf = get.squareFootage(obj)
sf = 0;
for whichRoom = 1:numel(obj.theRooms)
sf = sf + obj.theRooms(whichRoom).squareFootage;
end
end
end
end
And finally a house would have an array of stories. A house's square footage is the sum of the square footage of its stories.
classdef house
properties
stories (1, :) story;
end
properties(Dependent)
squareFootage
end
methods
function sf = get.squareFootage(obj)
sf = 0;
for whichStory = 1:numel(obj.stories)
sf = sf + obj.stories(whichStory).squareFootage;
end
end
end
end
There's some duplication between house's and story's get.squareFootage methods. I might abstract that out into an aggregateSquareFootage function:
function sf = aggregateSquareFootage(listOfAreas)
sf = 0;
for whichArea = 1:numel(listOfAreas)
sf = sf + listOfAreas(whichArea).squareFootage;
end
end
and turn the methods into:
function sf = get.squareFootage(obj)
sf = aggregateSquareFootage(obj.stories);
end
% or
function sf = get.squareFootage(obj)
sf = aggregateSquareFootage(obj.theRooms);
end
Regarding this comment from the original post:
On the other hand, I'm struggling to assign values for floors of multiple house simultaneously.
Perhaps that's a sign that the number of rooms on floor 1 of a house may not be really closely related to the number of rooms on floor 2 of that house or on floor 1 of the house next door. In reality there may be a relationship especially if all the houses were build to the same plan, but in that case I'd probably construct a plannedHouse and make copies of it to represent the buildings in the housing development / condo association / neighborhood / etc.
plannedHouse = house(etc);
houses = repmat(plannedHouse, 1, numHouses);
  1 Comment
broken_arrow
broken_arrow on 15 Apr 2021
Edited: broken_arrow on 15 Apr 2021
Thanks for your answer (actually the first time i hear about dependent properties). Regarding your remark Perhaps that's a sign that the number of rooms on floor 1 of a house may not be really closely related to the number of rooms on floor 2 of that house or on floor 1 of the house next door: What I meant is the problem of directly assigning values to subfields of nested structures like
[Houses(:).story(1).number_of_rooms] = numbers_of_rooms_story_1{:};
as described above - which is not possible afaik. A house may not be the best example, but there may be cases where we pretty much know what the structure will be like and it would be nice to be able to assign values directly:
Person(1).profession = 'lawyer'
Person(2).profession = 'lawyer'
Person(3).profession = 'sawyer'
left_hand_fingers = num2cell([5 5 4]);
right_hand_fingers = num2cell([5 5 3]);
[Person(:).left_hand.number_of_fingers] = left_hand_fingers{:}; % -> error
[Person(:).right_hand.number_of_fingers] = right_hand_fingers{:}; % -> error
As I discussed with Matt, a loop currently seems to be the only way to perform assignments when dealing with nested nonscalar objects/structs.

Sign in to comment.

Products


Release

R2020b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!