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

文章目录

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

一、引言

创建一个类对象的传统方式是使用关键字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),让该接口负责创建一系列相关或者相互依赖的对象,而无须指定它们具体的类。

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

四、总结

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

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

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

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

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

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

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

生成式 AI 与向量搜索如何扩大零售运营:巨大潜力尚待挖掘

在竞争日益激烈的零售领域&#xff0c;行业领导者始终在探索革新客户体验和优化运营的新途径&#xff0c;而生成式 AI 和向量搜索在这方面将大有可为。从个性化营销到高效库存管理&#xff0c;二者在零售领域的诸多应用场景中都展现出变革性潜力&#xff0c;已成为保持行业领先…

leetcode438. 找到字符串中所有字母异位词

题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 示例 1: 输入: s "cbaebabacd", p "abc" 输出: [0,6] 解释: 起始索引等于 0 的子串是 &quo…

【IC每日一题】

IC每日一题 1 组合逻辑VS时序逻辑1.1 组合逻辑1.1.1 竞争冒险1.1.2 解决方法 1.2 时序逻辑1.3 比较1.4 场景 2 计数器2.1 代码片段法2.2 实现计数器--异步复位&#xff0c;带clear端&#xff0c;计10则归0&#xff1b; 1 组合逻辑VS时序逻辑 1.1 组合逻辑 组合逻辑&#xff1…

MySQL练习题-求连续、累计、环比和同比问题

目录 准备数据 1&#xff09;求不同产品每个月截止当月最近3个月的平均销售额 2&#xff09;求不同产品截止当月的累计销售额 3&#xff09;求环比增长率和同比增长率 准备数据 -- product 表示产品名称&#xff0c;ym 表示年月&#xff0c;amount 表示销售金额&#xff…

【K8S系列】Kubernetes Service 基础知识 详细介绍

在 Kubernetes 中&#xff0c;Service 是一种抽象的资源&#xff0c;用于定义一组 Pod 的访问策略。它为这些 Pod 提供了一个稳定的访问入口&#xff0c;解决了 Pod 可能频繁变化的问题。本文将详细介绍 Kubernetes Service 的类型、功能、使用场景、DNS 和负载均衡等方面。 1.…

react 总结+复习+应用加深

文章目录 一、React生命周期1. 挂载阶段&#xff08;Mounting&#xff09;补充2. 更新阶段&#xff08;Updating&#xff09;补充 static getDerivedStateFromProps 更新阶段应用补充 getSnapshotBeforeUpdate3. 卸载阶段&#xff08;Unmounting&#xff09; 二、React组件间的…

windows 训练yolov8官方数据集

第一步&#xff1a;安装Anaconda3-2024.06-1-Windows-x86_64.exe 下载地址&#xff1a;https://repo.anaconda.com/archive/ 第二步&#xff1a;创建环境 打开Anaconda Prompt 输入 conda info -e 打印&#xff1a; 已经安装了一些环境&#xff0c;然后我们创建新的环境&a…

大模型面试挺水的,面试官听到这些直接过

AI大模型600道面试总结(LLM) 1、目前主流的开源模型体系有哪些? 目前 主流的开源模型体系 分三种: 第一种:prefixDecoder系 介绍:输入双向注意力&#xff0c;输出单向注意力 代表模型:ChatGLM、ChaGLM2、U-PaLM 第二种:causal Decader系 介绍:从左到右的单向注意力 代表…

【状态机DP】力扣1186. 删除一次得到子数组最大和

给你一个整数数组&#xff0c;返回它的某个 非空 子数组&#xff08;连续元素&#xff09;在执行一次可选的删除操作后&#xff0c;所能得到的最大元素总和。换句话说&#xff0c;你可以从原数组中选出一个子数组&#xff0c;并可以决定要不要从中删除一个元素&#xff08;只能…

驱动-----LED

