DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Coding
  3. Languages
  4. Design Choices: Return Values and Mocks

Design Choices: Return Values and Mocks

Giorgio Sironi user avatar by
Giorgio Sironi
·
Feb. 27, 13 · Interview
Like (0)
Save
Tweet
Share
3.62K Views

Join the DZone community and get the full member experience.

Join For Free

let's suppose you have a graph of collaborating objects, such as this:

to perform some action, you have to send a message to the facade object, that will collaborate with the rest of the graph to produce a result. there are at least two basic ways in which the output of this computation can exit this object graph:

  • through the return value of facade.
  • by being sent to a target object, passed in the initial call.

the second choice works like this:

facade.dosomething(target target, ...)
// somewhere in the graph:
target.accept(result);

in this article i want to investigate which contexts call for the first solution, and which for the second one.

equivalence

first of all we can say there is not much difference between the solution in some basic scenarios: it's just a matter of style. the first style reminds me of functional programming, while the second of the callback style of javascript applications, since its non-blocking nature makes the target objects and functions a necessity.

the cost of the solution in terms of code is similar. in the first case you have:

  • a return statement on the facade
  • a return statement on the intermediate objects
  • a return statement on the leaf objects

in the second:

  • a target interface
  • an additional parameter on the facade
  • an additional parameter on the intermediate objects
  • an additional parameter on the leaf objects

not that in some languages both the return statements and the interface construct may be implicit, so i'm not talking about lines of code and their length here; i'm counting the cost in time that we have to pay every time we read the code: a method with 3 parameters is relatively more difficult to understand with respect to the same method without one of them; in the same way, a void method is easier to reason about and modify than a method that returns a result.

dimensions: who produces the result

the functional approach certainly wins when the final value cannot be directly produced by the leaf objects.

for example, if you are representing a mathematical expression such as (1+2)*(3+4) with a composite pattern, the value of the whole expression can only be computed in the facade object. in this case, you only have the choice of putting a return statement there.

consider instead the case where a leaf has to be chosen, and is capable by itself to produce the result; for example, you're choosing which url to redirect the user to, and each leaf generates a different one while the rest of the graph chooses the leaf to ask. here passing a target object to the leaf is possible.

dimensions: unit testing

in the second scenario above, unit testing of the facade and the intermediate object is influenced. in the case of return statements, we have a stub and an assertion:

leaf leaf = mock(leaf.class);
intermediate intermediate = new intermediatea(leaf);
allow(leaf.dosomething()).andreturnvalue(23);
assertequals(23, intermediate.dosomething(input, ...);

testing delegation is verbose (forgive my rusting mockito skills). consider instead using the target object:

target target = mock(target.class);
leaf leaf = mock(leaf.class);
intermediate intermediate = new intermediatea(leaf);
intermediate.dosomething(input, ...);
verify(leaf).dosomething(target);

and we can just test that target is passed down.

dimensions: flexibility of the result

moreover, this ease of unit testing persists even for maintenance, as you can change the target interface without facade and intermediate objects having to know. you can refactor from:

interface target {
  public void accept(string result);
}

to:

interface target {
  public void accept(string result, int anotherfield);
}

by changing only the leaf objects. the way you can achieve this in the return-based solution is by introducing an additional value object called result, and wrap the string in there so that other fields can be added later without intervention on the return types of facade and intermediate.

dimensions: producing a result at all

in some cases you may want to not produce a result. in the return-based solution, this requires a null value or a null object:

result = facade.dosomething(...);
if (result != null) {
  // use it
}

while this is easier with the target object:

class leaf1
{
  public void sosomething(target target, ...)
  {
  // call target.accept(), or do not call it
  }
}

and this form also scales to multiple results of the same type:

public void dosomething(target target, ...)
  {
  target.accept(23);
  target.accept(42);
  }

conclusions

we have seen that different contexts call for different forms of the code to accomodate the requirements; moreover, changes we want to support are better dealt with one of the solutions with respect to the other. design means searching for the appropriate form of your code, not sticking with the one you know better: as you see in the multiple results example, disruptive changes for one form are very easy to make in another design. don't choose blindly...

Object (computer science) unit test Design

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Best CI/CD Tools for DevOps: A Review of the Top 10
  • Unlocking the Power of Elasticsearch: A Comprehensive Guide to Complex Search Use Cases
  • 5 Steps for Getting Started in Deep Learning
  • Top 10 Best Practices for Web Application Testing

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: