C++设计模式创建型模式———简单工厂模式、工厂方法模式、抽象工厂模式

news2024/11/6 0:13:42

文章目录

  • 一、引言
  • 二、简单工厂模式
  • 三、工厂方法模式
  • 三、抽象工厂模式
  • 四、总结

一、引言

创建一个类对象的传统方式是使用关键字new , 因为用 new 创建的类对象是一个堆对象,可以实现多态。工厂模式通过把创建对象的代码包装起来,实现创建对象的代码与具体的业务逻辑代码相隔离的目的(将对象的创建和使用进行解耦)。

试想,如果创建一个类A的对象,可能会写出A* pa=new A()这样的代码行,但当给类A的构造函数增加一个参数时,所有利用new创建类A对象的代码行全部需要修改,如果通过工厂模式把创建类A对象的代码统一放到某个位置,则对于诸如给类A的构造函数增加参数之类的问题,只需要修改一个位置就可以了。

工厂模式属于创建型模式,一般可以细分为3种:简单工厂模式、工厂方法模式和抽象

工厂模式主要包括简单工厂模式工厂方法模式抽象工厂模式


二、简单工厂模式

简单工厂模式并不属于设计模式中的经典模式之一,但它是工厂模式的基础。它通过一个工厂类根据传入的参数来决定创建哪种类的对象。

简单工厂模式的核心思想是:将对象的创建集中管理,通过一个工厂类来实例化对象,而不是让客户端直接使用 new 操作符来创建对象。这样就可以将对象的创建和使用分离,提高代码的可维护性和灵活性。

简单工厂模式主要由以下三部分组成:

  1. 工厂类(Factory):负责对象的创建,根据传入的参数,决定创建哪种产品对象。
  2. 抽象产品类(Abstract Product):定义了产品的接口或抽象类,规定了所有产品必须实现的功能。
  3. 具体产品类(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_UndeadM_ElementM_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_UndeadM_ElementM_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_UndeadM_Element
M_Mechanic,那么,就需要创建3个工厂类,例如分别命名为M_UndeadFactory
M_ElementFactoryM_MechanicFactory。而且这3个工厂类还会共同继承自同一个工厂父类,例如将该工厂父类命名为M_ParFactory(工厂抽象类)。

如果将来策划要求引人第四种类型的怪物,那么毫无疑问,需要为该种类型的怪物增加
对应的一个新工厂类,当然该新工厂类依然继承自M_ParFactory类。

从上面的描述,可以初步看出,工厂方法模式通过增加新的工厂类来符合开闭原则(对扩展开放,对修改关闭),但付出的代价是需要增加多个新的工厂类。

下面开始改造简单工厂模式中实现的代码。实现所有工厂类的父类M_ParFactory(等价于将简单工厂模式中的工厂类MonsterFactory进行抽象),代码如下:

// 抽象工厂类 所有工厂类的父类
class M_ParFactory {
public:
    virtual unique_ptr<Monster> createMonster() = 0; // 工厂方法
    virtual ~M_ParFactory(){}
};

然后,针对每个具体的怪物子类,都需要创建一个相关的工厂类,所以,针对
M_UndeadM_ElementM_Mechanic类,创建3个工厂类M_UndeadFactoryM_ElementFactoryM_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_UndeadM_ElementM_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_UndeadFactoryM_ElementFactoryM_MechanicFactory)决定要实例化的类是哪一个。该模式使得某个类(M_UndeadM_ElementM_Mechanic)的实例化延迟到子类(M_UndeadFactoryM_ElementFactoryM_MechanicFactory)。

在这里插入图片描述

  • Gbl_CreateMonster函数所依赖的Monster类和M_ParFactory类都属于稳定部分(不需要改动的类)。
  • M_UndeadFactoryM_ElementFactoryM_MechanicFactory类以及M_UndeadM_ElementM_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();

工厂方法的模式结构

在这里插入图片描述

  1. 产品 (Product) 将会对接口进行声明。 对于所有由创建者及其子类构建的对象, 这些接口都是通用的。

  2. 具体产品 (Concrete Products) 是产品接口的不同实现。

  3. 创建者 (Creator) 类声明返回产品对象的工厂方法。 该方法的返回对象类型必须与产品接口相匹配。

    可以将工厂方法声明为抽象方法, 强制要求每个子类以不同方式实现该方法。 或者, 你也可以在基础工厂方法中返回默认产品类型。

    注意, 尽管它的名字是创建者, 但它最主要的职责并不是创建产品。 一般来说, 创建者类包含一些与产品相关的核心业务逻辑。 工厂方法将这些逻辑处理从具体产品类中分离出来。 打个比方, 大型软件开发公司拥有程序员培训部门。 但是, 这些公司的主要工作还是编写代码, 而非生产程序员。

  4. 具体创建者 (Concrete Creators) 将会重写基础工厂方法, 使其返回不同类型的产品。

