【问题标题】:How to refactor this code with two same class name and same helper methods with different return types?如何使用两个相同的类名和具有不同返回类型的相同辅助方法重构此代码?
【发布时间】:2022-06-10 18:57:11
【问题描述】:

我有两个类如下:

public async A GernerateStuff(int expireDays = 15)
{
    using var randomNumberGenerator = RandomNumberGenerator.Create();
    var randomBytes = new byte[64];
    var now = DateTime.UtcNow;
    randomNumberGenerator.GetBytes(randomBytes);
    return new A
    {
        Stuff = Convert.ToBase64String(randomBytes),
        Created = now,
        Expires = now.AddDays(expireDays)
    };
}

public async B GernerateStuff(int expireDays = 10)
{
    using var randomNumberGenerator = RandomNumberGenerator.Create();
    var randomBytes = new byte[64];
    var now = DateTime.UtcNow;
    randomNumberGenerator.GetBytes(randomBytes);

    return new B
    {
        Stuff = Convert.ToBase64String(randomBytes),
        Created = now,
        Expires = now.AddDays(expireDays)
    };
}

public class A
{
    public string Stuff{ get; set; }
    public DateTime Created { get; set; }
    public DateTime Expires { get; set; }
}

public class B
{
    public string Stuff{ get; set; }
    public DateTime Created { get; set; }
    public DateTime Expires { get; set; }
}

限制是:我不能只创建一个类而不是两个单独的类 A 和 B,因为它们在用法上有很大差异。

现在,我的问题是:我如何清理这段代码,同时拥有 A 类和 B 类,但 GernerateStuff 的单一方法?

我可以创建这样的界面:

public class A : IInterface
{
}

public class B : IInterface
{

}

public interface IInterface
{
    public string Stuff{ get; set; }
    public DateTime Created { get; set; }
    public DateTime Expires { get; set; }
}

那么,问题是public async IInterface GernerateStuff(int expireDays = 15) 签名如何处理 A 类和 B 类?

【问题讨论】:

  • 我试图这样做,但是对于公共异步 IInterface GernerateStuff(int expireDays = 15) 签名,我将如何返回一个可以同时处理 A 类和 B 类的接口类型?
  • 只用一个,完全一样。不要过度设计。
  • 考虑一下,您的方法可以移动到静态类,因为它似乎不依赖于 A 或 B 的任何实例级别信息。然后您可以将其设为通用方法,即 T GenerateStuff (int expireDays)
  • 是的,我同意@PabloCaballero。不要过度设计它。
  • 我不同意 Pablo Caballero 和 Francis Decena 的观点。分离类型不是过度工程。这可能有很好的理由,例如未来的变化是可以预料的。

标签: c# oop refactoring


【解决方案1】:

有几种方法可以实现这一目标。其他答案中已经提到了一个,即使用泛型。但是,这假设您的类型 AB 确实有任何共同点,您可以将其用作通用通用约束 - 例如通用基础接口。然后你可以这样做:

public async T GernerateStuff<T>(int expireDays = 15) where T: new(), MyInterface
{
    using var randomNumberGenerator = RandomNumberGenerator.Create();
    var randomBytes = new byte[64];
    var now = DateTime.UtcNow;
    randomNumberGenerator.GetBytes(randomBytes);
    return new T
    {
        Stuff = Convert.ToBase64String(randomBytes),
        Created = now,
        Expires = now.AddDays(expireDays)
    };
}

您还可以创建一个工厂,根据某些条件创建 AB 的实例 - 例如一些配置。但是,您还需要一个通用的基础接口:

public async MyInterface GernerateStuff(int expireDays = 15)
{
    using var randomNumberGenerator = RandomNumberGenerator.Create();
    var randomBytes = new byte[64];
    var now = DateTime.UtcNow;
    randomNumberGenerator.GetBytes(randomBytes);
    return CreateTheThing(Convert.ToBase64String(randomBytes), now, now.AddDays(expireDays));
}

MyInterface CreateTheThing(string stuff, DateTime created, DateTime expires)
{
    if(...)
        return new A { ... }
    else if(...)
        return new B { ... }
    return new C { ... }
}

此解决方案的优点是,当您向工厂添加新类型时,您无需更改客户端逻辑。您只需通过引入new C { ... } 来更改工厂本身。此外,客户端无法提供任何实际上不起作用的类型,因为它们根本不提供任何类型信息。

顺便说一句,你的方法没有await 任何东西,所以没有理由让它成为async

【讨论】:

    【解决方案2】:

    创建一个静态工厂方法并将其作为参数注入:

    public static A CreateA(byte[] stuff, DateTime now, DateTime expires)
        => new A{  Stuff = stuff,   Created = now,  Expires = expires  }
    
    ...
    public static T GernerateStuff<T>(Func<byte[], DateTime, DateTime, T> ctor, int expireDays = 15)
    {
        using var randomNumberGenerator = RandomNumberGenerator.Create();
        var randomBytes = new byte[64];
        var now = DateTime.UtcNow;
        randomNumberGenerator.GetBytes(randomBytes);
        return ctor(    
            Convert.ToBase64String(randomBytes),
            now,
            now.AddDays(expireDays)
        ;
    }
    ...
    var a = GernerateStuff(CreateA);
    

    但是如果A类和B类是相同的,为什么不使用同一个类呢?这是一种明确的代码气味,您可能应该考虑如何对问题进行更多建模。另外,async 在没有等待的情况下不会起到任何作用,所以摆脱它。

    【讨论】:

      【解决方案3】:

      我在聚会上有点晚了,但是您可以使用带有代理的字典,它可以提供延迟初始化。

      让我举个例子。首先,我们需要在创建类实例时进行枚举以避免if else 语句:

      public enum FooType
      {
          A, B
      }
      

      那么我们需要一个抽象基类,它对所有派生类都具有相同的行为:

      public abstract class FooBase
      {
          public string? Stuff { get; set; }
          public DateTime Created { get; set; }
          public DateTime Expires { get; set; }
      }
      

      派生类看起来像这样:

      public class A : FooBase
      {
          public A(int expireDays)
          {
          }
      
          public override string ToString() => $"I am A class";
          
      }
      
      public class B : FooBase
      {
          public B(int expireDays)
          {
          }
      
          public override string ToString() => $"I am B class";
      }
      

      那么工厂看起来像这样:

      public class FooFactory
      {
          Dictionary<FooType, Func<FooBase>> _fooBaseByType;
      
          public Func<FooBase> GetInstance(FooType fooType, int expireDays) 
          {
              _fooBaseByType = new Dictionary<FooType, Func<FooBase>>
              {
                  { FooType.A, () => new A(expireDays) { Created = DateTime.Now} },
                  { FooType.B, () => new B(expireDays) { Created = DateTime.Now} }
              };
      
              return _fooBaseByType[fooType];
          }
      }
      

      你可以这样使用它:

      FooFactory factory = new FooFactory();
      A a = (A)factory.GetInstance(FooType.A, 11)();
      

      【讨论】:

        猜你喜欢
        • 2020-03-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多