Rust·设计模式·工厂方法·抽象工厂

 

工厂方法可以通过传入的类型参数创建具体的类 把MainForm与具体的类解耦 使用MainForm不用new创建类的实例

  • 工厂方法可以通过传入的类型参数创建具体的类
    • 把MainForm与具体的类解耦
    • 使用MainForm不用new创建类的实例

“对象创建”模式

  • 通过“对象创建” 模式绕开new
    • 来避免对象创建(new)过程中所导致的紧耦合(依赖具体类)
    • 从而支持对象创建的稳定
    • 它是接口抽象之后的第一步工作
  • 典型模式
    • Factory Method
    • Abstract Factory
    • Prototype
    • Builder

工厂方法

动机(Motivation)

  • 在软件系统中,经常面临着创建对象的工作
    • 由于需求的变化,需要创建的对象的具体类型经常变化
  • 如何应对这种变化?
  • 如何绕过常规的对象创建方法(new)
    • 提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?

初始设计·文件分割器

  • 一个类如果未来有变化,就应该声明为一个接口
    • 一个对象的类型应该声明为一个接口
    • 不要声明为具体的类
      • 具体类不能支持未来的变化
  • 文件分割器

    class FileSplitter {
      public:
        void split() {...}
    }
    

    使用

    class MainForm : public Form {
      TextBox* txtFilePath;
      TextBox* txtFileNumber;
      ProgressBar* progressBar;
    
    public:
      void Button1_Click(){
          ISplitter * splitter= new FileSplitter();//依赖具体类
          splitter->split();
      }
    };
    
  • 未来扩展,要支持
    • 文本文件的分割
    • 视频文件的分割
    • 二进制文件的分割
  • 每个需求写一个对应的类

    class BinarySplitter { };
    class TxtSplitter{ };
    class PictureSplitter{ };
    class VideoSplitter{ };
    
  • 抽象出一个基类(接口)

    class ISplitter{
    public:
        virtual void split()=0;
        virtual ~ISplitter(){}
    };
    
    class BinarySplitter : public ISplitter{ };
    class TxtSplitter: public ISplitter{ };
    class PictureSplitter: public ISplitter{ };
    class VideoSplitter: public ISplitter{ };
    

    使用:

    // 二进制文件分割器
    ISplitter *splitter = new BinarySplitter();
    splitter->split()
    // 文本文件分割器
    ISplitter *splitter = new TxtSplitter();
    splitter->split()
    
  • 为什么使用面向接口编程?
    • 因为“依赖倒置原因”
      • 依赖抽象,不依赖实现细节
      • ISplitter *splitter就是依赖抽象
      • new BinarySplitter()就是依赖现实细节
      • 但是如果把BinarySplitter()或者TxtSplitter()换成ISplitter不行,因为它是接口,不能实例化
    • 这就是为什么要绕开new
      • 因为它涉及到的是实现细节

