设计模式之四:工厂模式

引言:除了使用new操作符之外,还有更多制造对象的方法。同时,实例化这个活动不应该总是公开地进行。

1.简单工厂模式

这里有一些相关的具体类,要在运行时有一些具体条件来决定究竟实例化哪个类。这样的代码(if..elseif..elseif),一旦有变化或扩展,就必须重新打开进行检查和修改。

Pizza orderPizza(String type)
{
	Pizza* pizza;

	if (type.equals("cheese"))
	{
		pizza = new CheesePizza();
	}
	else if (type.equals("greek"))
	{
		pizza = new GreekPizza();
	}
	else if (type.equals("pepperoni"))
	{
		pizza = new PepperoniPizza();
	}

	pizza->prepare();
	pizza->bake();
	pizza->cut();
	pizza->box();
	
	return pizza;

}

我们知道其中的if..elseif..elseif代码部分会改变,因此,我们阔以将创建pizza的代码移到一个专职创建pizza的对象中去。这个新对象就叫做“工厂”,一旦有了SimplePizzaFactory,orderPizza就变成了此对象的客户。

SimplePizzaFactory可以有多个客户,并且需要修改时,只需要修改这个类即可。

(利用静态方法定义一个简单的工厂,被称为静态工厂。它不能通过继承来改变创建方法的行为)

class SimplePizzaFactory
{
public:

	Pizza createPizza(String type)
	{
		Pizza* pizza = nullptr;

		if (type.equals("cheese"))
		{
			pizza = new CheesePizza();
		}
		else if (type.equals("greek"))
		{
			pizza = new GreekPizza();
		}
		else if (type.equals("pepperoni"))
		{
			pizza = new PepperoniPizza();
		}

		return pizza;
	}
class PizzaStore
{
private:
	SimplePizzaFactory* factory;

public:
	PizzaStore(SimplePizzaFactory* factory)
	{
		this->factory = factory;
	}

	Pizza orderPizza(String type)
	{
        // 使用工厂对象的创建方法替换new操作符
		Pizza* pizza = factory->createPizza();

		pizza->prepare();
		pizza->bake();
		pizza->cut();
		pizza->box();
		return pizza;
	}
}

简单工厂其实并不是一种设计模式,反而像一种编程习惯。

2.工厂方法

由于Pizza店生意火爆,需要连锁模式加盟,这个时候该怎么做呢?

利用SimplePizzaFactory写出三种不同的工厂,如NYPizzaFactory。

NYPizzaFactory* nyFactory = new NYPizzaFactory();
PizzaStore* nyStore = new PizzaStore(nyFactory);
nyStore->orderPizza("Veggie");

但你发现,加盟店虽然是用你的工厂创建Pizza,但是流程却不一样,他们不切片,或者使用其它厂商的盒子。因此,你希望支持他们的操作,把加盟店和创建Pizza捆绑在一起的同时,又保持一定的弹性(之前制作Pizza的代码绑定在PizzaStore里,大家都一样,没有弹性)。

(这里我没理解原文,开始说不切片、不用相同盒子,为了支持这个操作,除了把createPizza做成抽象方法,还应该把prepare, bake这个方法也封装成一个抽象方法才对)

因此,我们重新将createPizza方法放到PizzaStore,并将其设置为“抽象方法”,最后为每个区域创建一个PizzaStore的子类。

class PizzaStore()
{
public:
	Pizza* orderPizza(String type)
	{
		Pizza* pizza = createPizza();

		pizza->prepare();
		pizza->bake();
		pizza->cut();
		pizza->box();
		return pizza;
	}

	Pizza* createPizza(String type) = 0;
}

现在拥有PizzaStore作为超类,NYPizzaStore等只需继承它,自行决定如何制造Pizza。同时,PizzaStore已经有一个不错的订单系统,我们希望不同加盟商都用这个系统,因此,我们把orderPizza直接在超类中实现。子类负责createPizza方法(允许子类做决定)。

由于Pizza是抽象的,orderPizza()并不知道哪些具体类参与,这就是解耦。

(原本由一个对象负责所有具体类的实例化,现在通过对PizzaStore做一些小转变,变成由一群子类负责实例化)

工厂方法模式通过让子类决定该创建的对象是什么,来达到对象创建过程封装的目的。

 依赖倒置原则:要依赖抽象,不要依赖具体类(当你实例化一个类的时候,就是在依赖它的具体类)。这个原则说明了:不能让高层组件依赖底层组件,而且,不论高层或者底层组件,两者都应该依赖于抽象。

若你在orderPizza方法中写出下面这样的代码:

 

上面代码问题在于,它依赖每个Pizza类型,因为他在orderPizza里面,实例化了这个具体类型。虽然我们有了一个抽象Pizza,但我们在代码中创建了具体的Pizza,所有这个抽象没什么用。而使用工厂方法可以解决这个问题。

 

你可以注意到,底层组件竟然在依赖高层的抽象,这就是依赖倒置。

下面几个指导方针可以帮助你遵守此原则:

