36

Say I have an interface:

public interface IFeature
{
    Task execFeature();
}

and two implementations:

public class FirstFeature : IFeature
{
    private IWebApi webApi;
    public FirstFeature(IWebApi webApi)
    {
        this.webApi = webApi;
    }

    public async Task execFeature()
    {
        string response = await webApi.getClassName();
        IResult result;
        if(response==null)
            result = new TextResult("Error accessing api - check internet connection/api address");
        else
            result = new TextResult("Hello dear user – the selected class name is " + response);
        result.display();
    }
}

public class SecondFeature : IFeature
{
    private IWebApi webApi;
    public SecondFeature(IWebApi webApi)
    {
        this.webApi = webApi;
    }

    public async Task execFeature()
    {
        List<string> classNames = new List<string>();
        var classNameTasks = Enumerable.Range(1, 3).Select(i => webApi.getClassName()).ToArray();
        classNames.AddRange((await Task.WhenAll(classNameTasks)));
        IResult result;
        if (classNames[0] == null)
            result = new TextResult("Error accessing api - check internet connection/api address");
        else 
            result = new TextResult("Hello dear user – we’ve selected three new class names for you, and they are " + classNames[0] + ", " + classNames[1] + ", and " + classNames[2]);
        result.display();
    }
}

As you can see, in both implementations I had to do the result = new TextResult("Error accessing api - check internet connection/api address"); line to report the error.

What is the best practice in OOP/Good Design to have a constant error_string that I can access in all of my implementations?

the way it is right now, code is duplicated.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Ofek Agmon
  • 5,040
  • 14
  • 57
  • 101

6 Answers6

59

I don't think there is a best practice. It is just a matter of preference.

I store constants inside static classes.

public static class Constants
{
   public static class Messages
   {
      public const string Error = "Error accessing api...";
      public const string Hello = "Hello ...";
   }
}

Usage

var result = new TextResult(Constants.Messages.Error);

FYI: Some developers prefer Enum.