前面我们学习了demo1的驱动的编写,在写LED的时候,我们可以在demo1的基础上修改。 1.首先就是修改名字,把所有的demo改成led,使用一个字符串替换指令。 2.设备号要变 3.想操作硬件,LED的初始化,亮灭 LED的初始化,在open的时候实现。 亮灭在write的时候实现。 现在就是…

技术成神之路:设计模式(二十三)解释器模式

相关文章&#xff1a;技术成神之路&#xff1a;二十三种设计模式(导航页) 介绍 解释器模式&#xff08;Interpreter Pattern&#xff09;是一种行为设计模式&#xff0c;用于定义一种语言的文法表示&#xff0c;并提供一个解释器来处理这种文法。它用于处理具有特定语法或表达…

移远通信斩获两项车载大奖,引领全球智能网联汽车产业发展

10月24日&#xff0c;由盖世汽车主办的2024第六届金辑奖中国汽车新供应链百强颁奖盛典在上海隆重举行。 作为全球领先的物联网和车联网整体解决方案供应商&#xff0c;移远通信凭借智能座舱模组AG855G、车载5G模组AG59x系列&#xff0c;以及公司在海外市场的优异表现&#xff0…

Mac 上无法烧录 ESP32C3 的问题记录:A fatal error occurred:Failed to write to target RAM

文章目录 问题描述驱动下载地址问题解决&#xff1a;安装 CH343 驱动踩的坑日志是乱码 问题描述 我代码编译可以&#xff0c;但是就是烧录不上去 A fatal error occurred:Failed to write to target RAM(result was 01070000:Operation timed out) Uploaderror:上传失败&…

selenium脚本编写及八大元素定位方法

selenium脚本编写 上篇文章介绍了selenium环境搭建&#xff0c;搭建好之后就可以开始写代码了 基础脚本,打开一个网址 from selenium import webdriver driver webdriver.Chrome()#打开chrome浏览器 driver.get(https://www.baidu.com) #打开百度 打开本地HTML文件 上篇…

ctfshow(265->266)--反序列化漏洞--指针引用与php://input读取请求体

Web265 源代码&#xff1a; error_reporting(0); include(flag.php); highlight_file(__FILE__); class ctfshowAdmin{public $token;public $password;public function __construct($t,$p){$this->token$t;$this->password $p;}public function login(){return $this…

企业贷款大揭秘:税贷VS票贷,哪个更适合你?

在金融界&#xff0c;资金就像是现代经济的血液&#xff0c;特别是对于企业的发展来说&#xff0c;银行的资金支持简直是不可或缺的。最近&#xff0c;多家银行可是动作频频&#xff0c;加快了资金投放的步伐&#xff0c;尤其是制造业、小微企业、专精特新以及“三农”这些领域…

网络编程 Linux环境 C语言实现

进程间通信的延续 跨电脑进程间通信 一、远程通信方式 电路交换------老式有线电话通信 ​ 报文交换 ​ 分组交换 支持分时机制的(分片机制)报文交换 ​现行网络大部分都是采用分组交换形式 二、网络&互联网&因特网 网络Network&#xff1a;多台计算机通过某种传输…

Javaee---多线程(一)

文章目录 1.线程的概念2.休眠里面的异常处理3.实现runnable接口4.匿名内部类子类创建线程5.匿名内部类接口创建线程6.基于lambda表达式进行线程创建7.关于Thread的其他的使用方法7.1线程的名字7.2设置为前台线程7.3判断线程是否存活 8.创建线程方法总结9.start方法10.终止&…

微积分复习笔记 Calculus Volume 1 - 3.5 Derivatives of Trigonometric Functions

3.5 Derivatives of Trigonometric Functions - Calculus Volume 1 | OpenStax

西门子S7-200 SMART 多泵轮换功能库案例下载

通用描述 在现场使用多台风机水泵的场合&#xff0c;需要考虑对多台风机水泵进行轮换&#xff0c;因此如何合 理的对多台风机水泵进行轮换就成了一道难题&#xff0c;本文针对上述情况&#xff0c;专门开发了多 泵轮换的应用库&#xff0c;可以方便统计泵的运行时间&#xf…