Click here to Skip to main content
15,886,071 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
Suppose, I have the base abstract class Function that looks like this:
C#
public abstract class Function
{
   public abstract string Invoke(string[] args);
}

And also I have a child sub-class DeterministicFunction that looks like this:
C#
public abstract class DeterministicFunction : Function
{
    private readonly Dictionary<string[], string> _cache;

    public sealed override string Invoke(string[] args)
    {
         if (args?.Length > 0)
         {
             string fnResult;
             if (_cache.TryGetValue(args, out fnResult))
                 return fnResult;

             fnResult = GetResult(args);
             _cache[args] = fnResult;
             return fnResult;
         }

         return GetResult(args);
    }

    protected abstract string GetResult(string[] args);
}

All functions can be either **deterministic** or **non-deterministic**.
Deterministic function always returns same result for same combination of arguments, so I decided to optimize it by putting result to cache so if expensive function is invoked with already invoked arguments it could cache result and retrieve it faster. I also have a bunch of non-deterministic functions related to date & time. They are non-deterministic cause they return
different result with every invocation. My base class for date & time functions:
C#
public abstract class DateTimeFunction : Function
{
     private CultureInfo _culture;

     protected DateTimeFunction(CultureInfo culture)
     {
         _culture = culture;
     }

     protected CultureInfo Culture => _culture;
}

public class UtcNowFunction : DateTimeFunction
{
     // skip constructor code, too long to type

     public override string Invoke(string[] args)
     {
         DateTime now = DateTime.NowUtc;
         string result = now.ToString(Culture);

         if (args?.Length > 0)
         {
            string format = args[0];
            result = now.ToString(format, Culture);
         }

         return result;
     }
}

My question is - do I need to create a base class for all non-deterministic functions? For now, I don't see reasons to add a new abstract level, but it would be logical to separate them into two major groups. If I create a new abstract class NonDeterministicFunction - it's going to be empty. What should I do??

What I have tried:

I did not create a sub-class NonDeterministicFunction cause I for now, only for now I see no reasons to do it. But I do care about logical design, and it would be logical to separate abstract entity to different groups
Posted
Updated 22-Mar-19 11:29am

1 solution

The only reason I would create a shared base class for NonDeterministicFunction is if there is logic that would/could be shared across all implementations of these non deterministic functions.

You can still use your abstract class as the base class as an option as well.

Personally, what you've got I would have implemented it as an interface (the invoke method) and use an abstract class to serve as the base class (if needed) to only host the common methods across all classes that inherit from it.

If you use your abstract class of Function to serve as base for both NonDeterministic and Deterministic functions you are exposing implementation that only concerns one side of those functions (Ex: deterministic classes would be able to see/implement nondeterministic methods (potentially) if you use the abstract Function class for both types of deterministic functions).

So i would put the NonDeterministic specific implementation into INonDeterministicFunctions interface and the rest in IDeterministicFunctions. Then any shared logic would go into the Function base class. Then any shared logic specific to NonDeterministicFunctions could go into a NonDeterministic base class.

If i understood you right, this is something like what i would refactor to

C#
public IFunction
{
    string Invoke(string[] args)
}

public abstract class BaseFunction
{
    // common logic shared across function types here
}

public abstract class NonDeterministicFunction : BaseFunction
{
   // common non-deterministic specific functionality here
}

public abstract class DeterministicFunction : BaseFunction
{
   // common deterministic specific functionality here
}


public abstract class DateTimeFunction : DeterministicFunction
{
     private CultureInfo _culture;

     protected DateTimeFunction(CultureInfo culture)
     {
         _culture = culture;
     }

     protected CultureInfo Culture => _culture;
}

public class UtcNowFunction : DateTimeFunction, IFunction
{
     // skip constructor code, too long to type

     public string Invoke(string[] args)
     {
         DateTime now = DateTime.NowUtc;
         string result = now.ToString(Culture);

         if (args?.Length > 0)
         {
            string format = args[0];
            result = now.ToString(format, Culture);
         }

         return result;
     }
}


The reason for the interface is that it describes the implementation of what the class should be for whatever inherits from it. Given the implementation is specific to the class, to me it seems best served as an interface.

You may find that the NonDeterministic/Deterministic classes aren't needed and you can just make do with 1 BaseFunction class and have interfaces for the deterministic nondeterministic specific implementations.

But as im sure you know there are 10 different ways to do everything so my suggestion may not be 100% what you want and possibly over engineered.
 
Share this answer
 
Comments
NickResh777 22-Mar-19 17:51pm    
Thanks! About these lines - If you use your abstract class of Function to serve as base for both NonDeterministic and Deterministic functions you are exposing implementation that only concerns one side of those functions (Ex: deterministic classes would be able to see/implement nondeterministic methods (potentially) if you use the abstract Function class for both types of deterministic functions).
But I put deterministic logic strictly to the [DeterministicFunction] class and the non-deterministic logic to [NonDeterministicFunction] - they will not intersect, I mean their interactions will not meet. As for interface [IFunction] - yes, I thought about it - that looks neat in terms of clean interface
David_Wimbley 23-Mar-19 3:23am    
Ah ok, this may have been me getting confused about your question. So yea, sounds like you had the base classes separated out, I guess I was adding if there is shared logic between both, you could go a step further and have a BaseFunction class - then NonDeterministicFunction base class would inherit from BaseFunction...then all your NonDeterministic functions would inherit from that non-determin function base class.

Ex:

public class NonDeterministicFunction : BaseFunction 
// Skip code and stuff
public class MyFunction : NonDeterministicFunction, IFunction


Then you'd have shared logic from base function accessible by MyFunction (in theory).
BillWoodruff 23-Mar-19 2:44am    
+5
Maciej Los 24-Mar-19 17:41pm    
5ed!

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900