C#设计模式之工厂模式
文章目录
本篇博客将介绍简单工厂,工厂方法和抽象工厂,之所以放在一起是因为,这三个模式存在着千丝万缕的联系,通过梳理清晰他们之间的逻辑,可以对这三个模式有更加深刻的理解。
简单工厂模式
简单工厂模式并没有被记录在经典设计模式之中,但是它确实工厂方法模式和抽象工厂模式的基础,可以认为工厂方法和抽象工厂就是在简单工厂的基础上发展过来的。
模式分类
创建型设计模式。
模式产生的原因
在软件设计中,我们常会有这样的需求:创建一个对象并使用它。如果我们直接将创建对象的代码连同使用的代码写在一起,毫无疑问我们将代码写死了,我们只能使用我们当前创建的对象,一旦当我们想使用这个对象的子类或者与其相同父类的其他子类时,我们就必须修改我们的代码,这在面向对象的软件开发中是应当尽力避免的。
为了避免这种情况,我们就需要将创建对象和使用对象之间进行解耦,为此,我们引入一个特殊的类来帮助我们完成对象的创建,这个类就是工厂类。
模式灵感的来源
从名字就可以看出,工厂模式灵感的来源来自于我们日常生活中的工厂。在现实生活中,工厂只负责生产商品,而我们消费者只负责使用购买来的商品即可,我们并不需要自己去生产商品,我们需要什么商品只需要通过途径告知工厂即可。
工厂模式所做的事情和上面举的例子是类似的。在实际的软件开发中,我们只需要告知工厂我们需要创建什么对象,工厂就会将创建好的对象返回给我们,而我们只需要使用这个对象即可。
模式类图
由上图我们可以分析得出:简单工厂模式包含三个对象:
Factory(工厂对象):
工厂对象是简单工厂设计模式中的核心对象,它负责创建所有产品实例的内部逻辑,工厂类可以被外界直接调用,创建并返回所需的产品对象,且一般是一个静态类。
Product(抽象产品对象):
抽像产品对象是所有具体产品对象的父类,它的出现将工厂类和具体的产品对象分离,属于使用了依赖倒置的原则。使得工厂只需要定义一个方法就可以处理所有的同类产品。
ConcreteProduct(具体产品对象):
工厂对象的目标产品对象,每一个具体的产品对象都需要继承抽像产品对象。
经典代码实现
Factory类:
namespace CSharpDesignPatternsLearn
{
public static class Factory
{
public static Chart CreateChart(string chartName)
{
Chart chart = null;
if (chartName.Equals("PieChart"))
{
chart = new PieChart();
chart.ChartName = "PieChart";
}
return chart;
}
}
}
Product类:
using System;
namespace CSharpDesignPatternsLearn
{
public abstract class Chart
{
public string ChartName;
public void SpeakChartName()
{
Console.WriteLine($"{ChartName}");
}
}
}
ConcreteProduct类:
namespace CSharpDesignPatternsLearn
{
public class PieChart : Chart
{
}
}
Program类:
namespace CSharpDesignPatternsLearn
{
internal class Program
{
public static void Main(string[] args)
{
Chart chart = Factory.CreateChart("PieChart");
chart.SpeakChartName();
}
}
}
简单工厂模式的总结(优点和缺点最好在使用中自行体会)
简单工厂模式提供了专门的工厂类用于创建对象,将对象的创建和对象的使用分离,他作为一种最简单的工厂模式在软件开发中被较为广泛的应用。
简单工厂模式的优点
- 简单工厂模式实现了对象创建和使用的分离。
- 如果我们将我们的创建任务进一步封装为配置文件,我们则可以在不修改任何代码的情况下增添性的产品类,进一步提升系统的灵活性。
简单工厂模式的缺点
- 简单工厂模式中的工厂击中了所有产品的创建逻辑,一旦出现问题,整个系统都会受到影响。
- 使用简单工厂模式势必会增加系统中类的个数,增加了系统的复杂度和理解难度。
- 系统拓展困难,一旦添加新的产品往往需要修改之前的工厂代码,违背了开闭原则。且当产品过多时不利于系统的维护。
- 简单工厂模式使用静态类和方法,这注定了简单工厂模式不能形成继承的等级结构,对于复杂需求的实现较为困难且拓展性差。
工厂方法模式
在使用简单工厂的时候,我们发现如果我们想要添加新的产品,我们就需要修改原有的工厂类,这是违反开闭原则的。为了可以随时随地的添加新的产品且不修改原有的工厂类,我们就发展出了工厂方法这种模式。
模式分类
创建型设计模式。
模式产生的原因
就如同上面提到的,我们不希望当我们想要增加一个新产品时需要改变原有工厂类的代码,我们会更希望以增加一个新类的方式解决这个问题。
我们现在回看简单工厂,简单工厂中我们将不同产品的生产代码写在了一起,也就是说一个工厂承担了多个产品的生产,但这不是问题的关键。问题的关键在于简单工厂中的产品之间往往没有什么关系,也就说生产不同的产品是不同的行为,这样导致工厂类中存在多个变化点,那么我们就需要针对变化点对工厂类进行解耦。
我们希望一个工厂只专注于生产一种产品,这样当我们想要添加新产品时,我们只需要添加新工厂即可,这就是工厂方法模式的核心思想。
模式灵感的来源
在现实生活中,工厂往往也具有单一职责性,也就是说往往一个工厂只会生产一种或者一系列的产品,生产的产品之间是有联系的。这不光光是工厂方法的灵感来源,也是抽象工厂的灵感来源。
模式类图
经过上面的分析,我们得出工厂方法模式应当包含4个对象:
Product(抽象产品):
可以是接口,也可以是一个抽象类,抽象产品是所有具体产品的父类。
ConcreteProduct(具体产品):
实现了或者继承了抽象产品类,某种类型的具体产品由专门的具体工厂创建,具体产品和具体工厂之间一一对应。
Factory(抽象工厂):
抽象工厂是工厂方法模式的核心,所有的具体工厂都需要实现或者继承抽象工厂。
ConcreteFactory(具体工厂):
具体工厂继承或者实现抽象工厂,负责具体的产品生产。
经典代码实现
这里使用简单工厂模式的实例代码做修改。
Product(抽象产品):
namespace CSharpDesignPatternsLearn.FactoryMethod
{
public interface IProduct
{
}
}
ConcreteProduct(具体产品):
using System;
namespace CSharpDesignPatternsLearn.FactoryMethod
{
public class ProductA : IProduct
{
public ProductA()
{
Console.WriteLine("ProductA");
}
}
}
using System;
namespace CSharpDesignPatternsLearn.FactoryMethod
{
public class ProductB : IProduct
{
public ProductB()
{
Console.WriteLine("ProductB");
}
}
}
Factory(抽象工厂):
namespace CSharpDesignPatternsLearn.FactoryMethod
{
public interface IFactory
{
IProduct CreateProduct();
}
}
ConcreteFactory(具体工厂):
namespace CSharpDesignPatternsLearn.FactoryMethod
{
public class ProductAFactory : IFactory
{
public IProduct CreateProduct()
{
return new ProductA();
}
}
}
namespace CSharpDesignPatternsLearn.FactoryMethod
{
public class ProductBFactory : IFactory
{
public IProduct CreateProduct()
{
return new ProductB();
}
}
}
工厂方法模式总结
工厂方法模式的优点
- 在工厂方法模式中,工厂方法负责用来创建客户所需要的产品,同时向客户隐藏了创建细节,客户只需要知道对于产品的工厂即可。
- 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键,工厂方法模式之所以叫做多态工厂模式,就是因为所有的具体工厂都具有同一个抽象父类。
- 工厂方法模式的另一个优点就是,当我们想要创建一个新的产品时,我们只需要新建一个工厂类即可,不需要更改之前已经写好的代码。
工厂方法模式的缺点
- 对工厂多了一层抽象,增加了系统抽象性和理解难度。
- 在添加新产品时需要创建一个新的工厂,产品和工厂之间需要保证一一对应。这会大量增加系统的类的数量。
抽象工厂模式
其实工厂方法模式就已经是一个相当不错的模式了,但为了改良其存在的一点小缺陷,抽象工厂模式出现了。
模式分类
创建型设计模式。
模式产生的原因
在工厂方法模式中,我们发现每当我们想要增加一个新的产品时,不论这个产品是不是和之前某个产品是同族的,我们可以优化工厂方法,让工厂方法的工厂类不再专注于生产一个产品,而是让其生产一个产品族的产品。
模式灵感的来源
在现实生活中,工厂往往也具有单一职责性,也就是说往往一个工厂只会生产一种或者一系列的产品,生产的产品之间是有联系的。这不光光是工厂方法的灵感来源,也是抽象工厂的灵感来源。
模式类图
经典代码实现
需求:
抽象工厂模式最早的应用之一是用来创建在不同操作系统的图形环境下都能够运行的应用程序,例如同时支持Windows和Linux系统。在每一个操作系统中,都有一个由图形构件组成的构建家族,可以通过一个抽象角色给出功能定义,而由具体子类给出不同操作系统下的具体实现,现在系统中包含了两个产品等级结构,分别是Button和Text,同时包含两个产品族Windows和Linux,请使用抽象工厂模式设计一个系统满足以上条件。
抽象工厂类:
public interface IGUIFactory
{
IButtonComponent CreateButtonComponent();
ITextComponent CreateTextComponent();
}
具体工厂类:
namespace NewFactory.AbstructFactory.Question7
{
public class WindowsGUIFactory : IGUIFactory
{
public IButtonComponent CreateButtonComponent()
{
return new WindowsButton();
}
public ITextComponent CreateTextComponent()
{
return new WindowsText();
}
}
}
namespace NewFactory.AbstructFactory.Question7
{
public class LinuxGUIFactory : IGUIFactory
{
public IButtonComponent CreateButtonComponent()
{
return new LinuxButton();
}
public ITextComponent CreateTextComponent()
{
return new LinuxText();
}
}
}
抽象产品类:
namespace NewFactory.AbstructFactory.Question7
{
public interface IButtonComponent
{
}
}
namespace NewFactory.AbstructFactory.Question7
{
public interface ITextComponent
{
}
}
具体产品类:
这里可以看到我多提了两个父类,原因是可以通过这个父类和反射来减少代码的书写。
namespace NewFactory.AbstructFactory.Question7
{
public class WindowsButton : IButtonComponent, IWindows
{
}
}
namespace NewFactory.AbstructFactory.Question7
{
public class WindowsText : ITextComponent, IWindows
{
}
}
namespace NewFactory.AbstructFactory.Question7
{
public class LinuxButton : ILinux, IButtonComponent
{
}
}
namespace NewFactory.AbstructFactory.Question7
{
public class LinuxText : ILinux, ITextComponent
{
}
}
抽象工厂模式总结
虽然抽象工厂模式是工厂方法改进而来的,但当我们为抽象工厂中的产品族提出一个公共父类,并让抽象工厂创建这个父类对象时,你就会发现抽象方法又变成了工厂方法模式。
抽象工厂模式的优点:
- 创建对象和使用对象隔离。
- 抽象工厂增加一个产品族非常方便,不用修改已有系统,符合原则。
- 当一个产品族中的多个对象被设计成工作在一起时,它可以保证客户端只使用一个产品族中的对象。
抽象工厂模式的缺点:
- 当用户想要横向增加一个产品等级结构时,需要对原系统做大量的修改。