简体   繁体   中英

Issue with using Static Constructors in Derived and Base Class

I am having some trouble refactoring code. I have an appcache class which has a static constructor that will initialize some information and create the cache. It also has other methods like getobject, saveobject...

I am creating a new cache class that should inherit all the useful methods of the appcache. I also want this new cache class to have a static constructor, but instead of the section "clusterclient" which is a connection section, a different initalization.

    public class AppCache
    {
        internal static AWSCache _internalCache;

        static AppCache()
        {
            TimeSpan awsExpiration = TimeSpan.FromMinutes(int.TryParse(ConfigurationManager.AppSettings["AppCacheMinutes"], out var cacheMinutes) ? cacheMinutes : 60);

            TimeSpan localExpiration = TimeSpan.FromMinutes(int.TryParse(ConfigurationManager.AppSettings["LocalCacheMinutes"], out cacheMinutes) ? cacheMinutes : 2);

            _internalCache = new AWSCache("clusterclient", awsExpiration, localExpiration, null);
        }

     // OTHER USEFUL METHODS ...
public class OtherCache : AppCache
{
        static OtherCache()
        {
            TimeSpan awsExpiration = TimeSpan.FromMinutes(int.TryParse(ConfigurationManager.AppSettings["AppCacheMinutes"], out var cacheMinutes) ? cacheMinutes : 60);

            TimeSpan localExpiration = TimeSpan.FromMinutes(int.TryParse(ConfigurationManager.AppSettings["LocalCacheMinutes"], out cacheMinutes) ? cacheMinutes : 2);

            _internalCache = new AWSCache("otherclusterclient", awsExpiration, localExpiration, null);
        }

}

// code in other parts of the program AppCache.GetObject(key) calls -> _internalCache.GetObject(...,...,...);

My main question is basically how do I setup and refactor the AppCache class, so that I don't have to completely copy it and make a new class called OtherCache. Instead I want to inherit the "OTHER USEFUL METHODS" and also initialize statically the _internalCache differently.

I tired various things, and couldn't make it work. It always calls the base class static method and initalizes the _internalcache that way even when I use like below.

OtherCache.GetObject(key) which calls => _internalCache.SaveObject(...,...,...);

Here's an implementation which (I think) meets your desires for syntax. It uses the Singleton pattern, a base class to hide/reuse implementation details, and generics with reflection to inject the unique initialization info.

You mentioned that you can refactor the AppCache class, and this approach requires modifying it.

Singleton: The Singleton pattern provides the static access you want (while avoiding static constructors). This pattern also ensures there are separate caches for each derived type, where each cache can be initialized with unique strings as you want. I implemented Singleton using Lazy, though there are other choices.

Base class: Having a base class allows you to define all the static "OTHER USEFUL METHODS" in the base, for good code reuse. I started one for you. It works assuming that every derived cache is always of type AWSCache .

Generics/reflection: More notably IMO, it uses generics with a where clause and ultimately reflection (via Activator.CreateInstance) to find and call the derived constructor and initialize caches the way you want. This means each derived class only needs to define one line: a constructor that calls the base constructor and gives it the unique string used during cache initialization. Reflection in general can be slow, but in this case it's only ever called once when initializing the singleton.

Presented as a compiling console app:

using System;
using System.Configuration;

namespace SomeNamespace
{
    public class Program
    {
        static void Main()
        {
            // example of desired syntax - no initialization required.
            _ = AppCache.GetObject("key");
            _ = DerivedCache.GetObject("key");
        }
    }

    // note the 'where' clause.
    // we want to restrict T to always be derived from this class type, and nothing else.
    public abstract class CacheBase<T> where T : CacheBase<T>
    {
        private readonly AWSCache _internalCache;

        protected CacheBase(string cacheTypeName)
        {
            TimeSpan awsExpiration = TimeSpan.FromMinutes(int.TryParse(ConfigurationManager.AppSettings["AppCacheMinutes"], out var cacheMinutes) ? cacheMinutes : 60);
            TimeSpan localExpiration = TimeSpan.FromMinutes(int.TryParse(ConfigurationManager.AppSettings["LocalCacheMinutes"], out cacheMinutes) ? cacheMinutes : 2);
            _internalCache = new AWSCache(cacheTypeName, awsExpiration, localExpiration, null);
        }

        // very, VERY simplistic implementation of the Singleton pattern
        // sets the 'nonPublic' bool to 'true' to find the hidden constructors
        // you may want better error handling of instance creation; modify as needed
        protected static readonly Lazy<T> _lazy = new Lazy<T>(() => (T)Activator.CreateInstance(typeof(T), true));

        // implement all the "USEFUL METHODS" here, in the base class.
        // This works because all derived classes create a cache of the same type.

        private static AWSCache Cache => _lazy.Value._internalCache;

        public static object GetObject(string key)
        {
            return Cache.GetObject(key);
        }

        // etc.
    }

    // for derived classes, remember to keep the constructors private to maintain Singleton pattern.
    public class AppCache : CacheBase<AppCache>
    {
        private AppCache() : base("clusterCache") { }
    }

    public class DerivedCache : CacheBase<DerivedCache>
    {
        private DerivedCache() : base("otherClusterCache") { }
    }

    // implemented as a shell so things compile for simple console app with no references
    public class AWSCache
    {
        public AWSCache(string name, TimeSpan awsExpiration, TimeSpan localExpiration, object dunno) { }
        public object GetObject(string key) { return null; }
    }
} 

The usage looks clean, as I think you want. You can call DerivedClass.USEFULMETHOD() at any time.

Every derived class should always use itself as the generic type T . You could (and should) add some error handling to catch the case where a dev puts the wrong type into T .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM