“Use Case: When you need to return an alternate object of the same base type which will controll the flow of the application. Example: UserServiceClient can return a UserNotFound object instead of throwing a custom UserServiceException or having serialization exception caught in the consumer of the service.”
Definition
“A subclass that provides special behavior for particular cases.” - martinfowler.com
Example
Consider the following UserServiceClient
with method GetUserAsync
1 | public async Task<UserDto> GetUserAsync(Guid userId, CancellationToken cancellationToken) |
The return type UserDto
is a simple data transfer object.
1 | public class UserDto |
If the service returns 404 with no content this will throw JsonException
as content will be null. We can fix this with the following steps:
- Add a virtual method to
UserDto
calledFound()
and return true
1 | public class UserDto |
- Create the
UserNotFound
object that inherits fromUserDto
and overrides theFound
method which can then return false
1 | public class UserNotFound : UserDto |
- In the
GetUserAsync
method insideUserServiceClient
check the status code. Here we assume 404 (not found) but you could check for any other code like 500 and respond with eitherUserServiceException
or another object likeUserError
1 | public async Task<UserDto> GetUserAsync(Guid userId, CancellationToken cancellationToken) |
- In the consumer we would then check the response
1 | var user = await _userServiceClient.GetUserAsync(userId, cancellationToken) |
This code now has some defensive checks using the Special Case Pattern.