注意, 并不一定每次调用工厂方法都会创建新的实例。 工厂方法也可以返回缓存、 对象池或其他来源的已有对象。

另外,必须注意,工厂方法模式往往需要创建一个与产品等级结构(层次)相同的工厂等
级结构,这也增加了新类的层次结构和数目。

工厂方法模式的优缺点:

  • 优点:符合开闭原则。添加新的产品类时,只需要添加对应的具体工厂类和产品类,不需要修改已有的工厂类代码。更容易增加新的产品类型。
  • 缺点:增加了类的数量,每增加一种产品类型都需要创建相应的工厂类。而且客户需要知道每个产品的具体工厂类。

三、抽象工厂模式

抽象工厂模式适用于需要创建一系列相关或依赖的对象的场景。它不仅定义了工厂方法,还定义了多个产品对象的创建方法。它能创建一系列相关的对象, 而无需指定其具体类。

继续使用之前的例子,前面开发的单机闯关打斗类游戏,随着游戏内容越来越丰富,游戏中战斗场景(关卡)数量和类型不断增加,从原来的在城镇中战斗逐步进入在沼泽地战斗、在山脉地区战斗等。于是,策划把怪物种类进一步按照场景进行了分类,怪物目前仍旧保持3类:亡灵类、元素类和机械类。战斗场景也分为3类:沼泽地区、山脉地区和城镇。这样来划分的话,整个游戏中目前就有9类怪物:沼泽地区的亡灵类、元素类、机械类怪物;山脉地区的亡灵类、元素类、机械类怪物;城镇中的亡灵类、元素类、机械类怪物。策划规定每个区域的同类型怪物能力上差别很大,例如,沼泽地中的亡灵类怪物攻击力比城镇中的亡灵类怪物高很多,山脉地区的机械类怪物会比沼泽地区的机械类怪物生命值高许多。

这样看起来,从怪物父类Monster继承而来的怪物子类就会由原来的3种M_Undead
M_ElementM_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_UndeadcreateMonster_ElementcreateMonster_Mechanic虚函数(接口)即可。这种代码实现方式符合开闭原则,也就是通过增加新代码而不是修改原有代码来为游戏增加新功能(对森林类型场景中怪物的创建支持)。
  • 如果游戏中新增加了一个新的怪物种类,则此时不但要新增3个继承自Monster的子类来分别支持沼泽龙类怪物、山脉龙类怪物、城镇龙类怪物,还必须修改工厂父类M_ParFactory来增加新的虚函数以支持创建龙类怪物,各个工厂子类也需要增加对新怪物的支持。这种在工厂类中通过修改已有代码来扩充游戏功能的方式显然不符合开闭原则。所以此种情况下不适合使用抽象工厂模式。

在这里插入图片描述

抽象工厂的模式结构

在这里插入图片描述

下面再分析一下工厂方法模式与抽象工厂模式的区别

工厂方法模式适用于一个工厂生产一个产品的需求,抽象工厂模式适用于一个工厂生产多个产品(一个产品族)的需求笔)。另外,无论是产品族数量较多还是产品等级结构数量较多,抽象工厂的优势都将更加明显。

引人抽象工厂设计模式的定义(实现意图):提供一个接口(AbstractFactory),让该接口负责创建一系列相关或者相互依赖的对象,而无须指定它们具体的类。

  • 优点:适用于创建一组相关产品的场景,可以确保产品间的一致性。
  • 缺点:增加了系统的复杂性,每新增一个产品族都需要修改工厂类。

四、总结