重构

  • 因为new是对具体细节的依赖,所以可以考虑用一个方法绕开new,返回一个对象

    class SplitterFactory {
    public:
        ISplitter *createSplitter() { 
            return new BinarySplitter()
        }
    }
    
  • 在MainForm中

    class MainForm : public Form {
        TextBox* txtFilePath;
        TextBox* txtFileNumber;
        ProgressBar* progressBar;
    
    public:
      void Button1_Click(){
          SplitterFactory factory;
          ISplitter *splitter = factory.createSplitter();
          splitter->split();
      }
    };
    
    • 但是MainForm依赖SplitterFactory,它依赖BinarySplitter
    • 所以MainForm最后还是会依赖具体细节
  • 上面的方法都是编译时依赖,但cpp有支持运行时依赖,即虚函数
    • 虚函数是运行时依赖

      class SplitterFactory {
      public:
          // 虚函数不必在这里定义函数内容,而是延迟到运行时定义
          virtual ISplitter *createSplitter() = 0;
      }
      
    • MainForm的使用

      SplitterFactory *factory; // 多态
      ISplitter *splitter = 
           factory->createSplitter(); // 多态的创建
      
      splitter->split();
      
      • 虽然在MainForm中的splitter是由factory创建
      • 但真正的创建是交给未来(因为调用的是虚函数)
        • 未来就是SplitterFactory *factory
        • 但是这个factory应该等于什么呢?
  • MainForm的重构
    • 针对具体类,实现一系列具体的工厂

      //具体工厂
      class BinarySplitterFactory: public SplitterFactory{
      public:
          virtual ISplitter* CreateSplitter(){
              return new BinarySplitter();
          }
      };
      
      class TxtSplitterFactory: public SplitterFactory{
      public:
          virtual ISplitter* CreateSplitter(){
              return new TxtSplitter();
          }
      };
      
      class PictureSplitterFactory: public SplitterFactory{
      public:
          virtual ISplitter* CreateSplitter(){
              return new PictureSplitter();
          }
      };
      
      class VideoSplitterFactory: public SplitterFactory{
      public:
          virtual ISplitter* CreateSplitter(){
              return new VideoSplitter();
          }
      };
      
    • MainForm的改变

      • 原来

        SplitterFactory *factory; // 多态
        ISplitter *splitter = 
            factory->createSplitter(); // 多态的创建
        
        • 问题是SplitterFactory *factory = ? 应该等于什么?
        • 如果是 new 则变成了编译时依赖,不行
      • 重构

        class MainForm : public Form {
            SplitterFactory*  factory;//工厂
        
        public:
            MainForm(SplitterFactory* factory){
                this->factory=factory;
            }
                  
          void Button1_Click(){
              ISplitter * splitter=
                      factory->CreateSplitter(); //多态new
              splitter->split();
          }
        };
        
        • Button可以点多次,所以不需要每次点击都创建一个factory
          • factory移到上层
        • 通过参数从外面把factory传给MainForm
          • 通过未来外面传递的具体SplitterFactory,来创建ISplitter类型
    • MainForm不再有对具体类的依赖了

  • 重构不是消除变化点(具体类)
    • 而是把变化点限制在一个局部的范围中

模式定义

  • 定义一个用于创建对象的接口(SplitterFactory)
    • 子类决定实例化哪一个类
      • BinarySplitterFactory
      • TxtSplitterFactory
  • Factory Method使得一个类的实例化延迟到子类
    • 目的:解耦
      • new 后面的具体类
    • 手段:虚函数
    • 延迟:支持了变化

类图

  • 目标
    • 让MainForm依赖稳定的部分
    • 把变化的部分隔离出去

要点总结

  • Factory Method模式用于隔离类对象的使用者和具体类型之间的耦合关系
    • 面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱
  • Factory Method模式通过面向对象的手法
    • 将所要创建的具体对象工作延迟到子类
    • 从而实现一种扩展(而非更改)的策略
    • 较好地解决了这种紧耦合关系
  • Factory Method模式解决“单个对象”的需求变化
    • 缺点在于要求创建方法/参数相同

Rust 设计模式·工厂方法

功能

  • 商场收银软件
    • 输入原价
    • 通过下拉菜单得到收费选项
      • 正常收费
      • 打八折
      • 打七折
      • 打五折
      • 满100返50
      • 满200返100
    • 最终得到当前价

简单工厂(1)

use rand::Rng;

extern crate rand;

fn 获取收费方式() -> &'static str {
    let s = vec![
        "正常收费",
        "打八折",
        "打七折",
        "打五折",
        "满100返50",
        "满200返100",
    ];
    let mut rng = rand::thread_rng();
    let r = rng.gen_range(0, 6);
    return s[r];
}

trait 收费 {
    fn 计算(&self, 原价: f64) -> f64;
}
struct 正常收费;
impl 收费 for 正常收费 {
    fn 计算(&self, 原价: f64) -> f64 {
        原价
    }
}

