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