C++设计模式创建型模式———简单工厂模式、工厂方法模式、抽象工厂模式
文章目录
- 一、引言
- 二、简单工厂模式
- 三、工厂方法模式
- 三、抽象工厂模式
- 四、总结
一、引言
创建一个类对象的传统方式是使用关键字new
, 因为用 new
创建的类对象是一个堆对象,可以实现多态。工厂模式通过把创建对象的代码包装起来,实现创建对象的代码与具体的业务逻辑代码相隔离的目的(将对象的创建和使用进行解耦)。
试想,如果创建一个类A的对象,可能会写出A* pa=new A()
这样的代码行,但当给类A的构造函数增加一个参数时,所有利用new
创建类A对象的代码行全部需要修改,如果通过工厂模式把创建类A对象的代码统一放到某个位置,则对于诸如给类A的构造函数增加参数之类的问题,只需要修改一个位置就可以了。
工厂模式属于创建型模式,一般可以细分为3种:简单工厂模式、工厂方法模式和抽象
工厂模式主要包括简单工厂模式、工厂方法模式和抽象工厂模式。
二、简单工厂模式
简单工厂模式并不属于设计模式中的经典模式之一,但它是工厂模式的基础。它通过一个工厂类根据传入的参数来决定创建哪种类的对象。
简单工厂模式的核心思想是:将对象的创建集中管理,通过一个工厂类来实例化对象,而不是让客户端直接使用 new
操作符来创建对象。这样就可以将对象的创建和使用分离,提高代码的可维护性和灵活性。
简单工厂模式主要由以下三部分组成:
- 工厂类(Factory):负责对象的创建,根据传入的参数,决定创建哪种产品对象。
- 抽象产品类(Abstract Product):定义了产品的接口或抽象类,规定了所有产品必须实现的功能。
- 具体产品类(Concrete Product):实现了抽象产品类,代表了具体要创建的产品。
这里以单机闯关打斗类游戏游戏开发来阐述。
游戏中的主角需要通过攻击并杀死怪物来进行闯关,策划规定,在该游戏中,暂时有3类怪物(后面可能会增加新的怪物种类),分别是亡灵类怪物、元素类怪物、机械类怪物,每种怪物都有一些各自的特点(细节略),当然,这些怪物还有一些共同特点,例如同主角一样,都有生命值、魔法值、攻击力3个属性,为此,创建一个Monster
(怪物)类作为父类,而创建M_Undead
(亡灵类怪)、MElement
(元素类怪)和M_Mechanic
(机械类怪)作为子类是合适的。针对怪物,程序定义了如下几个类:
class Monster
{
public:Monster(int life, int magic, int attack):m_life(life), m_magic(magic), m_attack(attack){}virtual~Monster() {}//作父类时析构函数应该为虚函数
protected: //可能被子类访问的成员,用protected修饰int m_life; //生命值int m_magic;//魔法值int m_attack;//攻击力
};class M_Undead :public Monster
{
public:M_Undead(int life, int magic, int attack) :Monster(life, magic, attack) { cout << "一只亡灵类怪物来到了这个世界" << endl;}//...
};
class M_Mechanic :public Monster
{
public:M_Mechanic(int life, int magic, int attack) :Monster(life, magic, attack){cout << "一只亡灵类怪物来到了这个世界" << endl;}//...
};class M_Element :public Monster
{
public:M_Element(int life, int magic, int attack) :Monster(life, magic, attack){cout << "一只亡灵类怪物来到了这个世界" << endl;}//...
};
当需要在游戏的战斗场景中产生怪物时,传统方法可以使用new
直接产生各种怪物,
例如在main主函数中可以加人如下代码:
Monster*pM1=new M_Undead(300,50,80);//产生了一只亡灵类怪物
Monster*pM2=new M_Element(200,80,100);//产生了一只元素类怪物
Monster*pM3=new M_Mechanic(400,0,110);//产生了一只机械类怪物
//释放资源
delete pM1;
delete pM2;
delete pM3;
上面这种创建怪物的写法虽然合法,但不难看到,当创建不同种类的怪物时,避免不了直接与多个怪物类(M_Undead
、M_Element
、M_Mechanic
)打交道,这属于一种依赖具体类的紧耦合,因为需要知道这些类的名字,尤其是随着游戏内容的不断增加,怪物的种类也可能会不断增加。
如果通过某个扮演工厂角色的类(怪物工厂类)来创建怪物,则意味着创建怪物时不再使用new
关键字,而是通过该工厂类来进行,这样的话,即便将来怪物的种类增加,main
主函数中创建怪物的代码也可以尽量保持稳定。通过工厂类,避免了在mai~n函数中(也可以在任何其他函数中)直接使用new
创建对象时必须知道具体类名(这是一种依赖具体类的紧耦合关系)的情形发生,实现了创建怪物的代码与各个具体怪物类对象要实现的业务逻辑代码隔离,这就是简单工厂模式的实现思路。
当然,和使用new
创建对象的直观性比,显然简单工厂模式的实现思路是绕了弯的。下面就创建一个怪物工厂类MonsterFactory
,用这个工厂类来生产(产生)出各种不同种类的怪物,代码如下:
// 简单工厂类
class MonsterFactory {
public:static unique_ptr<Monster> createMonster(const string& type, int life, int magic, int attack) {if (type == "undead") {return make_unique<M_Undead>(life, magic, attack);}else if (type == "mechanic") {return make_unique<M_Mechanic>(life, magic, attack);}else if (type == "element") {return make_unique<M_Element>(life, magic, attack);}else {return nullptr; // 无效类型}}
};
通过上面的代码可以看到,createMonster
成员函数的形参是一个字符串,代表怪物类型。虽然通过工厂创建怪物不再需要直接与各个怪物类打交道,但必须通过一个标识告诉怪物工厂类要创建哪种怪物,这就是该字符串的作用。当然,不使用字符串而使用一个整型数字也没问题,只要能标识出不同的怪物类型即可。createMonster
成员函数返回的是Monster*
的智能指针,这个所有怪物类的父类指针以支持多态。
也可以把
createMonster
函数实现为类的成员方法。
auto undead = MonsterFactory::createMonster("undead", 100, 50, 10);
auto mechanic = MonsterFactory::createMonster("mechanic", 120, 40, 15);
auto element = MonsterFactory::createMonster("element", 80, 60, 20);
代码经过改造后,创建各种怪物时就不必面对M_Undead
、M_Element
、M_Mechanic
等具体的怪物类,只要面对MonsterFactory
类即可。当然,其实main
主函数创建对象时遇到的麻烦(依赖具体怪物类)依旧存在,只是被转嫁给了MonsterFactory
类而已。其实,依赖这件事本身并不会因为引人设计模式而完全消失,程序员能做的是把这种依赖的范围尽量缩小(例如缩小到MonsterFactory
类的createMonster
成员函数中),从而避免依赖关系遍布整个代码(所有需要创建怪物对象的地方),这就是所谓的封装变化(把容易变化的代码段限制在一个小范围内),就可以在很大程度上提高代码的可维护性和可扩展性,否则可能会导致一修改代码就要修改一大片的困境。例如以往如果这样写代码:
Monster*pM1=new M_Undead(300,50,80);
那么一旦要对圆括号中的参数类型进行修改或者新增参数,则所有涉及new M_Undead
的代码段可能都要修改,但采用简单工厂模式后,只需要修改MonsterFactory
类的createMonster
成员函数,确实省了很多事。
MonsterFactory
类的实现也有缺点。最明显的缺点就是当引人新的怪物类型时,需要修改createMonster
成员函数的源码来增加新的if判断分支,从而支持对新类型怪物的创建工作,这违反了面向对象程序设计的一个原则一一开闭原则。
与类之间以实线箭头表示父子关系,子类(M_Undead、M_Element、M_Mechanic)
与父类(Monster)之间有一条带箭头的实线,箭头的方向指向父类。
MonsterFactory
类与M_Undead、M_Element、M_Mechanic类之间的虚线箭头表示箭头连接的两个类之间存在着依赖关系(一个类引l用另一个类),换句话说,虚线箭头表示一个类(MonsterFactory)实例化另外一个类(M_Undead、M_Element、M_Mechanic)的对象,箭头指向被实例化对象的类。
由于创建怪物只需要MonsterFactory
类的createMonster
函数,因此创建怪物的代码是稳定的,但是如果新增怪物就需要在工厂类修改函数代码,因此createMonster
函数是变化的。
简单工厂模式的优缺点:
引入简单工厂设计模式:定义一个工厂类,该类的一个函数可以根据不同的参数创建并返回不同的类对象,被创建的对象所属的类一般具有相同的父类。使用者不必关心创建对象的细节。
违反开闭原则:每当新增一个产品类型时,都需要修改工厂类的代码,这违背了“对扩展开放,对修改封闭”的设计原则。
不适合产品种类过多的情况:如果产品种类过多,工厂类的逻辑会变得很复杂,难以维护。
三、工厂方法模式
工厂模式是一种创建型设计模式, 其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。工厂方法模式解决了简单工厂模式中扩展性差的问题。它将对象的创建过程延迟到子类中,由不同的子类决定实例化哪个类。每一种具体产品都有对应的工厂。
在上面的简单工厂的代码中,我们引入新的怪物就需要就该工厂的成员函数(即新增if判断分支),这样导致代码过于琐碎,且难以维护。
工厂方法模式采用新增新的工厂类的方法支持新怪物类型(不影响已有代码),满足了开闭原则。这种方式的灵活性更强,实现也更为复杂,同时也要引入更多的新类(主要是工厂类)。
我们仍使用简单工厂的例子。
在工厂方法模式中,不是用一个工厂类MonsterFactory
来解决创建多种类型怪物的问
题,而是用多个工厂类来解决创建多种类型怪物的问题。而且,针对每种类型的怪物,都需
要创建一个对应的工厂类,例如,当前要创建3种类型的怪物M_Undead
、M_Element
、
M_Mechanic
,那么,就需要创建3个工厂类,例如分别命名为M_UndeadFactory
、
M_ElementFactory
、M_MechanicFactory
。而且这3个工厂类还会共同继承自同一个工厂父类,例如将该工厂父类命名为M_ParFactory
(工厂抽象类)。
如果将来策划要求引人第四种类型的怪物,那么毫无疑问,需要为该种类型的怪物增加
对应的一个新工厂类,当然该新工厂类依然继承自M_ParFactory
类。
从上面的描述,可以初步看出,工厂方法模式通过增加新的工厂类来符合开闭原则(对扩展开放,对修改关闭),但付出的代价是需要增加多个新的工厂类。
下面开始改造简单工厂模式中实现的代码。实现所有工厂类的父类M_ParFactory
(等价于将简单工厂模式中的工厂类MonsterFactory
进行抽象),代码如下:
// 抽象工厂类 所有工厂类的父类
class M_ParFactory {
public:virtual unique_ptr<Monster> createMonster() = 0; // 工厂方法virtual ~M_ParFactory(){}
};
然后,针对每个具体的怪物子类,都需要创建一个相关的工厂类,所以,针对
M_Undead
、M_Element
、M_Mechanic
类,创建3个工厂类M_UndeadFactory
、M_ElementFactory
和M_MechanicFactory
,代码如下:
// 亡灵怪物工厂
class M_UndeadFactory : public M_ParFactory {
public:unique_ptr<Monster> createMonster() override {return make_unique<M_Undead>(300,50,80);}
};// 机械怪物工厂
class M_MechanicFactory : public M_ParFactory {
public:unique_ptr<Monster> createMonster() override {return make_unique<M_Mechanic>(400,0,110);}
};// 元素怪物工厂
class M_ElementFactory : public M_ParFactory {
public:unique_ptr<Monster> createMonster() override {return make_unique<M_Element>(200,80,100);}
};
有了这3个怪物工厂类之后,可以创建一个全局函数Gbl_CreateMonster
来处理怪物对象的生成,代码如下:
unique_ptr<Monster> Gbl_CreateMonster(M_ParFactory* factory )
{return factory->createMonster();//createMonster虚函数扮演了多态new的行为,factory//指向的具体怪物工厂类不同,创建的怪物对象也不同
}
从现在的代码可以看到,Gbl_CreateMonster
作为创建怪物对象的核心函数,并不依赖
于具体的M_Undead
、M_Element
、M_Mechanic
怪物类,只依赖于Monster
类(Gbl_CreateMonster
的返回类型)和M_ParFactory
类(Gbl_CreateMonster
的形参类型),变化的部分被隔离到调用Gbl_CreateMonster
函数的地方去了。
在main主函数中,通过如下代码来通过各自的工厂生产各自的产品:
M_ParFactory* p_ud_fy = new M_UndeadFactory();//多态工厂,注意指针类型
auto pM1 = Gbl_CreateMonster(p_ud_fy);//产生了一只亡灵类怪物,也是多态,注意返
//回类型,当然也可以直接写成Monster
//pM1 =p_ud_fy->createMonster();
M_ParFactory* p_elm_fy = new M_ElementFactory();
auto pM2 = Gbl_CreateMonster(p_elm_fy);//产生了一只元素类怪物
M_ParFactory* p_mec_fy = new M_MechanicFactory();
auto pM3 = Gbl_CreateMonster(p_mec_fy);//产生了一只机械类怪物
delete p_ud_fy;
从上述代码可以看到,创建怪物对象时,不需要记住具体怪物类的名称,但需要知道创
建该类怪物的工厂的名称。
引人工厂方法设计模式的定义(实现意图):定义一个用于创建对象的接口(M_ParFactory
类中的createMonster
成员函数,这其实就是工厂方法,工厂方法模式的名字也是由此而来),但由子类(M_UndeadFactory
、M_ElementFactory
、M_MechanicFactory
)决定要实例化的类是哪一个。该模式使得某个类(M_Undead
、M_Element
、M_Mechanic
)的实例化延迟到子类(M_UndeadFactory
、M_ElementFactory
、M_MechanicFactory
)。
Gbl_CreateMonster
函数所依赖的Monster
类和M_ParFactory类都属于稳定部分(不需要改动的类)。M_UndeadFactory
、M_ElementFactory
、M_MechanicFactory
类以及M_Undead
、M_Element
、M_Mechanic
类都属于变化部分。Gbl_CreateMonster
函数并不依赖
于这些变化部分。
当出现一个新的怪物类型时,既不需要更改GbI_CreateMonster
函数,也不需要像简单工厂模式那样修改MonsterFactory
类中的createMonster
成员函数来增加新的if分支,除了要添加继承自Monster
的类之外,只需要为新的怪物类型增加一个新的继承自主工厂的工厂类即可。这正好符合面向对象程序设计的开闭原则一对扩展开放,对修改关闭(封闭)。所以,一般可以认为,将简单工厂模式的代码通过把工厂类进行抽象改造成符合开闭原则后的代码,就变成了工厂方法模式的代码。
当然我们也可以使用模板来实现,(前提是用户知道各种怪兽名)
template<class T>
class M_ChildFactory :public M_ParFactory {
public:virtual unique_ptr<Monster> createMonster() override {return make_unique<T>(200, 80, 100);}
};//使用
M_ChildFactory<M_Undead> myf;
unique_ptr<Monster> pm = myf.createMonster();
工厂方法的模式结构
-
产品 (Product) 将会对接口进行声明。 对于所有由创建者及其子类构建的对象, 这些接口都是通用的。
-
具体产品 (Concrete Products) 是产品接口的不同实现。
-
创建者 (Creator) 类声明返回产品对象的工厂方法。 该方法的返回对象类型必须与产品接口相匹配。
可以将工厂方法声明为抽象方法, 强制要求每个子类以不同方式实现该方法。 或者, 你也可以在基础工厂方法中返回默认产品类型。
注意, 尽管它的名字是创建者, 但它最主要的职责并不是创建产品。 一般来说, 创建者类包含一些与产品相关的核心业务逻辑。 工厂方法将这些逻辑处理从具体产品类中分离出来。 打个比方, 大型软件开发公司拥有程序员培训部门。 但是, 这些公司的主要工作还是编写代码, 而非生产程序员。
-
具体创建者 (Concrete Creators) 将会重写基础工厂方法, 使其返回不同类型的产品。
注意, 并不一定每次调用工厂方法都会创建新的实例。 工厂方法也可以返回缓存、 对象池或其他来源的已有对象。
另外,必须注意,工厂方法模式往往需要创建一个与产品等级结构(层次)相同的工厂等
级结构,这也增加了新类的层次结构和数目。
工厂方法模式的优缺点:
- 优点:符合开闭原则。添加新的产品类时,只需要添加对应的具体工厂类和产品类,不需要修改已有的工厂类代码。更容易增加新的产品类型。
- 缺点:增加了类的数量,每增加一种产品类型都需要创建相应的工厂类。而且客户需要知道每个产品的具体工厂类。
三、抽象工厂模式
抽象工厂模式适用于需要创建一系列相关或依赖的对象的场景。它不仅定义了工厂方法,还定义了多个产品对象的创建方法。它能创建一系列相关的对象, 而无需指定其具体类。
继续使用之前的例子,前面开发的单机闯关打斗类游戏,随着游戏内容越来越丰富,游戏中战斗场景(关卡)数量和类型不断增加,从原来的在城镇中战斗逐步进入在沼泽地战斗、在山脉地区战斗等。于是,策划把怪物种类进一步按照场景进行了分类,怪物目前仍旧保持3类:亡灵类、元素类和机械类。战斗场景也分为3类:沼泽地区、山脉地区和城镇。这样来划分的话,整个游戏中目前就有9类怪物:沼泽地区的亡灵类、元素类、机械类怪物;山脉地区的亡灵类、元素类、机械类怪物;城镇中的亡灵类、元素类、机械类怪物。策划规定每个区域的同类型怪物能力上差别很大,例如,沼泽地中的亡灵类怪物攻击力比城镇中的亡灵类怪物高很多,山脉地区的机械类怪物会比沼泽地区的机械类怪物生命值高许多。
这样看起来,从怪物父类Monster
继承而来的怪物子类就会由原来的3种M_Undead
、
M_Element
、M_Mechanic
变为9种,按照这样的怪物分类方式,使用工厂方法模式创建怪
物对象则需要创建多达9个工厂子类,但如果一个工厂子类能够生产不止一种具有相同规
则的怪物对象,那么就可以有效地减少所创建的工厂子类数量,这就是抽象工厂(Abstract Factory)模式的核心思想。
有两个概念在抽象工厂模式中经常被提及,分别是“产品等级结构”和“产品族”。绘制一个坐标轴,把前述的9种怪物放人其中:
在上图中,所需的三个工厂分别是Y轴的三个。抽象工厂模式是按照产品族来生产产品,一个地方一个工厂,这个工厂就负责生产本产地的所有产品。
那么我们保留之前的Monster
父类,删除原来的三个怪物子类,重新引入9个怪物。
// 沼泽亡灵类怪物
class M_Undead_Swamp : public Monster {
public:M_Undead_Swamp(int life, int magic, int attack) : Monster(life, magic, attack) {cout << "一只沼泽的亡灵类怪物来到了这个世界" << endl;}
};// 沼泽元素类怪物
class M_Element_Swamp : public Monster {
public:M_Element_Swamp(int life, int magic, int attack) : Monster(life, magic, attack) {cout << "一只沼泽的元素类怪物来到了这个世界" << endl;}
};// 沼泽机械类怪物
class M_Mechanic_Swamp : public Monster {
public:M_Mechanic_Swamp(int life, int magic, int attack) : Monster(life, magic, attack) {cout << "一只沼泽的机械类怪物来到了这个世界" << endl;}
};// 山脉亡灵类怪物
class M_Undead_Mountain : public Monster {
public:M_Undead_Mountain(int life, int magic, int attack) : Monster(life, magic, attack) {cout << "一只山脉的亡灵类怪物来到了这个世界" << endl;}
};// 山脉元素类怪物
class M_Element_Mountain : public Monster {
public:M_Element_Mountain(int life, int magic, int attack) : Monster(life, magic, attack) {cout << "一只山脉的元素类怪物来到了这个世界" << endl;}
};// 山脉机械类怪物
class M_Mechanic_Mountain : public Monster {
public:M_Mechanic_Mountain(int life, int magic, int attack) : Monster(life, magic, attack) {cout << "一只山脉的机械类怪物来到了这个世界" << endl;}
};// 城镇亡灵类怪物
class M_Undead_Town : public Monster {
public:M_Undead_Town(int life, int magic, int attack) : Monster(life, magic, attack) {cout << "一只城镇的亡灵类怪物来到了这个世界" << endl;}
};// 城镇元素类怪物
class M_Element_Town : public Monster {
public:M_Element_Town(int life, int magic, int attack) : Monster(life, magic, attack) {cout << "一只城镇的元素类怪物来到了这个世界" << endl;}
};// 城镇机械类怪物
class M_Mechanic_Town : public Monster {
public:M_Mechanic_Town(int life, int magic, int attack) : Monster(life, magic, attack) {cout << "一只城镇的机械类怪物来到了这个世界" << endl;}
};
因为工厂是针对一个产品族进行生产的,因此需要创建1个工厂父类,3个工厂子类。
// 工厂父类
class M_ParFactory {
public:virtual unique_ptr<Monster> createUndead() = 0; // 创建亡灵类怪物virtual unique_ptr<Monster> createElement() = 0; // 创建元素类怪物virtual unique_ptr<Monster> createMechanic() = 0; // 创建机械类怪物virtual ~M_ParFactory() {} // 虚析构函数
};// 沼泽工厂
class M_Factory_Swamp : public M_ParFactory {
public:unique_ptr<Monster> createUndead() override {return make_unique<M_Undead_Swamp>(300,50,120); // 创建沼泽亡灵类怪物}unique_ptr<Monster> createElement() override {return make_unique<M_Element_Swamp>(200,80,110); // 创建沼泽元素类怪物}unique_ptr<Monster> createMechanic() override {return make_unique<M_Mechanic_Swamp>(400,0,90); // 创建沼泽机械类怪物}
};// 山脉工厂
class M_Factory_Mountain : public M_ParFactory {
public:unique_ptr<Monster> createUndead() override {return make_unique<M_Undead_Mountain>(300,50,120); // 创建山脉亡灵类怪物}unique_ptr<Monster> createElement() override {return make_unique<M_Element_Mountain>(200,80,110); // 创建山脉元素类怪物}unique_ptr<Monster> createMechanic() override {return make_unique<M_Mechanic_Mountain>(400,0,90); // 创建山脉机械类怪物}
};// 城镇工厂
class M_Factory_Town : public M_ParFactory {
public:unique_ptr<Monster> createUndead() override {return make_unique<M_Undead_Town>(300,50,120); // 创建城镇亡灵类怪物}unique_ptr<Monster> createElement() override {return make_unique<M_Element_Town>(200,80,110); // 创建城镇元素类怪物}unique_ptr<Monster> createMechanic() override {return make_unique<M_Mechanic_Town>(400,0,90); // 创建城镇机械类怪物}
};//使用工厂创建怪物
int main() {// 创建不同的工厂实例unique_ptr<M_ParFactory> swampFactory = make_unique<M_Factory_Swamp>();unique_ptr<M_ParFactory> mountainFactory = make_unique<M_Factory_Mountain>();unique_ptr<M_ParFactory> townFactory = make_unique<M_Factory_Town>();// 使用工厂创建怪物实例auto swampUndead = swampFactory->createUndead();auto mountainElement = mountainFactory->createElement();auto townMechanic = townFactory->createMechanic();return 0; // 返回0表示程序成功结束
}
- 如果果游戏中的战斗场景新增加一个森林类型的场景而怪物种类不变(依旧是亡灵类怪物、元素类怪物和机械类怪物),则只需要增加一个新的子工厂类,并继承自
M_ParFactory
,而后在新的子工程类中实现createMonster_Undead
、createMonster_Element
、createMonster_Mechanic
虚函数(接口)即可。这种代码实现方式符合开闭原则,也就是通过增加新代码而不是修改原有代码来为游戏增加新功能(对森林类型场景中怪物的创建支持)。 - 如果游戏中新增加了一个新的怪物种类,则此时不但要新增3个继承自
Monster
的子类来分别支持沼泽龙类怪物、山脉龙类怪物、城镇龙类怪物,还必须修改工厂父类M_ParFactory
来增加新的虚函数以支持创建龙类怪物,各个工厂子类也需要增加对新怪物的支持。这种在工厂类中通过修改已有代码来扩充游戏功能的方式显然不符合开闭原则。所以此种情况下不适合使用抽象工厂模式。
抽象工厂的模式结构
下面再分析一下工厂方法模式与抽象工厂模式的区别:
工厂方法模式适用于一个工厂生产一个产品的需求,抽象工厂模式适用于一个工厂生产多个产品(一个产品族)的需求笔)。另外,无论是产品族数量较多还是产品等级结构数量较多,抽象工厂的优势都将更加明显。
引人抽象工厂设计模式的定义(实现意图):提供一个接口(AbstractFactory
),让该接口负责创建一系列相关或者相互依赖的对象,而无须指定它们具体的类。
- 优点:适用于创建一组相关产品的场景,可以确保产品间的一致性。
- 缺点:增加了系统的复杂性,每新增一个产品族都需要修改工厂类。
四、总结
简单工厂、工厂方法和抽象工厂是三种常见的创建型设计模式,它们各自有不同的特点和应用场景。下面做个总结:
-
代码实现复杂度上,简单工厂模式最简单,工厂方法模式次之,抽象工厂模式最复杂。简单工厂模式中的代码修改得符合开闭原则,就变成了工厂方法模式,修改工厂方法模式的代码使一个工厂支持对多个具体产品的生产,就变成了抽象工厂模式。
-
从需要的工厂数量上,简单工厂模式需要的工厂数量最少,工厂方法模式需要的工厂数量最多,抽象工厂模式能够有效地减少工厂方法模式所需要的工厂数量(可以将工厂方法模式看作抽象工厂模式的一种特例一一抽象工厂模式中的工厂若只创建一种对象就是工厂方法模式)。
-
从实际应用上,当项目中的产品数量比较少时考虑使用简单工厂模式,如果项目稍大一点或者为了满足开闭原则,则可以使用工厂方法模式,而对于大型项目中有众多厂商并且每个厂商都生产一系列产品时应考虑使用抽象工厂模式。
-
而且简单工厂模式不能遵守开放-封闭原则,工厂和抽象工厂模式可以。
-
工厂模式创建的产品对象相对简单,抽象工厂模式创建的产品对象相对复杂
- 工厂模式创建的对象对应的类不需要提供抽象类【这产品类组件中没有可变因素】
- 抽象工厂模式创建的对象对应的类有抽象的基类【这个产品类组件中有可变因素】
在许多设计工作的初期都会使用工厂方法模式(较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式(更灵活但更加复杂)。抽象工厂模式通常基于一组工厂方法, 但也可以使用原型模式来生成这些类的方法。可以同时使用工厂方法和迭代器模式来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。工厂方法是模板方法模式的一种特殊形式。 同时, 工厂方法可以作为一个大型模板方法中的一个步骤。
而且可以将抽象工厂和桥接模式搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。
相关文章:
C++设计模式创建型模式———简单工厂模式、工厂方法模式、抽象工厂模式
文章目录 一、引言二、简单工厂模式三、工厂方法模式三、抽象工厂模式四、总结 一、引言 创建一个类对象的传统方式是使用关键字new , 因为用 new 创建的类对象是一个堆对象,可以实现多态。工厂模式通过把创建对象的代码包装起来,实现创建对…...
C++ 类与对象(中) 默认成员函数
我们知道在类中,有成员变量和成员函数,我们可以通过创造不同的成员函数来实现这个类不同的功能,如果我们创造一个类,却不实现它的成员函数会如何呢?这个就涉及到类中的默认成员函数的概念了。但在本文我们主要介绍以下…...
中间人攻击(https降级攻击)和iptables命令分析
中间人攻击 以下是一个简单的中间人攻击示例,结合 ARP 欺骗和流量修改: 1. 进行 ARP 欺骗 首先,使用 arpspoof 进行 ARP 欺骗,将受害者的流量重定向到攻击者的机器上: sudo arpspoof -i eth0 -t 172.29.144.50 172…...
开源生活-分布式管理
开源竞争(当自己没有办法彻底掌握一门技术的时候就彻底开源掉;培养出更多的依赖,让更多人帮助你完善你的技术,那么这不就是在砸罐子吗?一个行业里面总会有人先砸罐子的,你不如先砸罐子,还能听个…...
华为OD机试真题- 关联子串
该专栏题目包含两部分: 100 分值部分题目 200 分值部分题目 所有题目都会陆续更新,订阅防丢失 题目描述: 给定两个字符串str1和str2,如果字符串str1中的字符,经过排列组合后的字符串中,只要有一个字符串是…...
云智慧完成华为原生鸿蒙系统的适配, 透视宝 APM 为用户体验保驾护航
2024 年 10 月 22 日,首个国产移动操作系统 —— 华为原生鸿蒙操作系统 HarmonyOS NEXT 正式面世,成为继 iOS 和 Android 后的全球第三大移动操作系统。HarmonyOS NEXT,从系统内核、数据库根基,到编程语言创新、AI(人工…...
QT 多语言转换 ts、qm
QT开发之路 企业级开发系列文章,主要目标快速学习、完善、提升 相关技能 高效完成企业级项目开发 分享在企业中积累的实用技能和经验。 通过具体的编码过程、代码示例、步骤详解、核心内容和展示的方法解决遇到的实际问题。 阅读前声明 本系列文章属于付费内容 禁止…...
C++学习:类和对象(二)
一、默认成员函数 1. 什么是默认成员函数? 在C中,每个类都有一些特殊的成员函数,如果程序员没有显式地声明,编译器会自动为类生成这些函数,这些函数称为默认成员函数 2. 默认成员函数列表 默认构造函数(…...
深度学习(五):语音处理领域的创新引擎(5/10)
一、深度学习在语音处理中的崛起 在语音处理领域,传统方法如谱减法、维纳滤波等在处理复杂语音信号时存在诸多局限性。这些方法通常假设噪声是平稳的,但实际噪声往往是非平稳的,导致噪声估计不准确。同时,为了去除噪声࿰…...
双曲函数(Hyperbolic functuons)公式
在python等语言里有双曲函数库和反双曲函数库,但是并没有包含所有的双曲函数。以numpy为例子,numpy只提供了sinh、cosh、tanh、arcsinh、arccosh、arctanh六种函数,那么其余的就需要用公式计算了。 转换公式 对于函数库不能直接计算的&#…...
【CSS/SCSS】@layer的介绍及使用方法
目录 基本用法layer 的作用与优点分离样式职责,增强代码可读性和可维护性防止无意的样式冲突精确控制样式的逐层覆盖提高复用性 兼容性实际示例:使用 import 管理加载顺序实际示例:混入与 layer 结合使用 layer 是 CSS 中用于组织和管理样式优…...
我为什么投身于青少年AI编程?——打造生态圈(三)
第五部分 青少年AI编程生态圈 一、生态圈 主要涵盖家庭、社区/中小学、高校高职、主管部门。 1、家庭 我们与社区/中小学一道打造让家长满意的模式。 教得好: 费用少: 家门口: 2、社区/中小学 社区党群服务中心和中小学都有大面积科普…...
出海要深潜,中国手机闯关全球化有了新标杆
经济全球化的大势之下,中国科技企业开拓海外市场已成为一种必然选择。 对于国内手机企业来说,推进全球商业版图扩张,业务潜力巨大,海外市场是今后的关键增长引擎。 当前中国手机厂商在海外市场的发展,有收获也有坎坷…...
百度SEO中的关键词密度与内容优化研究【百度SEO专家】
大家好,我是百度SEO专家(林汉文),在百度SEO优化中,关键词密度和关键词内容的优化对提升页面排名至关重要。关键词的合理布局与内容的质量是确保网页在百度搜索结果中脱颖而出的关键因素。下面我们将从关键词密度和关键…...
如何用fastapi集成pdf.js 的viewer.html ,并支持 mjs
fastapi 框架 集成pdf.js 的 viewer.html?file=***,支持跨域,支持.mjs .wasm .pdf 给出完整示例代码 要在 FastAPI 框架中集成 pdf.js 的 viewer.html,并支持跨域访问以及 .mjs、.wasm、.pdf 文件的正确加载,可以按照以下步骤进行。下面提供一个完整的示例,包括项目结构…...
文件相对路径与绝对路径
前言: 在写代码绘制图像的过程中,发现出现cant read input file的异常,而且输出框没有绘制图片,所以寻找解决方案。先贴上之前写的简洁版绘制图像代码 1.BackGround类 import java.awt.image.BufferedImage;public class BackG…...
Linux 重启命令全解析:深入理解与应用指南
Linux 重启命令全解析:深入理解与应用指南 在 Linux 系统中,掌握正确的重启命令是确保系统稳定运行和进行必要维护的关键技能。本文将深入解析 Linux 中常见的重启命令,包括功能、用法、适用场景及注意事项。 一、reboot 命令 功能简介 re…...
【北京迅为】《STM32MP157开发板嵌入式开发指南》-第六十七章 Trusted Firmware-A 移植
iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器,既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构,主频650M、1G内存、8G存储,核心板采用工业级板对板连接器,高可靠,牢固耐…...
`a = a + b` 与 `a += b` 的区别
在 Java 中,a a b 和 a b 都用于将 b 的值加到 a 上,但它们之间存在一些重要的区别,尤其是在类型转换和操作行为方面。 使用 操作符时,Java 会自动进行隐式类型转换,而使用 则不会。这意味着在 a b 的情况下&am…...
mysqld.log文件过大,清理后不改变所属用户
#1024程序员节# 一、背景 突然有一天,我的mysql报磁盘不足了。仔细查看才发现,是磁盘满了。而MySQL的日志文件占用了91个G.如下所示: [roothost-172-16-14-128 mysql]# ls -lrth 总用量 93G -rw-r----- 1 mysql mysql 1.1G 7月 30 2023 m…...
v4.7+版本用户充值在交易统计中计算双倍的问题修复
app/services/statistic/TradeStatisticServices.php 文件中 $whereInRecharge[recharge_type] no_system; $whereInRecharge[recharge_type] system; app/model/user/UserRecharge.php 中 修改此搜索器内容 public function searchRechargeTypeAttr($query, $value){ if…...
[GXYCTF 2019]Ping Ping Ping 题解(多种解题方式)
知识点: 命令执行 linux空格绕过 反引号绕过 变量绕过 base64编码绕过 打开页面提示 "听说php可以执行系统函数?我来康康" 然后输入框内提示输入 bjut.edu.cn 输入之后回显信息,是ping 这个网址的信息 输入127.0.0.1 因为提示是命令…...
MODSI EVI 数据的时间序列拟合一阶谐波模型
目录 简介 函数 ee.Reducer.linearRegression(numX, numY) Arguments: Returns: Reducer ee.Image.cat(var_args) Arguments: Returns: Image hsvToRgb() Arguments: Returns: Image 代码 结果 简介 MODIS/006/MOD13A1数据是由美国国家航空航天局(NASA)的MODIS…...
Java:String类(超详解!)
一.常用方法 🥏1.字符串构造 字符串构造有三种方法: 📌注意: 1. String是引用类型,内部并不存储字符串本身 如果String是一个引用那么s1和s3应该指向同一个内容,s1和s2是相等的,应该输出两…...
【日志】力扣13.罗马数字转整数 || 解决泛型单例热加载失败问题
2024.10.28 【力扣刷题】 13. 罗马数字转整数 - 力扣(LeetCode)https://leetcode.cn/problems/roman-to-integer/description/?envTypestudy-plan-v2&envIdtop-interview-150这题用模拟的思想可以给相应的字母赋值,官方的答案用的是用一…...
Mybatis高级
系列文章目录 高级Mybatis,一些结果映射,引入新的注解 目录 系列文章目录 文章目录 一、结果映射 1.ResultType 2.ResultMap 基础应用: 二、一对一 嵌套结果和嵌套查询 嵌套结果 嵌套查询 区别 三、一对多 四、多对多 五、注解补充 1.一对一…...
【spark】spark structrued streaming读写kafka 使用kerberos认证
spark版本:2.4.0 官网 Spark --files使用总结 Spark --files理解 一、编写jar import org.apache.kafka.clients.CommonClientConfigs import org.apache.kafka.common.config.SaslConfigs import org.apache.spark.sql.SparkSession import org.apache.spark.sql.streaming.T…...
【脚本】B站视频AB复读
控制台输入如下代码,回车 const video document.getElementsByTagName("video")[0];//获取bpx-player-control-bottom-center容器,更改其布局方式const div document.getElementsByClassName("bpx-player-control-bottom-center")[0];div.sty…...
leetcode - 257. 二叉树的所有路径
257. 二叉树的所有路径 题目 解决 做法一:深度优先搜索 回溯 深度优先搜索(Depth-First Search, DFS)是一种用于遍历或搜索树或图的算法。这种搜索方式会尽可能深地探索每个分支,直到无法继续深入为止,然后回溯到上…...
python 相关
python 1. pip 安装某个版本范围的软件 pip install “elasticsearch>6,<7” pip install elasticsearchX.Y.Z 2. pip 查看包版本 pip show pandas 3. pip 下载whl包 https://tendcode.com/subject/article/pip-offline-download/ (更多平台与架构)pip downl…...
信息手机网站模板/简单的网页设计作品
ID3V2ID3V2 到现在一共有4个版本{ID3V2.1,ID3V2.2 ID3V2.3 ID3V2.4},最流行的为第3版,即 ID3V2.3 ,由于ID3V1记录在文件末尾,ID3V2一般记录在文件头,据闻ID3V2.4也可以记录在文件末尾。ID3V2特点:可伸缩可扩展…...
.net做网站/竞价推广代运营
Excel表格中的迷你图表能够直观地向我们展示出数据的变化趋势。本文将介绍C#如何实现为表格数据生成迷你图表,以及修改和删除迷你图表的方法。下面将详细讲述。原Excel图表:一、添加迷你图表(折线图、柱形图、盈亏图)1.添加命名空间using System;using S…...
批量入侵wordpress/拉新app渠道
背景 最近一直在做公司的应用软件服务架构的升级工作,里面涉及使用mod_proxy替换先前的mod_ajp,因为我们要用jetty7。 同时万恶的jetty 7对ajp协议支持不是很好, 具体可见我的另一篇博文: 纠结的mod_jk与jetty的组合。 在线下测…...
wordpress付款插件/苏州网站seo服务
Linux文件系统结构 Linux目录结构的组织形式和Windows有很大的不同。首先Linux没有“盘(C盘、D盘、E盘)”的概念。已经建立文件系统的硬盘分区被挂载到某一个目录下,用户通过操作目录来实现磁盘读写。 Linux不像Windows那样的系统目录,Linux使用正斜杠&q…...
用文字写美食个人网站设计作品/网站做seo教程
题库来源:安全生产模拟考试一点通公众号小程序 2022年低压电工理论题库是低压电工全部考试题库全真模拟题!2022年低压电工考试题模拟考试题库模拟考试平台操作根据低压电工考试大纲。低压电工考试资料通过安全生产模拟考试一点通上提前检验学习成果。 1…...
山东省旅游局网站建设情况/品牌营销策划书
Maven学习总结(五)——聚合与继承 一、聚合 如果我们想一次构建多个项目模块,那我们就需要对多个项目模块进行聚合 1.1、聚合配置代码 1 <modules> 2 <module>模块一</module> 3 <module>模块二</module> 4 <mo…...