简单工厂、工厂方法和抽象工厂是三种常见的创建型设计模式,它们各自有不同的特点和应用场景。下面做个总结:

  • 代码实现复杂度上,简单工厂模式最简单,工厂方法模式次之,抽象工厂模式最复杂。简单工厂模式中的代码修改得符合开闭原则,就变成了工厂方法模式,修改工厂方法模式的代码使一个工厂支持对多个具体产品的生产,就变成了抽象工厂模式。

  • 从需要的工厂数量上,简单工厂模式需要的工厂数量最少,工厂方法模式需要的工厂数量最多,抽象工厂模式能够有效地减少工厂方法模式所需要的工厂数量(可以将工厂方法模式看作抽象工厂模式的一种特例一一抽象工厂模式中的工厂若只创建一种对象就是工厂方法模式)。

  • 从实际应用上,当项目中的产品数量比较少时考虑使用简单工厂模式,如果项目稍大一点或者为了满足开闭原则,则可以使用工厂方法模式,而对于大型项目中有众多厂商并且每个厂商都生产一系列产品时应考虑使用抽象工厂模式。

  • 而且简单工厂模式不能遵守开放-封闭原则,工厂和抽象工厂模式可以。

  • 工厂模式创建的产品对象相对简单,抽象工厂模式创建的产品对象相对复杂

    • 工厂模式创建的对象对应的类不需要提供抽象类【这产品类组件中没有可变因素】
    • 抽象工厂模式创建的对象对应的类有抽象的基类【这个产品类组件中有可变因素】

