X

曜彤.手记

随记,关于互联网技术、产品与创业

  1. 工厂方法(Factory Method)
  2. 抽象工厂模式(Abstract Factory)
  3. 原型模式(Prototype)
  4. 构建器模式(Builder)

“对象创建类”设计模式(C++)

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

工厂方法(Factory Method)

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

模式定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂模式使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。

struct Strategy {
  ~Strategy() {}
  virtual void strategy() = 0;
};
struct StrategyFactory {
  ~StrategyFactory() {}
  virtual std::shared_ptr<Strategy> createStrategy() = 0;
};
struct StrategyA : public Strategy {
  void strategy() {
    std::cout << "[StrategyA]" << std::endl;
  };
};
struct StrategyFactoryA : public StrategyFactory {
  std::shared_ptr<Strategy> createStrategy() {
    return std::make_shared<StrategyA>();
  };
};
class Application {  // 策略模式 + 工厂模式;
  std::shared_ptr<StrategyFactory> strategyFactory;
  std::shared_ptr<Strategy> strategy;
 public:
  Application(std::shared_ptr<StrategyFactory> strategyFactory) : 
    strategyFactory(strategyFactory), 
    strategy(strategyFactory->createStrategy()) {}
  void run() {
    strategy->strategy();
    std::cout << "[Application]" << std::endl;
  }
};
int main(int argc, char** argv) {
  auto strategyFactoryA = std::make_shared<StrategyFactoryA>();
  Application app(strategyFactoryA);
  app.run();
  return 0;
}

总结:

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

总的来看,思路还是将静态的部分(具体的工厂、策略以及需要用到哪些)留给用户代码去决定。其余都用动态代码组成。

* 知乎上的一个回答工厂模式(factory Method)的本质是什么?为什么引入工厂模式?

抽象工厂模式(Abstract Factory)

在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时,由于需求变化,往往存在更多系列对象的创建工作。如何应对这种变化?如何绕过常规的对象创建方法,提供一种“封装机制”来避免客户端程序和这种“多系列具体对象创建工作”的紧耦合?

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

struct StrategyInit {  // 稳定;
  ~StrategyInit() {}
  virtual void strategyInit() = 0;
};
struct StrategyFinish {  // 稳定;
  ~StrategyFinish() {}
  virtual void strategyFinish() = 0;
};
struct StrategyFactory {  // 稳定;一簇相互依赖的对象(StrategyInit,StrategyFinish);
  ~StrategyFactory() {}
  virtual std::shared_ptr<StrategyInit> createStrategyInit() = 0;
  virtual std::shared_ptr<StrategyFinish> createStrategyFinish() = 0;
};
struct StrategyInitA : public StrategyInit {
  void strategyInit() {
    std::cout << "[StrategyInit A]" << std::endl;
  };
};
struct StrategyFinishA : public StrategyFinish {
  void strategyFinish() {
    std::cout << "[StrategyFinish A]" << std::endl;
  };
};
struct StrategyFactoryA : public StrategyFactory {
  std::shared_ptr<StrategyInit> createStrategyInit() {
    return std::make_shared<StrategyInitA>();
  };
  std::shared_ptr<StrategyFinish> createStrategyFinish() {
    return std::make_shared<StrategyFinishA>();
  };
};
class Application {  // 策略模式 + 抽象工厂模式;
  std::shared_ptr<StrategyFactory> strategyFactory;
 public:
  Application(std::shared_ptr<StrategyFactory> strategyFactory) : 
    strategyFactory(strategyFactory) {}
  void run() {
    strategyFactory->createStrategyInit()->strategyInit();
    strategyFactory->createStrategyFinish()->strategyFinish();
    std::cout << "[Application]" << std::endl;
  }
};
int main(int argc, char** argv) {
  auto strategyFactoryA = std::make_shared<StrategyFactoryA>();
  Application app(strategyFactoryA);
  app.run();
  return 0;
}

总结:

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

原型模式(Prototype)

在软件系统中,经常面临着“某些结构复杂的对象”(即无法通过工厂模式简单创建的)的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口。如何应对这种变化?如何向“客户程序(使用这些对象的程序)”隔离出“这些易变对象”,从而使得“依赖这些易变对象的客户程序”不随着需求改变而改变?

模式定义:

struct Strategy {
  ~Strategy() {}
  virtual void strategy() = 0;
  virtual std::shared_ptr<Strategy> clone() = 0;  // 通过克隆自己创建对象;
};
struct StrategyA : public Strategy {
  std::shared_ptr<Strategy> clone() {
    return std::make_shared<StrategyA>(*this);  // 通过 copy-constructor 拷贝自己;
  }
  void strategy() {
    std::cout << "[Strategy A]" << std::endl;
  }
};
class Application {
  std::shared_ptr<Strategy> prototype;  // 使用原型来创建新对象;
 public:
  Application(std::shared_ptr<Strategy> prototype) : prototype(prototype) {}
  void run() {
    prototype->clone()->strategy();  // 创建新对象并调用方法;
    std::cout << "[Application]" << std::endl;
  }
};
int main(int argc, char** argv) {
  auto strategyA = std::make_shared<StrategyA>();
  Application app(strategyA);
  app.run();
  return 0;
}

总结:

  • 原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些“易变类”拥有“稳定的接口”;
  • 原型模式对于“如何创建易变类的实体对象”采用“原型克隆”的方式,它使得我们可以非常灵活地动态创建“拥有某些稳定接口”的新对象 —— 所需工作仅仅是注册一个新类的对象(即原型),然后再任何需要的地方 Clone;
  • 原型模式中的 Clone 方法可以利用某些框架中的序列化来实现深拷贝;

构建器模式(Builder)

在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化?从而保持系统中的“稳定构建算法”不随着需求改变而改变?

模式定义:将一个复杂对象的构建与其表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)。

struct House {};
struct HouseA : public House {};
struct Builder {
  Builder(std::shared_ptr<House> pHouse) : pHouse(pHouse) {}
  ~Builder() {}
  std::shared_ptr<House> getBuildResult() {
    return pHouse;
  }
  virtual void buildStepA() = 0;
  virtual void buildStepB() = 0;
 private:
  std::shared_ptr<House> pHouse;
};
struct HouseABuilder : public Builder {
  HouseABuilder() : Builder(std::make_shared<HouseA>()) {}
  void buildStepA() {
    // 可以直接使用基类的 pHouse 指针;
    std::cout << "[BuilderA - StepA]" << std::endl;
  };
  void buildStepB() {
    std::cout << "[BuilderA - StepB]" << std::endl;
  };
};
struct BuilderDirector {
  BuilderDirector(std::shared_ptr<Builder> builder) : builder(builder) {}
  std::shared_ptr<House> build() {
    builder->buildStepA();
    builder->buildStepB();
    return builder->getBuildResult();
  }
 private:
  std::shared_ptr<Builder> builder;
};
int main(int argc, char** argv) {
  auto director = std::make_shared<BuilderDirector>(std::make_shared<HouseABuilder>());
  director->build();
  return 0;
}

总结:

  • 构建器模式主要用于“分步骤构建一个复杂对象”。在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化;
  • 变化点在哪里,封装哪里 —— 构建器模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建算法”的需求变动;
  • 在构建器模式中,要注意不同语言中构建器内调用虚函数的差别(C++ vs. Java)。



评论 | Comments


Loading ...