简体   繁体   中英

Object caching design pattern

I am wondering if there is a less painful way to write something like the following:

public class Company {
  // CEO, Programmer, and Janitor all inherit from an Employee class.
  private CEO _CacheCEO {get;} = CEOFactory.NewCEO();
  private Programmer _CacheJavaProgrammer {get;} = ProgrammerFactory.NewJavaProgrammer();
  private Programmer _CacheIOSProgrammer {get;} = ProgrammerFactory.NewIOSProgrammer();
  private Janitor _CacheJanitor {get;} = JanitorFactory.NewJanitor();
  // etc.
  public IEnumerable<Employee> GetEmployeesPresentToday() {
    List<Employee> r = new List<Employee>();
    if (ExternalCondition1) {   // value of the condition may differ on successive calls to this method
      r.Add(this._CacheCEO);
    };
    if (ExternalCondition2) {  // all conditions are external to the Company class, and it does not get notified of changes.
      r.Add(this._CacheJavaProgrammer);
    }
    if (ExternalCondition3) {
      r.Add(this._CacheIOSProgrammer);
    }
    if (ExternalCondition4) {
      r.Add(this._CacheJanitor);
    }
    // etc.
    return r;
  }

The part that bugs me here is all the private IVars. If I have (say) 30 different employees, it gets laborious. Is there a way to avoid having a separate ivar for each employee?

I am not likely to need any access to the employee objects, other than through the GetEmployeesPresentToday() method. In other words, I do not expect any code to be asking "Who is your CEO?" or the like.

However, it is important that if two different calls are made to GetEmployeesPresentToday(), and Condition1 in the above code is true each time, then the same CEO object should appear in each list.

Here is one possible answer. I'm not sure if it's better or worse than the code in the question, however.

public class Company {
  private Dictionary<string, Employee> _CacheEmployees { get;} = new Dictionary<string, Employee>();

  public Employee GetEmployee(string key, Func<Employee> factory) {
    // Existence of this method is not really annoying as it only has to be written once.
    private Dictionary<string, Employee> cache = this._CacheEmployees;
    Employee r = null;
    if (cache.ContainsKey(key)) {
      r = cache[key];
    }
    if (r == null) {
      r = factory();
      cache[key] = r;
    }
    return r;
  }

  public IEnumerable<Employee> GetEmployeesPresentToday() {
    // still somewhat messy, but perhaps better than the question code.
    List<Employee> r = new List<Employee>();
    if (ExternalCondition1) {   // value of the condition may differ on successive calls to this method
      r.Add(this.GetEmployee("CEO", CEOFactory.NewCEO));
    };
    if (ExternalCondition2) {   // all conditions are external to the Company class, and it does not get notified of changes.
      r.Add(this.GetEmployee("Java Programmer", ProgrammerFactory.NewJavaProgrammer));
    }
    if (ExternalCondition3) {
      r.Add(this.GetEmployee("IOS Programmer", ProgrammerFactory.NewIOSProgrammer));
    }
    if (ExternalCondition4) {
      r.Add(this.GetEmployee("Janitor", JanitorFactory.NewJanitor));
    }
    // etc.
    return r;
  }
}

I'd be tempted to use Lazy<T> along with Dictionary<T,V> to make a fully lazy implementation, such as:

public sealed class Company
{
    private readonly Dictionary<string, Lazy<Employee>> _cache = new Dictionary<string, Lazy<Employee>>();

    public Company()
    {
        _cache["CEO"]            = new Lazy<Employee>(CeoFactory.NewCeo);
        _cache["Janitor"]        = new Lazy<Employee>(JanitorFactory.NewJanitor);
        _cache["IosProgrammer"]  = new Lazy<Employee>(ProgrammerFactory.NewIosProgrammer);
        _cache["JavaProgrammer"] = new Lazy<Employee>(ProgrammerFactory.NewJavaProgrammer);
    }

    public IEnumerable<Employee> GetEmployeesPresentToday(bool cond1, bool cond2, bool cond3, bool cond4)
    {
        if (cond1)
            yield return _cache["CEO"].Value;

        if (cond2)
            yield return _cache["JavaProgrammer"].Value;

        if (cond3)
            yield return _cache["IosProgrammer"].Value;

        if (cond4)
            yield return _cache["Janitor"].Value;
    }

Another thing to consider is a Dictionary<string,bool> to specify which employees you want to return, rather than a load of separate booleans. Then the GetEmployeesPresentToday() method might look like this:

public IEnumerable<Employee> GetEmployeesPresentToday(Dictionary<string, bool> want)
{
    if (want["CEO"])
        yield return _cache["CEO"].Value;

    if (want["JavaProgrammer"])
        yield return _cache["JavaProgrammer"].Value;

    if (want["IosProgrammer"])
        yield return _cache["IosProgrammer"].Value;

    if (want["Janitor"])
        yield return _cache["Janitor"].Value;
}

Or you could make it more general by passing in a Predicate<string> so that the implementation of the conditional check is not exposed to GetEmployeesPresentToday() :

public IEnumerable<Employee> GetEmployeesPresentToday(Predicate<string> want)
{
    if (want("CEO"))
        yield return _cache["CEO"].Value;

    if (want("JavaProgrammer"))
        yield return _cache["JavaProgrammer"].Value;

    if (want("IosProgrammer"))
        yield return _cache["IosProgrammer"].Value;

    if (want("Janitor"))
        yield return _cache["Janitor"].Value;
}

Note that to avoid the possibility of typos, you might want to use constants for the strings instead of typing them in multiple places. Then if you spell the constant wrong, you'll get a compile error.

Also note that I slightly changed the code to make sure it compiled. I used these test classes:

public abstract class Employee
{
}

public sealed class Ceo: Employee
{
}

public abstract class Programmer: Employee
{
}

public sealed class JavaProgrammer: Programmer
{
}

public sealed class IosProgrammer: Programmer
{
}

public sealed class Janitor: Employee
{
}

public static class ProgrammerFactory
{
    public static Programmer NewJavaProgrammer()
    {
        return new JavaProgrammer();
    }

    public static Programmer NewIosProgrammer()
    {
        return new IosProgrammer();
    }
}

public static class CeoFactory
{
    public static Ceo NewCeo()
    {
        return new Ceo();
    }
}

public static class JanitorFactory
{
    public static Janitor NewJanitor()
    {
        return new Janitor();
    }
}

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