  • 变量不可以持有具体类的引用(如果用new,就会持有具体类的引用,可以使用工厂来避开这样做法)
  • 不要让类派生自具体类(如果派生自具体类,就会依赖具体类)
  • 不要覆盖基类中已实现的方法(如果覆盖基类已实现的方法,那么你的基类就不是一个真正适合被继承的抽象,基类中已实现的方法,应该由所有子类共享)

当然,要完全遵守这些方针也不太可能,但我们应该把这些方针内化成思考的一部分。那么在设计时,就会指导何时有足够的理由违反这样的原则。

3.抽象工厂模式

现在需要建造一家原料工厂,供给各家加盟店。因为各加盟店需求不一样,因此需要有不同的工厂。

class PizzaIngredientFactory
{
public:
	
	Dough createDough() = 0;
	Sauce createSauce() = 0;
	Cheese createCheese() = 0;
	Veggies[] createVeggies() = 0;
	Pepperoni createPepperoni() = 0;
	Clams createClam() = 0;
}

具体的工厂,如纽约工厂:

class NYPizzaIngredientFactory : public PizzaIngredientFactory
{
public:
	Dough* createDough()
	{
		return new ThinCrustDough();
	}

	Sauce* createSauce()
	{
		return new MarinaraSauce();
	}

	Cheese* createCheese()
	{
		return new ReggianoCheese();
	}

	Veggies*[] createVeggies()
	{
		Veggies* veggies[] = { new Garlic(), new Onion() };
		return veggies;
	}

	Pepperoni createPepperoni()
	{
		return new SlicedPepperoni();
	}

	Clams createClam()
	{
		return new FreshClams();
	}
}

再来改造Pizza类:

class Pizza
{
private:
	String* name;
	Dough* dough;
	Sauce* sauce;
	Veggies* veggies[];
	Cheese* cheese;
	Pepperoni* pepperoni;
	Clams* clam;

public:
	void prepare() = 0;

	void bake()
	{
		std::cout << "Bake for 25 mins at 350" << std::endl;
	}

	void cut()
	{
		std::cout << "Cutting pizza into slices" << std::endl;
	}

	void box()
	{
		std::cout << "Place pizza into box" << std::endl;
	}

	void setName(String name)
	{
		this->name = name;
	}
};
// 不同区域的pizza用的工厂和种类数不一样

class CheesePizza : public Pizza
{
private:
	PizzaIngredientFactory* ingredientFactory;

public:
	CheesePizza(PizzaIngredientFactory* ingredientFactory)
	{
		this->ingredientFactory = ingredientFactory;
	}
	
