工厂类不支持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原则中的开放-封闭原则。请建议是否有更好的方法来处理这种情况。

工厂类不支持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知识保留在您的特定应用程序中,与您的汽车/工厂库解耦。


与其他回复相比,为什么我的提议是固体?

  1. S:这些工厂有一项责任:创建他们的汽车实施;其他提案增加了弄清楚type含义的责任
  2. O:您的ICarICarFactory可以通过添加新的实现进行扩展,但在执行此操作时不需要修改现有的实现
  3. L:我们已经在做了,b/c我们正在返回ICar对象,所以实现是BigCar还是SmallCar或其他什么都无关紧要
  4. I:我们在这里通过保持接口和类的简单来做到这一点;其他建议通过type参数将您的库与应用程序相关联,该参数具有特定于应用程序的含义
  5. D:在这种特殊情况下,库类并不完全需要依赖于注入的外部信息