利用开闭原则 (SOLID)
Utilizing Open Closed Principle (SOLID)
我看过 SOLID Open Close Principle 的几个示例。这些解释通常很清楚。
但是我还有1个疑问,那就是我们如何在不使用条件语句的情况下初始化那些不同的classes?
示例代码如下:
public enum PreferredMeal
{
Vegetarian = 1,
NonVegetarian = 2
}
public class Customer
{
public string Name { get; set; }
public PreferredMeal PreferredMeal { get; set; }
}
public interface IMealGenerator
{
List<Meal> GenerateMeals(Customer customer);
}
public class VegetarianMealGenerator : IMealGenerator
{
public override List<Meal> GenerateMeals(Customer customer)
{
// Some codes here
}
}
public class NonVegetarianMealGenerator : IMealGenerator
{
public override List<Meal> GenerateMeals(Customer customer)
{
// Some codes here
}
}
假设我得到了以下数据,我被要求阅读这些数据并为所有顾客制作餐点。
Input(CustomerName, PreferredMeal):
Customer1,1
Customer2,1
Customer3,2
我们是不是也要使用 if 语句来识别哪个 class 实现了 MealGenerator 来根据客户实例化,如下所示?
// Let's assume this function is called after all customers data has been read
// And those data is passed here
public void GenerateCustomerMeals(List<Customer> customers)
{
foreach (var customer in customers)
{
if (customer.PreferredMeal == PreferredMeal.Vegetarian)
new VegetarianMealGenerator().GenerateMeals(customer);
else if (customer.PreferredMeal == PreferredMeal.NonVegetarian)
new NonVegetarianMealGenerator().GenerateMeals(customer);
}
}
如果是这样,那么GenerateCustomerMeals似乎不满足开闭原则。有没有更好的 SOLID 方法来做到这一点? :)
如果您有多个实现,并且需要在它们之间切换,一种选择是提供一个额外的实现,允许您在它们之间切换。这样 SOLID 仍然被保留,因为路由机制对消费代码是隐藏的。
public class RoutingMealGenerator : MealGenerator
{
public override List<Meal> GenerateMeals(Customer customer)
{
if (customer.PreferredMeal == PreferredMeal.Vegetarian)
return new VegetarianMealGenerator().GenerateMeals(customer);
else if (customer.PreferredMeal == PreferredMeal.NonVegetarian)
return new NonVegetarianMealGenerator().GenerateMeals(customer);
}
}
更好的选择是使用依赖注入框架,例如Autofac, that supports implementation selection based on keys。
这可以允许针对每个键单独注册服务,然后进行服务查找安排,例如:
public class PreferenceRoutingMealGenerator : MealGenerator
{
IIndex<PreferredMeal, MealGenerator> _serviceLookup;
public PreferenceRoutingMealGenerator( IIndex<PreferredMeal, MealGenerator> serviceLookup )
{
_serviceLookup = serviceLookup;
}
public override List<Meal> GenerateMeals(Customer customer)
{
MealGenerator gen = _serviceLookup[customer.PreferredMeal];
return gen.GenerateMeals(customer);
}
}
how do we initialize those different classes without using the conditional statement?
条件语句并不邪恶。当我们需要将某些条件(您的示例中的 PreferredMeal)映射到相应的实现(IMealGenerator 接口)时,这是必要的,switch
语句也是如此。
您的代码中的问题是您在将要使用它的方法中构建 IMealGenerator
的实现。这是不正确的,因为在大多数情况下,您会有一些方法,例如 GenerateCustomerMeals
。这些方法不应该知道如何将 PreferredMeal
映射到 IMealGenerator
的实现。只有 class 知道映射是这样的 MealGeneratorFactory
:
class MealGeneratorFactory : IMealGeneratorFactory
{
IMealGenerator GetMealGenerator(Customer customer)
{
// if/switch here
}
}
你所有的方法,比如 GenerateCustomerMeals
都依赖于 IMealGeneratorFactory
,得到一个 IMealGenerator
并使用它。
依赖注入会让事情变得更简单,但结论是一样的。
我看过 SOLID Open Close Principle 的几个示例。这些解释通常很清楚。
但是我还有1个疑问,那就是我们如何在不使用条件语句的情况下初始化那些不同的classes?
示例代码如下:
public enum PreferredMeal
{
Vegetarian = 1,
NonVegetarian = 2
}
public class Customer
{
public string Name { get; set; }
public PreferredMeal PreferredMeal { get; set; }
}
public interface IMealGenerator
{
List<Meal> GenerateMeals(Customer customer);
}
public class VegetarianMealGenerator : IMealGenerator
{
public override List<Meal> GenerateMeals(Customer customer)
{
// Some codes here
}
}
public class NonVegetarianMealGenerator : IMealGenerator
{
public override List<Meal> GenerateMeals(Customer customer)
{
// Some codes here
}
}
假设我得到了以下数据,我被要求阅读这些数据并为所有顾客制作餐点。
Input(CustomerName, PreferredMeal):
Customer1,1
Customer2,1
Customer3,2
我们是不是也要使用 if 语句来识别哪个 class 实现了 MealGenerator 来根据客户实例化,如下所示?
// Let's assume this function is called after all customers data has been read
// And those data is passed here
public void GenerateCustomerMeals(List<Customer> customers)
{
foreach (var customer in customers)
{
if (customer.PreferredMeal == PreferredMeal.Vegetarian)
new VegetarianMealGenerator().GenerateMeals(customer);
else if (customer.PreferredMeal == PreferredMeal.NonVegetarian)
new NonVegetarianMealGenerator().GenerateMeals(customer);
}
}
如果是这样,那么GenerateCustomerMeals似乎不满足开闭原则。有没有更好的 SOLID 方法来做到这一点? :)
如果您有多个实现,并且需要在它们之间切换,一种选择是提供一个额外的实现,允许您在它们之间切换。这样 SOLID 仍然被保留,因为路由机制对消费代码是隐藏的。
public class RoutingMealGenerator : MealGenerator
{
public override List<Meal> GenerateMeals(Customer customer)
{
if (customer.PreferredMeal == PreferredMeal.Vegetarian)
return new VegetarianMealGenerator().GenerateMeals(customer);
else if (customer.PreferredMeal == PreferredMeal.NonVegetarian)
return new NonVegetarianMealGenerator().GenerateMeals(customer);
}
}
更好的选择是使用依赖注入框架,例如Autofac, that supports implementation selection based on keys。
这可以允许针对每个键单独注册服务,然后进行服务查找安排,例如:
public class PreferenceRoutingMealGenerator : MealGenerator
{
IIndex<PreferredMeal, MealGenerator> _serviceLookup;
public PreferenceRoutingMealGenerator( IIndex<PreferredMeal, MealGenerator> serviceLookup )
{
_serviceLookup = serviceLookup;
}
public override List<Meal> GenerateMeals(Customer customer)
{
MealGenerator gen = _serviceLookup[customer.PreferredMeal];
return gen.GenerateMeals(customer);
}
}
how do we initialize those different classes without using the conditional statement?
条件语句并不邪恶。当我们需要将某些条件(您的示例中的 PreferredMeal)映射到相应的实现(IMealGenerator 接口)时,这是必要的,switch
语句也是如此。
您的代码中的问题是您在将要使用它的方法中构建 IMealGenerator
的实现。这是不正确的,因为在大多数情况下,您会有一些方法,例如 GenerateCustomerMeals
。这些方法不应该知道如何将 PreferredMeal
映射到 IMealGenerator
的实现。只有 class 知道映射是这样的 MealGeneratorFactory
:
class MealGeneratorFactory : IMealGeneratorFactory
{
IMealGenerator GetMealGenerator(Customer customer)
{
// if/switch here
}
}
你所有的方法,比如 GenerateCustomerMeals
都依赖于 IMealGeneratorFactory
,得到一个 IMealGenerator
并使用它。
依赖注入会让事情变得更简单,但结论是一样的。