Reputation: 505
I've got a repository interface (simplified example code):
public interface IPersonRepository
{
Task<PersonDTO> Get();
}
With two implementations.
One for a direct connection to a database:
public SqlPersonRepository : SqlRepository, IPersonRepository
{
public SqlPersonRepository(IDbConnectionProvider dbCon) : base(dbCon) { }
public async Task<PersonDTO> Get()
{
// use dbCon and dapper to get PersonDTO from database
}
}
And another one for remote access via web api:
public ApiPersonRepository : ApiRepository, IPersonRepository
{
public ApiPersonRepository(IApiConnectionProvider apiCon) : base(apiCon) { }
public async Task<PersonDTO> Get()
{
// use apiCon (contains base url and access token) to perform an HTTP GET request
}
}
The interface makes sense here, because the server can use the SqlPersonRepository
. And the remote (native) client can use the ApiPersonRepository
. And for most all of the the use cases, this is all I need.
However, my application supports an extraction of a subset of data from the server so that the client application can run while offline. In this case, I'm not just grabbing one person, I'm grabbing a large set of data (several to tens of megabytes) which many times will be downloaded over a slow mobile connection. I need to pass in an IProgress
implementation so I can report progress.
In those cases, I need an ApiDatabaseRepository
that looks like this:
public ApiDatabaseRepository : ApiRepository, IDatabaseRepository
{
public ApiDatabaseRepository(IApiConnectionProvider apiCon) : base(apiCon) { }
public async Task<DatabaseDTO> Get(IProgress<int?> progress)
{
// use apiCon (contains base url and access token) to perform an HTTP GET request
// as data is pulled down, report back a percent downloaded, e.g.
progress.Report(percentDownloaded);
}
}
However the SqlDatabaseRepository
does NOT need to use IProgress
(even if Dapper COULD report progress against a database query, which I don't think it can). Regardless, I'm not worried about progress when querying the database directly, but I am worried about it when making an API call.
So the easy solution, is that the SqlDatabaseRepository
implementation accepts the IProgress
parameter, with a default value of null, and then the implementing method just ignores that value.
public SqlDatabaseRepository : SqlRepository, IDatabaseRepository
{
public SqlDatabaseRepository(IDbConnectionProvider dbCon) : base(dbCon) { }
public async Task<DatabaseDTO> Get(IProgress<int?> progress = null)
{
// use dbCon and dapper to get DatabaseDTO from database
// progress is never used
}
}
But that smells funny. And when things smell funny, I wonder if I'm doing something wrong. This method signature would give the indication that progress will be reported, even though it won't.
Is there a design pattern or a different architecture I should be using in this case?
Upvotes: 0
Views: 167
Reputation: 17668
Oversimplifying this, you basically have 2 options: having a consistent interface or not.
There are, of course other design patterns which might work here, (e.g.; some decorators and a factory method), but I believe them to be overkill.
If you stick to the general rule that consistent interface is desired, I think having a "not entirely implemented" callback technique isn't that bad. You could also consider just to implement it - or at least: make it return something which makes sense.
I would definitely avoid a construction with 2 different interfaces of some kind. Although sometimes this is the better option (when checking if something supports something), e.g.; testing if a hardware component is available - I see it as overkill in your scenario. It would also put more logic at the caller side, and unless you want to open a process-dialog screen only in this scenario, I would avoid it.
A last note: there are alternative progress report patterns such as using an event, or, passing in an optional callback method. This latter looks like your solution but is in fact a little different.
Still this faces you with the same issue in the end, but might be worth to consider.
There are many more solutions - but given the context you provided, I am not sure if they apply. And keep in mind, this is highly opinion based.
Upvotes: 1