zoukankan      html  css  js  c++  java
  • 设计模式——工厂方法模式

    shanzm-2020年4月3日 22:26:27

    1. 简介

    工厂方法模式(Factory Method Pattern)也称为工厂模式,又称为虚拟构造器模式或多态模式。

    在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

    工厂方法模式主要类:

    • Product抽象产品类(或是接口),派生出所有的具体产品类ConcreteProductAConcreteProductA ……

    • ConcreteProduct 具体产品类,继承于Product抽象类

    • Factory 抽象工厂接口,所有的具体工厂类都是实现该接口

    • ConcreteFactory 具体工厂,实现了Factory接口,创建具体的产品对象

    shanzm_FactoryMethodPattern_UML

    注:在GoF的《设计模式:可复用面向对象软件的基础》,对工厂方法模式的描述中使用的是 Creator接口,其含有 FactoryMethod()方法


    2. 示例1-计算器重构

    2.1 背景说明

    设计模式——简单工厂模式一文中,使用简单工厂模式创建了一个简单的四则计算器,

    我们需要一个抽象的Operation父类,派生出四个加减乘除子类

    在工厂中根据传入的符号参数,生成不同的运行类。

    但是问题也就来了,若是添加一个新的运行类,我们依旧可以继承于Operation抽象父类,override运算方法,然后在工厂中添加一个新的分支,创建该运算类。

    那么问题就来了!按照设计原则——开闭原则,我们应该开放扩展,关闭修改。在这里,我们扩展了运算方法,但是同时也对Factory类进行了修改,这明显不符合开闭原则啊!

    其实这就是简单工厂模式的缺点。

    为了解决这个缺点,我们引入工厂方法模式。(事实上,简单工厂模式是工厂方法模式的简化版,而不是因为简单工厂模式有缺点才演变为工厂方法模式的)

    我们对在简单工厂模式中实现的计算器进行进一步的重构

    2.2 代码重构

    提取出工厂接口:IFactory,分别创建每个运行类的工厂类AddFactory,SubFactory,MulFactory,DivFactory

    //工厂接口:抽象工厂
    public interface IFactory
    {
        Operation CreateOperation();
    }
    
    //加法运行工厂:具体工厂
    public class AddFactory : IFactory
    {
        public Operation CreateOperation()
        {
            return new OperationAdd();
        }
    }
    
    //减法运行工厂:具体工厂
    public class SubFactory : IFactory
    {
        public Operation CreateOperation()
        {
            return new OperationSub();
        }
    }
    
    //乘法运行工厂:具体工厂
    public class MulFactory : IFactory
    {
        public Operation CreateOperation()
        {
            return new OperationMul();
        }
    }
    
    //除法运行工厂:具体工厂
    public class DivFactory : IFactory
    {
        public Operation CreateOperation()
        {
            return new OperationDiv();
        }
    }
    
    

    这时我们在客户端,可以这样使用:

    static void Main(string[] args)
    {
        //创建一个具体的工厂对象:加法工厂对象
        IFactory addFactory = new AddFactory();
        //使用加法工厂对象创建加法运算类
        Operation addOper = addFactory.CreateOperation();
        addOper.NumA = 2;
        addOper.NumB = 3;
        Console.WriteLine(addOper.GetResult());//print:5
        Console.ReadKey();
    }
    

    其实到这里是可以发现,经过重构后的代码,每个具体运算类都有一个相应的工厂类

    若是需要添加一个新的运行符,则我们只需要创建一个新的具体工厂类XXXFactory,实现抽象工厂接口IFactory,同时再创建新的运算符实现类XXX,继承于运算抽象父类Operation。这样就可以在不修改程序中的现有的类的情形下,实现对程序的扩展!满足了开闭原则,避免了简单工厂模式的缺点!

    工厂方法模式实现时,客户端需要决定实例化哪一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂的内部逻辑判断移到了客户端代码来进行。你想要加功能,本来是改工厂类的,而现在是修改客户端

    2.3 程序类图

    shanzm_计算器5.0

    注1:类图是vs自动生成的,但是VS中无法生成`依赖关系`的图示,所以我自己用画图软件给补上来,便于理解程序中各个类的关系。
    注2:实现接口在vs中自动生成的类中是使用棒棒糖表示法


    3. 示例2-模拟多功能日记记录器

    3.1 背景说明

    示例来源于《设计模式实训-第二版》

    某系统日志记录器要求支持多种日志记录方式,如文件日志记录(FileLog)、数据库日志记录(DatabaseLog)等,且用户可以根据要求动态选择日志记录方式,现使用工厂方法模式设计该系统。

    这里我为什么要用这个示例? 因为在实际开发中,一些框架和程序包都是按照工厂方法模式开发的,包括日志框架。

    其实你想一想,使用的一些框架和程序包的调用,是不是都先是创建一个ConcreteFactory对象(或者Creator对象),之后使用该具体工厂对象创建ConcreteProduct对象

    比如:Quartz .NET任务调度框架

    3.2 代码实现

    ①抽象产品和具体产品的实现代码

    //抽象产品:日志记录器总接口(使用抽象类也可以)
    public interface ILog
    {
        void WriteLog();
    }
    //具体产品:文件日志记录器
    public class FileLog : ILog
    {
        public void WriteLog()
        {
            Console.WriteLine("记录日志于日志文件中");
        }
    }
    //具体产品:数据库日志记录器
    public class DatabaseLog : ILog
    {
        public void WriteLog()
        {
            Console.WriteLine("记录日志于日志数据库中");
        }
    }
    

    ②抽象工厂和具体工厂的实现代码

    //抽象工厂:日志记录器工厂
    public interface ILogFactory
    {
        ILog CreateLog();
    }
    //具体工厂:文件日记记录器工厂
    public class FileLogFactory : ILogFactory
    {
        public ILog CreateLog()
        {
            return new FileLog();
        }
    }
    //具体工厂:数据库日记记录器工厂
    public class DatabaseLogFactory : ILogFactory
    {
        public ILog CreateLog()
        {
            return new DatabaseLog();
        }
    }
    

    ③客户端代码

    class Program
    {
        static void Main(string[] args)
        {
            //创建一个具体的工厂对象:FileLogFactory
            ILogFactory logFac = new FileLogFactory();
            //由工厂对象,创建产品对象:FileLog
            //FileLog log = logFac.CreateLog() as FileLog;
            ILog log = logFac.CreateLog();
            log.WriteLog();//print:记日志于日志文件中
            Console.ReadKey();
        }
    }
    

    3.3 程序类图



    4. 总结分析

    4.1 优点

    • 当调用者需要一个具体产品对象,只要使用该具体产品的具体工厂创建该对象即可。调用者不需要知道具体产品对象是怎么创建的

    • 便于扩展:使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。

    • 工厂方法模式是平行的类层次结构

      • 什么是平行的类层次结构?简单的说,假如有两个类的层次,其中一个层次中的类在另外一个层次中都有相对应的类,则称这两个类层次是平行的类层次结构。工厂方法模式就是平行的类层次结构。这里可以看UML图,非常明显!工厂类层次和产品类层次就是平行的。

      • 这种平行的类层次结构用来干什么呢?主要用来把一个类层次中的某些行为分离出来,让类层次中的类把原本属于自己的职责,委托给分离出来的类去实现,从而使得类层次本身变得更简单,更容易扩展和复用。

    4.2 缺点

    • 扩展系统的时候,一旦添加一个具体产品类,则必须同时添加一个相应的具体工厂类。所以系统中类的添加是成对的添加,一定程度上造成系统繁杂。

    4.3 适应场合

    • 工厂方法模式是new一个对象的替代品。所以其实在需要大量实例化对象的地方都是可以使用的。

    • 实际中开发中,工厂方法主要用于工具包和框架中

    4.4 其他说明

    • 为什么工厂方法模式也称为多态工厂模式?工厂接口的有不同的实现(也就是多态),换一句说也就是具体工厂类都有同一个工厂接口。

    • 工厂方法模式的简化:若是产品对象较少,可以使用一个具体工厂类(包含一个静态的工厂方法)根据参数去创建所有的具体产品,这就是所谓的简单工厂模式

      • 注意简单工厂模式就是通过参数化工厂方法实现的。参数化工厂方法具体指:通过给工厂方法传递参数,让工厂方法根据参数创建不同的产品对象。

      • 详细可参考《研磨设计模式》



    5. 参考及源码下载

  • 相关阅读:
    Maximal Square
    Count Complete Tree Nodes
    Rectangle Area
    Implement Stack using Queues
    Basic Calculator
    Invert Binary Tree
    Summary Ranges
    Basic Calculator II
    Majority Element II
    Kth Smallest Element in a BST
  • 原文地址:https://www.cnblogs.com/shanzhiming/p/12629387.html
Copyright © 2011-2022 走看看