What to do when one of implementations requires a bit more data

What to do when one of implementations requires a bit more data



I am thinking what is the right way to deal with a situation like this: I have an interface IService that looks like this:


class Configuration

public int Min get; set;
public int Max get; set;


interface IService

int Calculate(int userId, Configuration configuration)



I have, let's say, 5 classes that implement this interface and they work well.
One day I have to implement a 6th service, but this one is a little bit different. To do its job, a new service needs configuration like this:


class ExtendedConfiguration : Configuration

public string Filter get; set;



My new service could look like this:


class NewService : IService

public int Calculate(int userId, Configuration configuration)

var extendedConfig = configuration as ExtendedConfiguration;
//Calculating and returning result using extendedConfig...




It seems to be fine, the service would work.
BUT, I don't like the fact, that Calculate method signature requires Configuration object, while in reality ExtendedConfiguration is needed - otherwise it will not be able to do the calculation (and will throw exception).



Is there a better way of writing this code?





How about making the interface generic? IService<TConfig> could then take a TConfig configurationparameter in the Calculate method.
– DeveloperExceptionError
Sep 4 '18 at 7:28


IService<TConfig>


TConfig configuration





You could achieve this by using generics in your interface. Something like int Calculate(int userId, T configuration) with T constrained to be of type Configuration at the interface-definition-level.
– HimBromBeere
Sep 4 '18 at 7:28



int Calculate(int userId, T configuration)


T


Configuration





"(and will throw exception)." FYI configuration as ExtendedConfiguration does not throw an exception. It returns null if the casting failed. you can check for it.
– Mong Zhu
Sep 4 '18 at 7:34


configuration as ExtendedConfiguration


null





The problem of this question is Is there a better way of writing this code? depends on a lot of other factors, you mention factories and not liking generics. I kind of think you have answered your own question. the only other things i can think of is, making IServiceExtended, and using composition. however i dont think its going to solve anymore problems then it creates and It all really depends on how far you want to go down the rabbit hole
– TheGeneral
Sep 4 '18 at 7:37



Is there a better way of writing this code?


IServiceExtended





Shouldn't the configuration be a property of the service? Or is it just more like parameters? If it could be in the service, then you would be able to have IService which contains the method with the int parameter, and an IService<T> that would provide the configuration in the specialized type. Then your services have to implement both (maybe a common interface). Would that work for you?
– DeveloperExceptionError
Sep 4 '18 at 7:41




4 Answers
4



that Calculate method signature requires Configuration object, while in reality ExtendedConfiguration is needed



As already mentioned you could aim for a generic solution of your restriction problem. Define the interface with a generic parameter and constrain it to be of type Configuration:


Configuration


interface IService<T> where T : Configuration

int Calculate(int userId, T configuration)



Then your old services could still look like they used to be:


class OldService : IService<Configuration>

public int Calculate(int userId, Configuration configuration)

return (configuration.Min + configuration.Max) * 2;




and in the NewService you can specify that the input parameter has to be of type ExtendedConfiguration :


NewService


ExtendedConfiguration


class NewService : IService<ExtendedConfiguration>

public int Calculate(int userId, ExtendedConfiguration configuration)

string accessHereTheExtendedVersion = configuration.Filter;
return (configuration.Min + configuration.Max) / 2;




This is not really the answer to your exact question:



Is there a better way of writing this code?



But it is a different approach of how this problem can be solved. Whether it suits your context and situation, you have to test it.





It is one of ideas that I tried before asking this question on SO. Still, thanks for presenting it nicely, might be useful for others. Personally I'm not a big fan of generics, usually when you make one thing generic, sudenly many other things also have to become generic.
– Loreno
Sep 4 '18 at 7:47






Upvote, seems like a sane and straight forward and modern approach
– TheGeneral
Sep 4 '18 at 7:51





@Loreno "usually when you make one thing generic, sudenly many other things also have to become generic." I know such cases. Another approach could be a strategy pattern. (I guess this is what [DeveloperExceptionError 14 ] spoke about) But I don't really know whether it fits your construct of services
– Mong Zhu
Sep 4 '18 at 7:57





@Loreno how different is the usage of Configuration between the services? do you use it only in Calculation ? or also in other methods?
– Mong Zhu
Sep 4 '18 at 8:04


Configuration


Calculation





@Loreno actually without generics you are always left with the ambiguity that a normal Configuration can be inserted. This is usually solved using polymorphism. I think for this you would need an overrideable method in Configuration which will be overridden in ExtendedConfiguration and it has to behave differently depending on the type of configuration. Then you can use it as a property inside the service and inside the calculate method
– Mong Zhu
Sep 4 '18 at 8:09


