工厂类不支持SOLID原则
本文关键字:原则 SOLID 不支持 工厂 | 更新日期: 2023-09-27 18:06:53
我有如下所示的代码
public interface ICar
{
void Created();
}
public class BigCar : ICar
{
public void Created()
{
}
}
public class SmallCar : ICar
{
public void Created()
{
}
}
public class LuxaryCar : ICar
{
public void Created()
{
}
}
public class CarFactory
{
public ICar CreateCar(int carType)
{
switch (carType)
{
case 0:
return new BigCar();
case 1:
return new SmallCar();
case 2:
return new LuxaryCar();
default:
break;
}
return null;
}
}
在这段代码中,我有一个返回具体实例的工厂。但每次我需要ICar接口的新实现时,我都必须更改CarFactory的CreateCar((方法。我似乎不支持SOLID原则中的开放-封闭原则。请建议是否有更好的方法来处理这种情况。
您可能希望将其配置为:
void Main()
{
// configurable array
var factories = new ICarFactory[] { new BigCarFactory() };
// create factory
var realfactory = new CarFactory(factories);
// create car
var car = realfactory.CreateCar(0);
}
public class CarFactory : ICarFactory
{
private ICarFactory[] _factories;
public CarFactory (ICarFactory[] factories)
{
_factories = factories;
}
public ICar CreateCar(int carType)
{
return _factories.Where(x=>x.SupportCar(carType)).First().CreateCar(carType);
}
public bool SupportCar(int type) => _factories.Any(x=>x.SupportCar(type));
}
public interface ICarFactory
{
ICar CreateCar(int type);
bool SupportCar(int type);
}
public class BigCarFactory : ICarFactory
{
public ICar CreateCar(int carType)
{
if(carType != 0) throw new NotSupportedException();
return new BigCar();
}
public bool SupportCar(int type) => type == 0;
}
public interface ICar
{
void Created();
}
public class BigCar : ICar
{
public void Created()
{
}
}
public class SmallCar : ICar
{
public void Created()
{
}
}
public class LuxaryCar : ICar
{
public void Created()
{
}
}
关于打开/关闭原则以及它对代码的意义,有很多困惑。
事实是,你很好。移动到配置文件只是将代码与数据分开并隐藏意图,这将导致问题。
答案是什么?这取决于情况。要务实。"只是硬编码"。通过保留switch语句来明确工厂的意图。将数据和意图紧密结合在一起,如下所示。确保您从为丢失的switch
用例编写测试开始,并始终包含一个default
用例(即使它只是抛出一个异常(。
你的工厂只有一个改变的理由,而且只做一项工作。你的设计目前还不错。如果你发现你经常回来,添加新的案例,那么也许你会想其他方法来解决这个问题(配置文件不是答案,它只是将编辑从代码文件转移到配置文件(,并重构你的解决方案。不断增加的switch语句是不好的,但与其想办法用不同的方式来做到这一点,不如先努力消除这个问题。见以下附录:
附录
另一种解决方法是不让多个类实现同一接口。想想其他SOLID原则。也许还有另一个一致性或功能边界,而不是"汽车类型"?也许有一些功能组可以在更具体、更细粒度的接口中指定,然后你可以使用DI将实现注入到你需要的地方,而不必首先使用工厂(让我们面对现实,这只是一个捏造的构造函数(。另见:"偏爱组合而非继承"。
我的评论似乎被误解了,所以我们开始:
假设我们有一些简单的配置文件:
<config>
<MyType key="1" type="My.Namespace.Ferrari"/>
<MyType key="2" type="My.Namespace.Fiat"/>
</config>
现在,您可以从该配置中读取并根据类型信息创建实例:
ICar CreateCar(int carType)
{
foreach(var typeInfo in myConfig)
{
if(typeInfo.Key == carType) return Activator.CreateInstance(Type.GetType(typeInfo.type));
}
}
现在只需调用myFactory.CreateCare(2)
即可获得Fiat
的一个实例。
这肯定是假设您已经反序列化了配置(例如,使用Linq2Xml或使用XmlSerializer
(,并且包含这些类型的程序集已经在运行时加载。如果这不适用,您应该先致电Assembly.Load
。
现在,工厂所要做的就是读取配置文件,最终加载程序集,并构建一个缓存键和类型的字典,以稍微提高性能。
依赖外部文件的优势在于,您不必重新发布整个应用程序,而只需重新发布那些实际更改的部分。
编辑:如果你的类型没有默认的构造函数,并且你必须提供args,事情可能会变得复杂。然而,为了简单起见,我们假设具有默认构造函数。
我认为Vlad在评论中的建议是更好的方法,我在后续评论中做了更具体的说明,但我在下面详细说明。与tym32167的答案不同,这种方法不会通过将type
参数从应用程序本身发送到ICarFactory
类型,将库与使用它的特定应用程序耦合起来。
换句话说,我不认为tym32167的答案在每次需要添加新的实现时都会让类"关闭以进行修改"。
汽车
public interface ICar {
string Name { get; }
}
class BigCar : ICar {
string Name {
get { return "Big Car"; }
}
}
class SmallCar : ICar {
string Name {
get { return "Small Car"; }
}
}
工厂
public interface ICarFactory {
ICar CreateCar();
}
public class BigCarFactory : ICarFactory {
ICar CreateCar() { return new BigCar (); }
}
public class SmallCarFactory : ICarFactory {
ICar CreateCar() { return new SmallCar (); }
}
请注意,他们没有受到"控制耦合"的影响,即他们不依赖外部的"魔法"值来确定工厂的内部控制流程。换句话说,它们不是ICar CreateCar(int type);
,工厂只是ICar CreateCar();
。工厂不应通过type
参数耦合到您的特定应用程序,该参数具有特定于应用程序的含义。
将它们组合在一起
现在,在你的客户端应用程序中的一些任意方法中,你有你的汽车创建逻辑,即在你的库之外(例如读取你的配置文件或其他东西(,你可以做一些事情,比如:
public List<ICar> CreateCars() {
// organize your factories based on whatever value you want for 'type'
ICarFactory[] factories = { new BigCarFactory(), new SmallCarFactory() };
List<ICar> carsList = new List<>();
while (/* more config to read */ ) {
int type = // read type from config, etc.
carsList.Add(factories[type].CreateCar());
}
return carsList;
}
现在,每次添加新的ICar
实现时,都会创建一个关联的ICarFactory
实现,这显然非常容易做到,并且在您想要的type
汽车位置向ICarFactory[] factories
阵列添加一个新工厂,并且type
知识保留在您的特定应用程序中,与您的汽车/工厂库解耦。
与其他回复相比,为什么我的提议是固体?
- S:这些工厂有一项责任:创建他们的汽车实施;其他提案增加了弄清楚
type
含义的责任 - O:您的
ICar
和ICarFactory
可以通过添加新的实现进行扩展,但在执行此操作时不需要修改现有的实现 - L:我们已经在做了,b/c我们正在返回
ICar
对象,所以实现是BigCar
还是SmallCar
或其他什么都无关紧要 - I:我们在这里通过保持接口和类的简单来做到这一点;其他建议通过
type
参数将您的库与应用程序相关联,该参数具有特定于应用程序的含义 - D:在这种特殊情况下,库类并不完全需要依赖于注入的外部信息