struct 打七折;
impl 收费 for 打七折 {
    fn 计算(&self, 原价: f64) -> f64 {
        0.7 * 原价
    }
}
struct 打八折;
impl 收费 for 打八折 {
    fn 计算(&self, 原价: f64) -> f64 {
        0.8 * 原价
    }
}
struct 打五折;
impl 收费 for 打五折 {
    fn 计算(&self, 原价: f64) -> f64 {
        0.5 * 原价
    }
}
struct 10050;
impl 收费 for 10050 {
    fn 计算(&self, 原价: f64) -> f64 {
        原价 - (原价 as u64 / 100) as f64 * 50.0
    }
}

struct 200100;
impl 收费 for 200100 {
    fn 计算(&self, 原价: f64) -> f64 {
        原价 - (原价 as u64 / 200) as f64 * 100.0
    }
}

fn 收费方式工厂(收费选项: &str) -> Box<dyn 收费> {
    match 收费选项 {
        "正常收费" => Box::new(正常收费),
        "打八折" => Box::new(打八折),
        "打七折" => Box::new(打七折),
        "打五折" => Box::new(打五折),
        "满100返50" => Box::new(10050),
        "满200返100" => Box::new(200100),
        _ => Box::new(正常收费),
    }
}

fn main() {
    let mut rng = rand::thread_rng();
    let 原价 = rng.gen_range(0, 2000) as f64;

    let 收费选项 = 获取收费方式();
    println!("收费选项:{}", 收费选项);
    let 收费方式 = 收费方式工厂(收费选项);
    println!("原价:{}, 特价:{}", 原价, 收费方式.计算(原价));
}

简单工厂模式(2)

use rand::Rng;

extern crate rand;

fn 获取收费方式() -> &'static str {
    let mut rng = rand::thread_rng();
    let s = vec![
        "正常收费",
        "打八折",
        "打七折",
        "打五折",
        "满100返50",
        "满200返100",
    ];
    let r = rng.gen_range(0, 6);
    return s[r];
}

trait 收费 {
    fn 计算(&self, 原价: f64) -> f64;
}
struct 正常收费;
impl 收费 for 正常收费 {
    fn 计算(&self, 原价: f64) -> f64 {
        原价
    }
}

struct 打折 {
    折扣: f64,
}
impl 打折 {
    fn new(折扣: f64) -> Self {
        Self { 折扣 }
    }
}
impl 收费 for 打折 {
    fn 计算(&self, 原价: f64) -> f64 {
        self.折扣 * 原价
    }
}
struct 满减 {
    : f64,
    : f64,
}
impl 满减 {
    fn new(: f64, : f64) -> Self {
        Self { ,  }
    }
}
impl 收费 for 满减 {
    fn 计算(&self, 原价: f64) -> f64 {
        原价 - (原价 / self.满).floor() * self.减
    }
}

fn 收费方式工厂(收费选项: &str) -> Box<dyn 收费> {
    match 收费选项 {
        "正常收费" => Box::new(正常收费),
        "打八折" => Box::new(打折::new(0.8)),
        "打七折" => Box::new(打折::new(0.7)),
        "打五折" => Box::new(打折::new(0.5)),
        "满100返50" => Box::new(满减::new(100.0, 50.0)),
        "满200返100" => Box::new(满减::new(200.0, 100.0)),
        _ => Box::new(正常收费),
    }
}

fn main() {
    let mut rng = rand::thread_rng();
    let 原价 = rng.gen_range(0, 2000) as f64;

    let 收费选项 = 获取收费方式();
    println!("收费选项:{}", 收费选项);
    let 收费方式 = 收费方式工厂(收费选项);
    println!("原价:{}, 优惠价:{:02}", 原价, 收费方式.计算(原价));
}

python设计模式