Configuration


Configuration


ExtendedConfiguration



I guess it comes down to the definition of "better". I don't personally like the assumptive cast to ExtendedConfiguration. Not only do you outsource the problem of how to populate that configuration to somewhere else, but now your code will crash if I send the wrong implementation. So something outside needs to know that you need this specific implementation, and populate the setting values accordingly. In the world of code smells, it is hardly a capital offence, but I would invert that problem.



Rather than provide a configuration to the service, provide a configuration provider:
(using C#7 ValueTuples)


public enum ValueType

ReturnedConfigured,
NotConfiguredReturnedDefault,
InvalidConfigurationReturnedDefault


public interface IConfigurationProvider

(T result, ValueType resultType) GetSetting<T>(string serviceName, string settingKey, T defaultValue);


public interface IService

int Calculate(int userId, IConfigurationProvider configurationProvider);



You can consume this as follows:


public class NewService : IService

public int Calculate(int userId, IConfigurationProvider configurationProvider)

(int min, _) = configurationProvider.GetSetting(nameof(NewService), "Min", -1);
(int max, _) = configurationProvider.GetSetting(nameof(NewService), "Max", Int32.MaxValue);
(string filter, ValueType filterConfigResponse) = configurationProvider.GetSetting(nameof(NewService), "Filter", string.Empty);
if (filterConfigResponse!=ValueType.ReturnedConfigured)

throw new ArgumentException("Oh no! Where's my filter?", nameof(configurationProvider));

Console.WriteLine($"nameof(NewService),min=min, max=max, filter=filter");
return 0;




Here is an example of an IConfigurationProvider that you might inject into a unit test


public class FakeConfigurationProvider : IConfigurationProvider

public (T result, ValueType resultType) GetSetting<T>(string serviceName, string settingKey, T defaultValue)

switch (settingKey)

case "Min":

return (result: (T)Convert.ChangeType(1, typeof(T)), resultType: ValueType.ReturnedConfigured);

case "Max":

return (result: (T)Convert.ChangeType(42, typeof(T)), resultType: ValueType.ReturnedConfigured);

case "Filter":

return (result: (T)Convert.ChangeType("Hello World", typeof(T)), resultType: ValueType.ReturnedConfigured);

default:

return (result: defaultValue, resultType: ValueType.NotConfiguredReturnedDefault);






From here it is pretty straight forward to imagine other configuration providers for pulling the settings from app.Config or a database table or a Uri or however you choose to have them stored.





Your solution is.. complicated. Probably it's very good and follows some good practices, but personally I am not able to quickly understand such code when I look at it.
– Loreno
Sep 7 '18 at 13:59





@Loreno, if you find my solution complicated then perhaps I haven't explained it well enough. What part are you struggling to understand?
– Adam G
Sep 9 '18 at 14:12





no, it's not your fault at all. I just find it hard to think in such abstractions, which I really should improve on my side.
– Loreno
Sep 10 '18 at 7:48



I don't find this solution terrible; algorithms which try to treat objects in an inheritance hierarchy uniformly come with the downside that at some point you'll need to set or check certain traits of objects at run time. The reason is exactly what you experience: It's rare that one solution fits all if you get to the fringe cases.



An alternative apporach to checking the type would be to make Configuration, well, configurable. The typical approaches are


Configuration



Inheritance: Configuration defines extra virtual methods (e.g. pre-processing, extra-processing or post-processing ;-)) which are empty but can be filled with life in derived classes. The calling code doesn't have to know the exact type, it just stupidly calls these functions in the proper order, and whatever that particular class defines them to do gets done.


Configuration



Injection: The behavior of Configure objects is configured at run time by setting members which "know how to do" things. The calculation is performed using information or actions defined in these "agents".


Configure



Actually, there is no problem. As mentioned before, the casting is failed.



You don't want to generic type approach. Maybe you can create an instance ExtConfigclass and assign config's values to it.


class NewService : IService

public int Calculate(int userId, Configuration configuration)

var extendedConfig = new ExtendedConfiguration
Max = configuration.Max,
Min=configuration.Min
;


return e.Max - e.Min;






I think you misunderstood the issue. ExtendedConfiguration has MORE data than Configuration. If someone used Configuration in Calculate method, the method will fail - it REQUIRES ExtendedConfiguration, since it contains the required data (string Filter)
– Loreno
Sep 4 '18 at 9:06



Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Some of your past answers have not been well-received, and you're in danger of being blocked from answering.



Please pay close attention to the following guidance:



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)