工厂类不支持SOLID原则

时间:2016-09-14 10:31:17

标签: c# oop design-patterns solid-principles

我的代码如下所示

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

4 个答案:

答案 0 :(得分:5)

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

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

    }
}

答案 1 :(得分:4)

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

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

答案是什么?这取决于。务实。 "只是f **王硬编码"。保持switch语句,使工厂的意图明确。保持数据和意图紧密相连,如此处所示。确保首先为缺少switch个案例编写测试,并始终包含default个案(即使它只是抛出异常)。

您的工厂只有一个改变的理由,只做一份工作。你现在的设计就好了。如果你发现你经常回来,添加新的案例,那么你可能想要考虑其他方式(配置文件不是答案,它只是将编辑移动到配置来自代码文件的文件)并重构您的解决方案。不断增加的转换语句并不好,但不是弄清楚如何以不同的方式做到这一点,而是花费精力试图从一开始就消除这个问题。见下面的附录:

附录

另一种方法是没有多个类实现相同的接口。想想其他SOLID原则。也许还有另一种一致性或功能边界,而不是"类型的汽车"?也许有一组功能可以在更多,特定,细粒度的界面中指定,然后你可能会使用DI将实现注入你需要的地方,而不是必须首先使用工厂(其中,让我们面对它,无论如何只是一个捏造的构造函数)。另请参阅:"赞成合成而非继承"。

答案 2 :(得分:0)

似乎我的评论被误解了,所以我们走了:

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

<config>
  <MyType key="1" type="My.Namespace.Ferrari"/>
  <MyType key="2" type="My.Namespace.Fiat"/>
</config>

现在,您可以从该配置中读取并根据type-in​​fo:

创建实例
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,那么事情可能会变得复杂。但是为了简单起见,我们假设我们拥有一个默认构造函数。

答案 3 :(得分:0)

我认为弗拉德在评论中提出的建议是更好的方法,我在后续评论中提出了更具体的建议,但我在下面详细阐述。与tym32167's answer不同,此方法通过从应用程序发送到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实施,这显然非常容易实现,并且您向ICarFactory[] factories添加了一个新工厂数组在您想要的任何车辆type位置,type知识保留在您的特定应用程序中,与您的汽车/工厂库分离。

与其他回复相比,为什么我的提案 SOLID

  1. S:这些工厂有1个责任:创建他们的汽车实施;其他提案增加了确定type意味着什么
  2. 的责任
  3. O:您的ICarICarFactory可以通过添加新实施进行扩展,但在执行此操作时不需要修改现有实现。
  4. L:我们已经在执行此b / c我们正在返回ICar个对象,因此无论实现是BigCar还是{{}} {1}}或其他什么。
  5. 我:我们通过保持界面和类简单来做到这一点;其他提案通过SmallCar参数将您的资源库与您的应用程序结合,该参数具有应用程序特定的含义
  6. D:在这种特殊情况下,库类并不需要依赖注入的外部信息。