Factory class 不支持 SOLID 原则

Factory class is not supporting SOLID principle

我有如下代码

 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 原则的开闭原则。请建议是否有更好的方法来处理这种情况。

看来我的评论被误解了,所以我们开始:

假设我们有一些简单的配置文件:

<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

现在您的工厂仍然需要做的就是读取配置文件,最终加载程序集并构建一个字典来缓存键和类型以稍微提高性能。

依赖外部文件的优势在于您不必重新发布整个应用程序,而只需重新发布那些实际 更改.

的部分

编辑:如果您的类型没有默认构造函数并且您必须提供参数,事情可能会变得复杂。然而,为了简单起见,我们假设我们一个默认构造函数。

您可能希望使其可配置,如下所示:

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()
    {

    }
}

有很多confusion around the open/closed principle, and what it should mean to your code

事实就在这里,你很好。移动到配置文件只会将代码与数据分开并隐藏意图,这会导致问题。

答案?这取决于。要务实。 "Just f**king hardcode it"。通过保留 switch 语句来明确工厂的意图。将数据和意图保持在一起,如此处所示。确保首先为缺失的 switch 个案例编写测试,并始终包含 default 个案例(即使它只是抛出异常)。

你的工厂只有一个改变的理由,并且只做一项工作。你现在的设计很好。如果你发现你经常回来,添加新的案例,那么也许你会想考虑一些其他的方法(配置文件不是答案,它只是将编辑从代码文件移动到配置文件)并重构您的解决方案。不断增加的 switch 语句并不好,与其弄清楚如何以不同的方式做到这一点,不如首先努力尝试消除该问题。请参阅下面的附录:

附录

另一种解决方法是不要让多个 类 实现相同的接口。想想其他 SOLID 原则。也许还有另一个一致性或功能边界而不是 "types of car"?也许有一些功能组可以在更具体、更细粒度的接口中指定,然后也许您可以使用 DI 将实现注入到您需要的地方,而不是一开始就必须使用工厂(其中,让我们面对现实吧,无论如何它只是一个捏造的构造函数)。另见:"favouring composition over inheritance".

我认为 Vlad 在评论中的建议是更好的方法,我在后续评论中更具体一些,但我在下面详细说明。与 不同,此方法 不会 通过将 type 参数发送到 ICarFactory 类型来将您的库耦合到使用它的特定应用程序来自应用程序本身。

换句话说,我不认为 tym32167 的答案在您每次需要添加新实现时都能让您 类 "closed for modification"。


汽车

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 (); }
}

请注意它们不受 "control coupling" 的影响,即它们不依赖外部 'magic' 值来确定工厂的内部控制流。换句话说,他们不是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 实现,这显然很容易做到,并且向 [=20] 添加一个新工厂=] 数组放在您想要的任何汽车 type 位置,并且 type 知识保留在您的特定应用程序中,与您的 Car/Factory 库分离。


与其他回复相比,为什么我的建议SOLID

  1. S: 这些工厂有 1 个职责:创建他们的汽车实现;其他提案增加了弄清楚 type 意味着什么的责任
  2. O: 您的 ICarICarFactory 可以通过添加新的实现来扩展,但是这样做时不需要修改现有的。
  3. L: 我们已经这样做了 b/c 我们正在返回 ICar 个对象,所以实现是否是一个并不重要BigCarSmallCar 或其他。
  4. I: 我们在这里通过保持界面和 类 简单来做到这一点;其他建议通过 type 参数将您的库与您的应用程序耦合,该参数具有特定于应用程序的含义
  5. D: 在这种特殊情况下,库 类 并不完全需要依赖被注入的外部信息。