Win
  • 61,100
  • 13
  • 102
  • 181
  • thanks, I used that. but maybe change the public to internal, to restrict access only to this project – Ofek Agmon May 04 '16 at 18:26
  • Since you are defining them as "const" you don't have to declare the container classes as "static". It should work just the same without it. – gafda Aug 01 '18 at 13:07
  • 2
    @gafda [static class is preferred](http://pihrt.net/roslynator/analyzer?id=RCS1102) – Alexander Dec 18 '18 at 14:42
  • 1
    Nested types should not be visible. – Sumit Joshi Dec 25 '19 at 11:36
  • @SumitJoshi Why not? The developer only having to type `Constants.` to see via IntelliSense what categories of constants are available is extremely convenient and intuitive. Combined with C# 10's global usings, it's even better. – Jacob Stamm Feb 18 '22 at 01:42
12

I usually make a distinction based on the intended audience for the message. As such, I break them into two categories.
Regardless of the category, I avoid writing the same code more than once (e.g. message strings).

Developer Messages

  • Messages displayed in unit tests, messages displayed only during debugging, or messages logged to super-detailed diagnostic files
  • Developer messages require no localization and should be written in the language of the development house.
    For example, if the development company is located in Moscow, then there's a strong argument to write developer messages in Russian.
    In practice, a lot of developers choose English.
  • Implementation options are multiple. I typically keep it simple, using fields in a static class. Note that you could have a message class for each type that will display messages, or you could have a central message class where you group multiple classes. You could have nested message groups. You could also add other types of constants for use in your code... As I mentioned, options and preferences abound.

    public static class FeatureMessages
    {
        #region Public Fields
    
        public const string ApiAccessError = 
            @"Error accessing api - check internet connection/api address";
        public const string SelectedClassFormatString = 
            @"Hello dear user – the selected class name is {0}";
    
        #endregion
    }
    

User Messages

  • Messages displayed to end users. For example, installation prompts, in the user GUI menus, on a user warning message boxes, etc.
  • These messages should be localized.
  • Implementation is simple, and will require at least a default language resource file. There are lots of resources out there that can show you how to do this, you can see a simple explanation here
Community
  • 1
  • 1
Gustavo Mori
  • 8,319
  • 3
  • 38
  • 52
6

I would generally recommend using a resource file (created from your project settings). You may want to provide some kind of wrapper if you want to make this code more testable.

drz
  • 962
  • 7
  • 22
3

Put that in a static class :

internal static class ErrorMessage
{
    public const string NoAccess = "Error accessing api - check internet connection/api address";
}

And you can reference that by using :

result = new TextResult(ErrorMessage.NoAccess);

Or, you can use a resource file.

Xiaoy312
  • 14,292
  • 1
  • 32
  • 44
  • by internal you mean I should put it in one of the classes? or in the interface file? – Ofek Agmon May 04 '16 at 18:11
  • `internal` is there to limit the usage within the same assembly(_project_). So that if you expose the whole thing as a library/dll, it will not be visible. See http://stackoverflow.com/questions/165719/practical-uses-for-the-internal-keyword-in-c-sharp – Xiaoy312 May 04 '16 at 18:13
3

Not that I disagree with @Win's answer, I believe putting Error and Hello consts which are logically related to IFeature in unrelated static class for the sake of avoiding duplication might not be appropriate approach. If the objective is to avoid duplication then I would like to achieve it in the following manner:

public abstract class Feature:IFeature
{
    public static readonly string Error = "Error accessing api...";
    public static readonly string Hello = "Hello ...{0}";

    protected IWebApi webApi;

    protected Feature(IWebApi webApi)
    {
        this.webApi = webApi;
    }
    public async Task execFeature()
    {
        var o = _execFeature();
        IResult result;
        if(o==null)
            result = new TextResult(Error);
        else 
            result = new TextResult( string.Format(Hello, o);
        result.display();
    }
    protected abstract object _execFeature();
}

So now I have achieved not only optimum minimization of code duplication, put Error and Hello where they are logically belong to. First and second Feature classes now can inherit from Feature class:

public class FirstFeature:Feature
{
    public FirstFeature(IWebApi webApi):base(webApi){}

    protected override object _execFeature ()
    {
        //your code for first Feature
        //return response if no error else return null
    }
}

public class SecondFeature:Feature
{
    public SecondFeature(IWebApi webApi):base(webApi){}

    protected override object _execFeature ()
    {
        //your code for second Feature
        //return class name[0] if no error else return null
    }
}

So that's what my design would be.

Mukesh Adhvaryu
  • 642
  • 5
  • 16
-2

Apart from the fact that your feature is displaying messages, which I think it should not do, you clearly have a problem with localization if you put messages in your code. No matter where you hold it and how you access it, you don't want to recompile to have a different language. You may not even want to recompile to fix a stupid spelling mistake.

In your code, use error codes. This can for example be enums. Then in your front end application, have a translator service (.NET has a pretty nice feature called "resources" for this).

This way, you can have different resources for different front ends (like one for French. Or maybe one for children with easier language. Or one for technical people who can handle the real deal.)

Additionally, you can also have an automated front end that can react to codes instead of having to parse error messages and throwing a fit whenever someone corrects a spelling mistake or finds a friendlier term for something.

nvoigt
  • 75,013
  • 26
  • 93
  • 142
  • Although answer is OK I think it's for the different question. Question is about best OOP design to avoid duplication and how to do it. – Mukesh Adhvaryu May 04 '16 at 19:51
  • 1
    @MukeshAdhvaryu Why do you think "don't do this, it's a bad idea" is a non-OOP answer? Sometimes, the best way to design something is not to design it or use an already existing design. "Don't reinvent the wheel" is good OOP in my book. – nvoigt May 04 '16 at 19:55
  • agreed. That's why I said answer is OK – Mukesh Adhvaryu May 04 '16 at 20:11
  • agreed. That's why I said answer is OK but don't you think "don't do this" can be better if it comes with "do this". Does your answer provide any help upon how duplication of code can be avoided using OOP? I emphasise " duplication ". – Mukesh Adhvaryu May 04 '16 at 20:19
  • @MukeshAdhvaryu What makes you think this is about code duplication? The question clearly states it's about constant strings and my answer provides a way out. Use errors *codes* and drop *all* the stuff that you guys see as duplication. My answer is not how to minimize that duplication, my answer is "drop it all, it's bad anyway. Use Codes and resources in the front end.". – nvoigt May 04 '16 at 22:37
  • please refer to answers posted here and you will know what the question is all about – Mukesh Adhvaryu May 05 '16 at 12:01
  • @MukeshAdhvaryu There is at least one answer sitting at +3 that says *exactly* the same as mine, just that it comes without any explanation *why* you should do it. And for the record, *only* yours touches OOP or code duplication at all. – nvoigt May 05 '16 at 14:03
  • What about Win's answer sitting at 8+? Regarding your comment about my answer then all I would say is if you consider abstraction, inheritance and ploymorphism as duplication then well who am I to question your opinion? Let it be. – Mukesh Adhvaryu May 05 '16 at 14:22
  • @MukeshAdhvaryu You seem to confuse the term OOP with "good programming". Yes, that answer is a good programming practice. It's not OOP at all. A public static is a fancy global constant. Nothing more. That would be a good programming practice even in functional languages. – nvoigt May 05 '16 at 14:24
  • Can I politely request you to end this topic? We are not getting anywhere with this. I took a stand based on my understanding which might be incorrect in your opinion that's all. I would rather like to engage in some productive discussion with you some other time. – Mukesh Adhvaryu May 05 '16 at 14:33