我有如下两个类:
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类?
有几种方法可以实现这一点。其中一个已经在其他答案中提到了,那就是使用泛型。然而,这假设你的类型A
和B
甚至有任何共同之处,你可以用作共同的通用约束-例如,一个共同的基本接口。然后你可以这样做:
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)
};
}
您还可以创建一个工厂,根据某些条件(例如某些配置)创建A
或B
的实例。但是,您还需要一个通用的基本接口:
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
。
创建一个静态工厂方法并将其作为参数注入:
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
在没有await的情况下不会为任何点提供服务,因此请删除它。
我有点晚了,但是你可以使用字典和委托,它可以提供延迟初始化。
让我举个例子。首先,在创建类的实例时,我们需要创建枚举来避免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 = SomeCustomDateTime(),
Stuff = SomeCustomString()
}
},
{
FooType.B, () => new B(expireDays)
{
Created = SomeCustomDateTime()
}
}
};
return _fooBaseByType[fooType];
}
private DateTime SomeCustomDateTime() => DateTime.Now;
private string SomeCustomString() => "My custom string";
}
你可以这样使用:
FooFactory factory = new FooFactory();
A a = (A)factory.GetInstance(FooType.A, 11)();