# Han Solo Revisited

A long time ago in a blog post far, far away… Andy wrote about Han Solo Encapsulation – to keep Jabba’s “system working as designed he needed to encapsulate his system behind a rock solid interface”.

By a stroke of good fortune, or judicious design choices depending on your perspective, a recent refactoring job that I thought could have far reaching consequences turned into a few changes in a single class, ultimately saving much time. Let’s have a look.

My scenario is that I have a DataProvider that accepts a DataRequest and returns some data:

classdef DataProvider

methods

function data = getData(dataProvider,dataRequest)

arguments
dataProvider (1,1) DataProvider
dataRequest (1,1) DataRequest
end

% Implementation continues...

end

end

end


The DataRequest looks like this:

classdef DataRequest

properties
Make (1,1) string = missing
Model (1,1) string = missing
ManufacturingYear (1,1) datetime = missing
end

end


It defines relevant properties that the DataProvider needs to serve up the data. All the values are scalar and have default values of missing to represent the fact that they are “unset” by the user.

To form its query, the DataProvider must extract the values of each property. To do this, it needs to know that “unset” is represented by “missing” and therefore it should be ignored from the search condition.

Here’s one possible implementation in DataProvider:

classdef DataProvider

methods

function data = getData(dataProvider,dataRequest)

arguments
dataProvider (1,1) DataProvider
dataRequest (1,1) DataRequest
end

searchTerms = {};
propsToGet = ["Make" "ManufacturingYear" "Model"];

for prop = propsToGet
if ~ismissing(dataRequest.(prop))
searchTerms = [searchTerms {prop} {dataRequest.(prop)}];
end
end

result = dataProvider.search(searchTerms{:});

% Implementation continues...

end

end

end


The problem here is that DataProvider, and any other code that makes use of DataRequest, is coupled to how DataRequest represents its “unset” values. That’s something that should be encapsulated within the DataRequest. What my DataProvider really wants is an array of name-value pairs of non-missing property names and values.

(We could also describe this as an instance of the tell don’t ask principle since we’re not actually hiding DataRequest’s properties from the outside world.)

Let’s refactor the DataRequest to add a method that does just that. I’ve called it namedargs2cell due to its similarity to the built-in MATLAB function.

classdef DataRequest

properties
Make (1,1) string = missing
Model (1,1) string = missing
ManufacturingYear (1,1) datetime = missing
end

methods

function paramCell = namedargs2cell(dataRequest)

arguments
dataRequest (1,1) DataRequest
end

paramCell = {};
propsToGet = ["Make" "ManufacturingYear" "Model"];

for prop = propsToGet
if ~ismissing(dataRequest.(prop))
paramCell = [paramCell {prop} {dataRequest.(prop)}];
end
end

end

end

end


This makes our DataProvider much simpler:

classdef DataProvider

methods

function data = getData(dataProvider,request)

arguments
dataProvider (1,1) DataProvider
request (1,1) DataRequest
end

searchTerms = namedargs2cell(request);
result = dataProvider.search(searchTerms{:});

% Implementation continues...

end

end

end


Returning to our encapsulation principle, what do we achieve? In my real-life case, I wanted to change the DataRequest to allow multiple values to be specified for a given property rather than just scalars. It therefore makes sense to represent “unset” with an empty rather that with a scalar missing. Since all knowledge of how “unset” is represented is contained within the DataRequest, the only changes I needed to make were within DataRequest itself. No other code had to be touched:

classdef DataRequest

properties
Make (1,:) string
Model (1,:) string
ManufacturingYear (1,:) datetime
end

methods

function paramCell = namedargs2cell(dataRequest)

arguments
dataRequest (1,1) DataRequest
end

paramCell = {};
propsToGet = ["Make" "ManufacturingYear" "Model"];

for prop = propsToGet
if ~all(isempty(dataRequest.(prop)))
paramCell = [paramCell {prop} {dataRequest.(prop)}];
end
end

end

end

end


In the above, note how ismissing has become all(isempty(…)).

In conclusion, by providing the right interface to your classes, code changes can become much more limited in scope and easier to implement.

Published with MATLAB® R2022b

|

### 댓글

댓글을 남기려면 링크 를 클릭하여 MathWorks 계정에 로그인하거나 계정을 새로 만드십시오.