在许多设计工作的初期都会使用工厂方法模式(较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式(更灵活但更加复杂)。抽象工厂模式通常基于一组工厂方法, 但也可以使用原型模式来生成这些类的方法。可以同时使用工厂方法和迭代器模式来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。工厂方法是模板方法模式的一种特殊形式。 同时, 工厂方法可以作为一个大型模板方法中的一个步骤。

而且可以将抽象工厂和桥接模式搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2230357.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据库系统概论】第3章 关系数据库标准语言SQL(二)数据查询(超详细)

目录 一、单表查询 1. 简单的数据查询 &#xff08;1&#xff09;选择表中若干列 &#xff08;2&#xff09;选择表中若干行&#xff08;元祖&#xff09; 2. 聚合函数与分组查询 聚集函数 GROUP BY分组查询 二、联接查询 1、连接概述 2. 内联接&#xff08;INNER JO…

Android Framework AMS(10)广播组件分析-1

该系列文章总纲链接&#xff1a;专题总纲目录 Android Framework 总纲 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;本章节主要解读应用层广播组件的发送广播和接收处理广播 2个过程&#xff0c;以及从APP层到AMS调用之间的打通。关注思维导图中左上部分即可。 有…

磁盘空间不足导致postgreSQL启动失败

背景&#xff1a; 智慧庭审平台安装了ivr/xvr等vr应用后&#xff0c;磁盘空间不足导致postgreSQL数据库一直重启 排查 到服务器下使用 systemctl status hik.postgresql96linux64.rdbms.1.service 查看进程报错信息 这次报的是 FATAL: could not write lock file "po…

C++进阶:C++11的新特性

✨✨所属专栏&#xff1a;C✨✨ ✨✨作者主页&#xff1a;嶔某✨✨ C11的发展历史 2011年&#xff0c;C标准委员会发布了C11标准&#xff0c;这是C的一次巨大飞跃&#xff0c;引入了许多重要的新特性&#xff0c;如智能指针、lambda表达式、并发编程支持等。这一版本的发布对C社…

LongVU :Meta AI 的解锁长视频理解模型,利用自适应时空压缩技术彻底改变视频理解方式

Meta AI在视频理解方面取得了令人瞩目的里程碑式成就&#xff0c;推出了LongVU&#xff0c;这是一种开创性的模型&#xff0c;能够理解以前对人工智能系统来说具有挑战性的长视频。 研究论文 "LongVU&#xff1a;用于长视频语言理解的时空自适应压缩 "提出了一种革命…

Ubuntu 22.04安装部署

一、部署环境 表 1‑1 环境服务版本号系统Ubuntu22.04 server lts运行环境1JDK1.8前端WEBNginx1.8数据库postgresqlpostgresql13postgis3.1pgrouting3.1消息队列rabbitmq3.X(3.0以上)运行环境2erlang23.3.3.1 二、安装系统 2.1安装 1.安装方式&#xff0c;选第一条。 2.选择…

无需手动部署的正式版comfyUI是否就此收费?开源等同免费?

​ ​ 关于ComfyUI的正式版是否会收费的问题是很多AI玩家都关心的问题。 一旦ComfyUI正式版发布&#xff0c;我们是否需要为它买单&#xff1f;不再开源 同时这也引出了一个核心问题&#xff1a;开源究竟等不等于免费&#xff1f; ComfyUI正式版到底是什么&#xff1f;它会收…

云计算作业二Spark:问题解决备忘

安装spark 教程源地址&#xff1a;https://blog.csdn.net/weixin_52564218/article/details/141090528 镜像下载 教程给的官网下载地址很慢&#xff0c;https://archive.apache.org/dist/spark/spark-3.1.1/ 这里的镜像快很多&#xff1a; 清华软件源&#xff1a;https://mi…

C语言 | Leetcode C语言题解之第524题通过删除字母匹配到字典里最长单词

题目&#xff1a; 题解&#xff1a; char * findLongestWord(char * s, char ** d, int dSize){char *result "";int max -1;for (int i 0; i < dSize; i) {char *p s, *q d[i];int j 0, k 0;while (p[j] ! \0 && q[k] ! \0) {if (p[j] q[k]) {k…

【含文档】基于ssm+jsp的学科竞赛系统(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: apache tomcat 主要技术: Java,Spring,SpringMvc,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定义了四个…

【5.5】指针算法-三指针解决颜色分类

一、题目 给定一个包含红色、白色和蓝色&#xff0c;一共n个元素的数组&#xff0c;原地对它们进行排序&#xff0c;使得相同颜色的元素相邻&#xff0c;并按照红色、白色、蓝色顺序排列。 此题中&#xff0c;我们使用整数0、1和2分别表示红色、白色和蓝色。 示例 1&#xff1…

双向链表及如何使用GLib的GList实现双向链表

双向链表是一种比单向链表更为灵活的数据结构&#xff0c;与单向链表相比可以有更多的应用场景&#xff0c;本文讨论双向链表的基本概念及实现方法&#xff0c;并着重介绍使用GLib的GList实现单向链表的方法及步骤&#xff0c;本文给出了多个实际范例源代码&#xff0c;旨在帮助…

web——warmup——攻防世界

这道题还是没有做出来。。&#xff0c;来总结一下 1.ctrlU显示源码 2.看见body里有source.php 打开这个source.php 看见了源码 highlight_file(FILE); 这行代码用于高亮显示当前文件的源码&#xff0c;适合调试和学习&#xff0c;但在生产环境中通常不需要。 class emmm 定义…

【MATLAB代码】三个CT模型的IMM例程,各CT旋转速率不同,适用于定位、导航、目标跟踪

三个CT模型&#xff0c;各CT模型下的运动旋转速率不同&#xff0c;适用于定位、导航、目标跟踪 文章目录 代码构成运行结果源代码代码讲解概述代码结构1. 初始化2. 仿真参数设置3. 生成量测数据4. IMM迭代5. 绘图 主要功能函数部分1. 卡尔曼滤波函数2. 模型综合函数3. 模型概率…

sklearn 实现随机森林分类器 - python 实现

python sklearn 实现随机森林分类器 from sklearn.ensemble import RandomForestClassifier from sklearn.datasets import load_iris # 加载数据集 irisload_iris() x,yiris.data,iris.target print("x y shape:",x.shape,y.shape) # 创建并训练模型 model Random…

GetX的一些高级API

目录 前言 一、一些常用的API 二、局部状态组件 1.可选的全局设置和手动配置 2.局部状态组件 1.ValueBuilder 1.特点 2.基本用法 2.ObxValue 1.特点 2.基本用法 前言 这篇文章主要讲解GetX的一些高级API和一些有用的小组件。 一、一些常用的API GetX提供了一些高级…

WPF+MVVM案例实战(十一)- 环形进度条实现

文章目录 1、运行效果2、功能实现1、文件创建与代码实现2、角度转换器实现3、命名空间引用3、源代码下载1、运行效果 2、功能实现 1、文件创建与代码实现 打开 Wpf_Examples 项目,在Views 文件夹下创建 CircularProgressBar.xaml 窗体文件。 CircularProgressBar.xaml 代码实…

SYN590RH

一般描述 SYN590RH是SYNOXO全新开发设计的一款宽电压范围&#xff0c;低功耗&#xff0c;高性能&#xff0c;无需外置AGC电容&#xff0c;灵敏度达到典型-110 dBm,400MHz~450MHz频率范围应用的单芯片ASK或00 K射频接收器。 SYN590RH是一款典型的即插即用型单片高…

kafka里的consumer 是推还是拉?

大家好&#xff0c;我是锋哥。今天分享关于【kafka里的consumer 是推还是拉&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; kafka里的consumer 是推还是拉&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在Kafka中&#xff0c;消费者&…

C语言的数组地址 数组的遍历与练习

1.int main(void) { int a[5] { 10,20,30,40,50 };//数组间的元素地址相连的 int* p; printf("%d\n", &a[0]); printf("%d\n", &a[1]); printf("%d\n", &a[2]); printf("%d\n", &a[3]); …