工厂模式

  • “工厂”表示一个负责创建其他类型对象的类
  • 客户端使用某些参数调用此方法
    • 工厂会据此创建所需类型的对象
    • 然后将它们返回给客户端
  • Factory 模式有 3 种变体
    • 简单工厂模式
      • 允许接口创建对象
      • 但不会暴露对象的创建逻辑
    • 工厂方法模式
      • 允许接口创建对象
      • 但使用哪个类来创建对象,则是交由子类决定的
    • 抽象工厂模式
      • 抽象工厂是一个能够创建一系列相关的对象而无需指定/公开其具体类的接口
      • 该模式能够提供其他工厂的对象,在其内部创建其他对象

简单工厂模式

from abc import ABCMeta, abstractmethod

class Animal(metaclass = ABCMeta):
  @abstractmethod
    def do_say(self):
  pass

class Dog(Animal):
  def do_say(self):
    print("Bhow Bhow!!")

class Cat(Animal):
  def do_say(self):
    print("Meow Meow!!")
  • 客户端使用Factory类
    • 有create_type方法
    • 当客户端调用create_type方法后
    • 根据传入的参数返回Cat或Dog

工厂方法模式

  • 定义一个接口来创建对象
    • 但是工厂本身并不负责创建对象
    • 而是将这一任务交由子类来完成
      • 即子类决定了要实例化哪些类
  • Factory 方法的创建是通过继承而不是通过实例化来完成的

抽象工厂

动机(Motivation)

  • 在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作
    • 同时,由于需求的变化,往往存在更多系列对象的创建工作
    • 一系列相互依赖的对象

      virtual IDBConnection* CreateDBConnection()=0;
      virtual IDBCommand* CreateDBCommand()=0;
      virtual IDataReader* CreateDataReader()=0;
      
      • Oracle是一个系列
      • MySQL是一个系列
  • 如何应对这种变化?
    • 如何绕过常规的对象创建方法(new)
    • 提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合?

初级设计

  • 设计一个数据访问层
    • 创建一系列的对象
    • 这些对象有的是SQL、mySQL等等不同的数据库

数据层对象

class EmployeeDAO{
public:
    vector<EmployeeDO> GetEmployees(){
        SqlConnection* connection =
            new SqlConnection();
        connection->ConnectionString("...");

        SqlCommand* command =
            new SqlCommand();
        command->CommandText("...");
        command->SetConnection(connection);

        SqlDataReader* reader = command->ExecuteReader();
        while (reader->Read()){ ... }
    }
};