	void prepare()
	{
		dough = ingredientFactory.createDough();
		sauce = ingredientFactory.createSauce();
		cheese = ingredientFactory.createCheese();
	}
};
class NYPizzaStore : public PizzaStore
{
public:
	Pizza* createPizza(String type)
	{
		Pizza* pizza = nullptr;
		PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();

		if (type.equals("cheese"))
		{
			pizza = new CheesePizza(ingredientFactory);
		}
		else if (type.equals("veggie"))
		{
			pizza = new ClamPizza(ingredientFactory);
		}
		else if ()
		{

		}
		
		return pizza;
	}
}

抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

如果从上述工厂方法模式的定义可以指导,抽象工厂每个方法都是工厂方法。

(工厂方法使用继承,把对象的创建委托给子类,子类实现工厂方法来创建对象。抽象工厂使用对象组合:对象的创建被实现在工厂接口所暴露出来的方法中)

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

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

相关文章

MFC自定义控件使用

用VS2005新建一个MFC项目,添加一个Custom Control控件在窗体 我们需要为自定义控件添加一个类。项目,添加类,MFC类 设置类名字,基类为CWnd,你也可以选择CDialog作为基类 类创建完成后,在它的构造函数中注册一个新的自定义窗体,取名为"MyWindowClass" WNDCL…

深入了解HTTP代理在网络爬虫与SEO实践中的角色

随着互联网的不断发展&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;成为各大企业和网站重要的推广手段。然而&#xff0c;传统的SEO方法已经难以应对日益复杂和智能化的搜索引擎算法。在这样的背景下&#xff0c;HTTP代理爬虫作为一种重要的工具&#xff0c;正在逐渐被…

JUC中其他常用类

1.CopyOnWriteArrayList ArrayList是线程不安全的&#xff0c;Vector是线程安全的(方法被Synchronized修饰)&#xff0c;CopyOnWriterArrayList是在Vector的基础上再做优化&#xff0c;因为当读取操作较多时&#xff0c;Vector的效率不高。CopyOnWriterArrayList中读操作并没有…

ceph-mon运行原理分析

一、流程&#xff1a;ceph-deploy部署ceph-mon组建集群 1.ceph-deploy部署ceph-mon的工作流程及首次启动 1&#xff09;通过命令创建ceph-mon&#xff0c;命令为&#xff1a;ceph-deploy create mon keyring def mon(args):if args.subcommand create:mon_create(args)elif…

苍穹外卖day07——缓存菜品套餐+购物车功能实现

缓存菜品——需求设计与分析 问题说明 用户访问量过大带来的一个直接效果就是响应速度慢&#xff0c;使用体验下降。 实现思路 使用redis缓存菜品数据&#xff0c;减少数据库查询操作。 页面展示上基本就是同一个分类在同一页&#xff0c;所以key-value结构可以使用不同的分…

Vue没有node_modules怎么办

npm install 一下 然后再npm run serve 就可以运行了

记一次偶然的网站sql注入

自己学了点渗透的内容后就开始尝试挖漏洞了&#xff0c;偶然发现了这个yp网站&#xff0c;由于好奇心就浏览了一下里面的内容&#xff0c;突然注意到有个id的地方跳转页面&#xff0c;于是就想试试看有没有注入&#xff0c;就有了以下的内容。。。 界面如下 当时就是好奇点进去…

事件标志组

Q: 什么是事件标志组&#xff1f; A: 事件标志位&#xff1a;表明某个事件是否发生&#xff0c;联想&#xff1a;全局变量 flag。通常按位表示&#xff0c;每一个位表示一个事件&#xff08;高8位不算&#xff09; 事件标志组是一组事件标志位的集合&#xff0c; 可以简单的理…

ElasticSearch基础篇-Java API操作

ElasticSearch基础-Java API操作 演示代码 创建连接 POM依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:sch…

springCloud Eureka注册中心配置详解

1、创建一个springBoot项目 2、在springBoot项目中添加SpringCloud依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2021.0.3</version><type>…

从源程序到可执行文件的四个过程

从源程序到可执行文件的四个过程 预处理编译汇编链接 程序要运行起来&#xff0c;必须要经过四个步骤&#xff1a;预处理、编译、汇编和链接&#xff0c;如下图所示&#xff1a; -E选项&#xff1a;提示编译器执行完预处理就停下来&#xff0c;后边的编译、汇编、链接就先不执…

关于在VS2017中编译Qt项目遇到的问题

关于在VS2017中编译Qt项目遇到的问题 【QT】VS打开QT项目运行不成功 error MSB6006 “cmd.exe”已退出,代码为 2。如何在VS2017里部署的Qt Designer上编辑槽函数 【QT】VS打开QT项目运行不成功 error MSB6006 “cmd.exe”已退出,代码为 2。 链接 如何在VS2017里部署的Qt Design…

flask实现一个登录界面

flask实现一个登录界面 基础的Flask项目结构 forms.py&#xff1a;定义登录表单和表单字段的文件。templates/login.html&#xff1a;用于渲染登录表单的 HTML 模板文件。routes.py&#xff1a;定义应用的路由和视图函数的文件。__init__.py&#xff1a;创建并初始化 Flask 应…

解压缩软件WinRAR-bandizip-7z--洛

个人收集的解压软件&#xff01;后期还会更新 ------------------------------------------------------------------- WinRAR&#xff1a;密码1234WinRARhttps://wwzb.lanzoue.com/b0485ldcj BandiZip&#xff1a;密码1234 Bandizip-Professionalhttps://wwzb.lanzoue.com/…

SpringBoot内嵌的Tomcat:

SpringBoot内嵌Tomcat源码&#xff1a; 1、调用启动类SpringbootdemoApplication中的SpringApplication.run()方法。 SpringBootApplication public class SpringbootdemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootdemoApplicat…

windows下载安装FFmpeg

FFmpeg是一款强大的音视频处理软件&#xff0c;下面介绍如何在windows下下载安装FFmpeg 下载 进入官网: https://ffmpeg.org/download.html, 选择Windows, 然后选择"Windows builds from gyan.dev" 在弹出的界面中找到release builds, 然后选择一个版本&#xff0…

如何在MacBook上彻底删除mysql

好久以前安装过&#xff0c;但是现在配置mysql一直出错&#xff0c;索性全部删掉重新配置。 一、停止MySQL服务 首先&#xff0c;请确保 MySQL 服务器已经停止运行&#xff0c;以免影响后续的删除操作。 sudo /usr/local/mysql/support-files/mysql.server stop如果你输入之…

【RTT驱动框架分析03】- sfus flash 操作库的分析和基于STM32F103RCT6+CUBEMX的SFUS移植教程

sfus flash 操作库的分析 sfus 抽象 /*** serial flash device*/ typedef struct {char *name; /**< serial flash name */size_t index; /**< index of flash device information table see flash_…

IntelliJ IDEA流行的构建工具——Gradle

IntelliJ IDEA&#xff0c;是java编程语言开发的集成环境。IntelliJ在业界被公认为最好的java开发工具&#xff0c;尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能可以说是超常的。 如…

Hive之窗口函数lag()/lead()

一、函数介绍 lag()与lead函数是跟偏移量相关的两个分析函数 通过这两个函数可以在一次查询中取出同一字段的前N行的数据(lag)和后N行的数据(lead)作为独立的列,从而更方便地进行进行数据过滤&#xff0c;该操作可代替表的自联接&#xff0c;且效率更高 lag()/lead() lag(c…