重构·面向接口的编程

  • 做一系列的基础类

    //数据库访问有关的基类
    class IDBConnection{ ... };
    class IDBConnectionFactory{
    public:
        virtual IDBConnection* CreateDBConnection()=0;
    };
    
    class IDBCommand{ ... };
    class IDBCommandFactory{
    public:
        virtual IDBCommand* CreateDBCommand()=0;
    };
    
    class IDataReader{ ... };
    class IDataReaderFactory{
    public:
        virtual IDataReader* CreateDataReader()=0;
    };
    
  • 不同的数据库继承基类

    • 支持SQL Server

      class SqlConnection: public IDBConnection{ ... };
      class SqlConnectionFactory:public IDBConnectionFactory{ ... };
      
      class SqlCommand: public IDBCommand{ };
      class SqlCommandFactory:public IDBCommandFactory{ };
      
      class SqlDataReader: public IDataReader{ };
      class SqlDataReaderFactory:public IDataReaderFactory{ };
      
    • 支持Oracle

      class OracleConnection: public IDBConnection{ };
      class OracleConnectionFactory:public IDBConnectionFactory{ ... };
      
      class OracleCommand: public IDBCommand{ };
      class OracleDataReader: public IDataReader{ };
      
  • 修改应用类EmployeeDAO

    • 组合各个工厂方法
    class EmployeeDAO{
        // 组合工厂方法
        IDBConnectionFactory* dbConnectionFactory;
        IDBCommandFactory* dbCommandFactory;
        IDataReaderFactory* dataReaderFactory;
          
    public:
        vector<EmployeeDO> GetEmployees(){
            IDBConnection* connection =
                dbConnectionFactory->CreateDBConnection();
            connection->ConnectionString("...");
    
            IDBCommand* command =
                dbCommandFactory->CreateDBCommand();
            command->CommandText("...");
            command->SetConnection(connection); //关联性
    
            IDBDataReader* reader = command->ExecuteReader(); //关联性
            while (reader->Read()){ ... }
        }
    };
    
  • 新的问题

    class EmployeeDAO{
        // 组合工厂方法
        IDBConnectionFactory* dbConnectionFactory;
        IDBCommandFactory* dbCommandFactory;
        IDataReaderFactory* dataReaderFactory;
    }
    
    • 这里必须是相同数据库对象的系列方法,不能是MySQL的Connection,接Oracle的Command
    • 因此涉及到把这些方法都要打包到一个工厂中,让工厂统一返回
    • 这个工厂就是抽象工厂
    • 统一成一个工厂
      • 原来的代码,三个不同的工厂
      // 三个接口类
      class IDBConnection{ };
      class IDBCommand{ };
      class IDataReader{ };
      
      // 三个类对应的工厂
      class IDBConnectionFactory{
      public:
          virtual IDBConnection* CreateDBConnection()=0;
      };
      
      class IDBCommandFactory{
      public:
          virtual IDBCommand* CreateDBCommand()=0;
      };
      
      class IDataReaderFactory{
      public:
          virtual IDataReader* CreateDataReader()=0;
      };
      
      • 新的代码

        • 三个工厂合并成一个工厂(因为是相互关联的类)
        //数据库访问有关的基类
        class IDBConnection{ };
        class IDBCommand{ };
        class IDataReader{ };
        
        class IDBFactory{
        public:
            virtual IDBConnection* CreateDBConnection()=0;
            virtual IDBCommand* CreateDBCommand()=0;
            virtual IDataReader* CreateDataReader()=0;
                  
        };
        
        • 每个数据库创建一个工厂SQL/Oracle

          //支持SQL Server
          class SqlConnection: public IDBConnection{ };
          class SqlCommand: public IDBCommand{ };
          class SqlDataReader: public IDataReader{ };
          
          class SqlDBFactory:public IDBFactory{
          public:
              virtual IDBConnection* CreateDBConnection()=0;
              virtual IDBCommand* CreateDBCommand()=0;
              virtual IDataReader* CreateDataReader()=0;
          };
          
        • 应用类

          class EmployeeDAO{
              // 抽象工厂(family factory)
              IDBFactory* dbFactory;
                      
          public:
              vector<EmployeeDO> GetEmployees(){
                  IDBConnection* connection =
                      dbFactory->CreateDBConnection();
                  connection->ConnectionString("...");
          
                  IDBCommand* command =
                      dbFactory->CreateDBCommand();
                  command->CommandText("...");
                  command->SetConnection(connection); //关联性
          
                  IDBDataReader* reader = command->ExecuteReader(); //关联性
                  while (reader->Read()){ ... }
              }
          };
          

模式定义

  • 提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类
    • 接口
      • IDBFactory
    • 一系列对象

      virtual IDBConnection* CreateDBConnection()=0;
      virtual IDBCommand* CreateDBCommand()=0;
      virtual IDataReader* CreateDataReader()=0;
      

类图

要点总结

  • 抽象工厂(family factory)的核心
    • 系列对象
  • 如果没有应对“多系列对象构建”的需求变化
    • 则没有必要使用 Abstract Factory模式
    • 这时候使用简单的工厂完全可以
  • “系列对象”指的是在某一特定系列下的对象之间有相互依赖、 或作用的关系
    • 不同系列的对象之间不能相互依赖
  • Abstract Factory模式主要在于应对“新系列”的需求变动
    • 其缺点在于难以应对“新对象”的需求变动