Java示例初学者的设计模式
在这篇文章中,你将学习:
- 什么是设计模式
- 为什么我们使用设计模式
- 不同类型的设计模式
- 当我们使用设计模式
- 你如何在Java中实现不同的设计模式
- 设计模式的真实示例。
什么是设计模式?
从第一个面向对象的语言Smalltalk开始,我们已经开发面向对象的软件已有40多年了。
编程界遇到了许多问题,并提出了各种解决方案来解决这些问题。
由四个人组成的小组试图在给定的背景下提出一系列共同的问题和解决方案,这些人被称为“四人帮”。
为什么要设计模式?
设计模式的优点是:
- 提供每个人都能理解的标准术语
- 不要一遍又一遍地重复同样的错误
设计模式有哪些类型?
我们在这里谈论的设计模式是从面向对象的世界的角度来看的。主要有三种不同的设计模式:
- 创作模式
- 结构模式
- 行为模式
创造力的
创建模式处理对象的创建。
结构性
结构模式处理对象的组成。
它处理以下问题:
- 一个类包含什么?
- 一个班级与其他班级的关系是什么?是继承还是组成?
行为的
行为模式更多地关注对象的行为,或更准确地说,关注对象之间的交互。
一个对象如何与另一个对象通信?
探索创作设计模式
我们探索以下创新设计模式:
原型模式
原型表示要复制或克隆的完全初始化的实例。
让我们举个例子:
让我们考虑一下国际象棋游戏的设计。每盘国际象棋都有相同的初始设置-国王,王后,新人,主教,骑士和棋子都有其特定的位置。假设我们要构建用于对国际象棋游戏进行建模的软件。
每次玩新的国际象棋游戏时,我们都需要创建初始的棋盘布局。
不必每次都重复制作棋盘,
- 我们可以创建一个包含初始设置的对象
- 克隆它-每次玩新的国际象棋游戏。
初始设置棋盘的对象是原型。并且,我们正在使用原型模式。
是不是很简单?
在原型模式中,你具有完全初始化的实例。在这里,初始电路板布局很容易获得。
例如,无论何时在众多在线国际象棋门户中的任何一个中开始新的国际象棋游戏时,都仅复制或克隆此初始化实例。
建造者模式
构建器模式将对象构造与其表示形式分开。这意味着什么?
假设你要去一家餐厅享用多道菜的晚餐。这样的晚餐会有很多选择,例如开胃菜,主菜和甜点。你可能会从显示的选项中选择两个或三个。特定的客户可能只想与前两个选项一起吃晚餐,而忽略了甜品选项。然而,另一个人则更喜欢主菜和甜点,完全跳过了开胃菜。
在设计软件时可能会出现类似情况。你可能需要使用可用选项的子集来构建对象。或者,你可能需要以多种方式创建对象。这是构建器模式派上用场的地方。
为了进一步理解它,让我们看一小段代码。
假设你正在为准备咖啡的机器编写软件。咖啡的主要成分是咖啡,牛奶和糖。
根据你来自世界的哪个地方,你选择是否有糖和牛奶。
构建器模式介入以为你提供这些咖啡选项。
看一下里面的代码main()
。
我们里面Coffee
有一个Builder
,我们将咖啡的强制性type
项传递给。与该呼叫相关联,我们进行其他呼叫以增加对其他成分的偏好。
想要其他咖啡的其他人可以轻松地制作它。这导致构建对象时具有极大的灵活性。
解决此问题的其他方法,例如使用setter,存在许多固有的问题。这些解决方案导致代码难以阅读,并且在多线程程序中的行为也异常。构建器模式解决了所有这些问题。
使用构建器模式的优点是:
- 它简化了对象的创建
- 导致代码更具可读性
- 不允许修改值
单例模式
单例模式是所有设计模式中最著名的。从其名称可以很清楚地看出该模式的作用-在任何时间点,每个JVM仅允许一个类的实例。
一个好的现实世界比较可能是一个国家的总统。
但是,这里有一个免责声明- 每个JVM只能有一个该类的实例。如果你有一个Java应用程序作为应用程序服务器集群的一部分运行,则每个服务器都运行一个单独的JVM实例。因此,可以在任何给定时间在每个应用程序服务器上创建一个单例实例。
每当创建单例类时,都需要记住一些事情。
- 构造函数必须为
private
,以防止其他对象创建类实例的可能性。 - 在Java中,使用来构建Singleton
Enum
。 - JEE 7具有一个名为的内置注释
@Singleton
以及其他相关注释。 - 使用单例模式的主要缺点是生成的代码难以进行单元测试。对于绝对需要使用单例的地方,以及不需要的地方,做出明确的决定。
- 在诸如Spring之类的框架中,被管理的对象称为bean,并且默认情况下bean是单例。Spring做得很好的是确保所有这些都在后台。
工厂方法模式
工厂方法模式的目的是创建一系列对象类型。让我们看一个代码示例。
此代码实现PersonFactory
。此类具有一个名为的静态方法getPerson()
,该方法接受一个人的姓名和性别作为参数。根据String
传入的性别,它会返回一个Male
或一个Female
对象。
如果有人想创建一个男性,他们会使用性别参数调用上的getPerson()
方法。同样,你可以通过调用性别参数为的方法来创建女性。PersonFactory"M"getPerson()PersonFactory"F"
在创建时,我们传入的是我们所需对象类型的标识符,同时仍引用泛型类型Person
。
在Male
和Female
类背后隐藏PersonFactory
的实现。
使用抽象方法模式的优点是,你可以向工厂添加其他类型,而无需使用该类在其他类中进行太多更改。在我们的示例中,你可以添加更多类型的性别,而不会影响现有的处理其他性别的代码Person
。
创建对象涉及的复杂性如何?
它极大地简化了对象创建的任务。该PersonFactory
做什么要创建的对象的决定,并将其传递给我们。
结构设计模式
现在让我们看一下我们要探索的结构设计模式。
代理模式
代理是代表另一个对象的对象。
让我们看一个真实的例子。
你的借记卡是你银行帐户的代理。每当你使用借记卡进行交易时,都会从银行帐户中扣除相应的金额。
借记卡是你的银行帐户(实际对象)的代理。
与此类似,在编程中,你可能必须对与远程对象的交互进行编程。在这种情况下,你将创建一个负责所有外部通信的代理对象。你将与代理进行通信,就像它驻留在本地计算机上一样。
EJB Home和Remote接口就是一个很好的例子。
代理隐藏了与真实对象通信所涉及的复杂性。
装饰图案
装饰器模式使我们可以动态地向对象添加职责。
在面向对象的编程中,我们通常使用很多继承。
例子1
假设某个特定的披萨店有10种披萨。对于这些类型的披萨,我们的实现有10个类。
现在,需要使这些披萨具有三种类型的浇头。如果我们要为每个披萨和浇头组合创建单独的类,则总共要管理30个类。
除了这样做,我们还可以使动态的披萨关系成为动态吗?我们可以在现有的比萨饼上添加浇头吗?
我们需要在任何披萨上使用浇头作为装饰。
例子2
另一个示例是在比萨饼订单上添加折扣。
假设你有一个订单,并且基于某些条件,你想为客户提供折扣。在不同时间可能会有各种折扣。如果你为每种类型的订单添加不同类型的折扣,那么在静态关系中,你需要维护数百个类。
将折扣视为订单上的装饰者会使关系动态化。
例子3
Java I / O包是在Java中实现装饰器模式的一个很好的例子。这反映在我们在I / O程序中创建输入流的方式中:
你有一个FileInputStream
。如果要使其缓冲,则以形式添加一个装饰器BufferedInputStream
。如果您希望缓冲区FileInputStream
另外具有行号,请为添加一个装饰器LineNumberInputStream
。
摘要
装饰器模式使您可以在运行时向现有对象添加行为。这使界面的用户可以决定他们想要如何创建对象。
这种方法的缺点是创建对象所涉及的复杂性。用户需要先了解许多类及其关系,然后才能使用装饰器的功能。
外墙图案
外观是代表整个子系统的单个类。
让我们以事件管理器为例。当你想要组织活动时,活动经理是必不可少的。他们处理活动的多个方面,例如装饰,食物,向客人发出邀请,音乐安排和其他类似的事情。事件管理器充当事件组织子系统的基础。
考虑分布式系统的情况。通常,你需要跨层进行多个调用。
以一个提供在线图书订购服务的系统为例。每当有订单进入时,都需要注意几件事,例如检查库存,保留订单,接受付款,更新库存以及生成发票。
我们可以创建一个单一的外观,例如订单界面,它将管理所有传入的订单并为客户提供界面。
使用外观模式的优点是它减少了网络调用的数量,并减少了类之间的耦合。
它成功地建立了通信对象之间的事务边界。外观与服务一样,都是实现交易的良好枢纽。
只要正面的界面保持不变,子系统的实现细节就可以更改。
适配器图案
适配器用于匹配不同类的接口。
让我们以电源适配器的实际示例为例。
问题:如果你在印度购买手机,则手机附带的充电器只能与印度使用的电源插座配合使用。例如,如果你将同一款充电器带到美国,它将无法正常工作,因为它无法装入那里的插座中。
解决方案:解决方案是使用旅行适配器,旅行时可以将其与充电器配合使用。你可以将充电器插入旅行适配器,旅行适配器用于连接特定国家/地区的插座。
同样,当你尝试与使用其他消息格式或语言的系统通话时,你需要一个适配器来翻译消息。
一个有趣的例子是Java程序和Web服务之间的通信。在将数据发送到服务之前,我们需要将对象转换为XML或JSON格式。我们正在实现适配器模式!
轻量级模式
让我们考虑一些情况
- 创建对象需要花费很多时间,并且涉及多个实例
- 对象的每个实例都占用大量内存
- 某些对象可能在同一应用程序中多次使用相同的值
在这些情况下,你可能不想每次都需要创建一个新实例。
如何缓存实例并在需要时重新使用它?
flyweight表示创建用于高效共享的细粒度实例。
例子1
真实的一个很好的例子是公用电话交换网(PSTN)。
在PSTN中,线路总是有限的,为简单起见,我们假设该数目为10。但是,有成千上万的客户在使用这些线路。由于所有1000个客户都不会在同一时间拨打电话,因此可以在现有的10条线路中有效地切换来电。
例子2
在软件世界中,Flyweight模式的一个很好的例子是JDBC连接。
连接池是到数据库的一组连接。该应用程序可能会触发很多查询,但是一旦有新查询出现,我们就不会创建新的连接。一旦有查询出现,我们就会将其与可用的连接进行匹配,并且会触发查询。执行完查询后,连接将释放回池中。
使用这样的工具可以使我们避免创建和关闭连接所涉及的成本。
行为设计模式
现在让我们看一下行为设计模式。
责任链模式
责任链模式表示一种在对象链之间传递请求的方式。
例子1
在大多数编程语言的异常处理机制中都可以看到这种模式的最佳示例。
假设你有一个method1()
呼叫method2()
,然后有method2()
呼叫method3()
。假设method3()
抛出一个异常。
如果method3()
没有异常处理,则将异常传递method2()
给它进行处理。如果method2()
内部没有异常处理,则将异常传递给method1()
。如果甚至method1()
无法处理它,它也会被丢弃method1()
。
例子2
考虑一下贷款审批流程的真实示例。
银行职员有权批准一定数量的贷款。如果金额超过该金额,则交给主管。主管也有类似的规定,尽管为其设置了更大的贷款批准限额。如果贷款额超过该限额,则交给其主管,依此类推。
摘要
有了责任链模式,我们就有了一系列准备就绪并等待处理请求的对象。当新请求进入系统时,它将转到链中的第一个对象以尝试处理。根据处理条件的不同,请求会沿着链条向上移动,并在某种程度上得到完全处理,或者可能根本不被处理。
迭代器模式
迭代器模式是最简单的设计模式之一。你在集合中安排了一组元素,并且想要顺序访问这些元素。电视遥控器是Iterator的一个很好的例子,它具有“下一个”和“上一个”按钮来浏览电视频道。按下“下一个”按钮会向前移动一个频道,而按下“上一个”按钮会向后移动一个频道。
在编程工作中,Java中的Iterator
类示例和增强的for
循环就是Iterator模式的示例。
状态模式
状态模式用于在对象状态更改时更改其行为。
看一下这个Java示例:
让我们以风扇壁控制为例。风扇壁控制器通过风扇旋转来控制速度。它的速度级别从0到5。处于级别0时,风扇不旋转,并且以级别5最快地旋转。
旋转风扇控件的旋钮时,电平会发生变化,这也会导致风扇速度也发生变化。这是状态(级别)变化导致行为(速度)变化的经典案例。
一个FanwallControl
对象由一个SpeedLevel
对象组成。SpeedLevel
是具有四个不同实现的接口。最初,水平为Off
,然后单击“旋转”时,新速度为SpeedLevel1
。该事件会连续发生,如果你在旋转SpeedLevel3
,则级别会返回Off
。
如果你需要定义其他速度级别,只需添加一个新的类来实现该SpeedLevel
接口并实现其rotation方法。
这是一个很好的例子,突出了可扩展类的优点。
策略模式
该策略的任务是将算法封装在类中。让我们看一个Java代码示例:
我们创建了一个实例,SachinCenturyNotifier
并为其注册了三个粉丝。
每当Sachin踏入一个世纪时,notifier.sachinScoredACentury()
就会打来电话,并通知所有三名粉丝。
访客模式
访问者模式允许我们在不更改类的情况下向类添加新操作。
设计框架时有很多方案,我们不希望其他人修改框架中的代码。我们希望其他人在不接触框架代码的情况下扩展功能。允许他们添加新操作,但不能更改现有操作。
访问者模式允许你执行此操作。
出租车模式的一个很好的现实例子是出租车公司的运营。
一旦有人致电出租车公司并派出出租车,公司便会接待游客。一旦访客或顾客上了出租车,他就不再控制行进路线。出租车司机现在处于控制之中。
如果将其视为面向对象的代码,则驱动程序类将控制客户类。驱动程序类可以在客户/访客之上添加新的操作。
模板方法模式
模板方法模式用于将算法的确切步骤推迟到子类。
这种模式的一个很好的现实例子是我们如何制定房屋计划。任何好的房屋平面图都包括平面图,地基,管道,框架和布线。这样的计划对于每个房子几乎都是相同的。
如果要在软件中对此建模,则可以创建一个定义了此标准行为的模板类。子类可以扩展它并给出实际的实现。这些细节可能包括木地板类型,墙壁油漆颜色以及根据需要添加的任何侧翼。
模板方法模式的一个很好的例子是在Spring框架中,其形式为AbstractController:
handleRequest()
只照顾基本的事情。但是,它为实现该方法留下了很大的份额handleRequestInternal()
。此方法由子类定义,可以在其中实现更具体的逻辑。
模板方法模式是关于执行高级步骤并将低级细节留给子类的。子类可以覆盖低级步骤,并提供自己的实现。
命令模式
命令模式将命令请求封装为对象。
让我们举一个真实的例子。
考虑当客户去餐厅并想下餐的情况。作者只是在一张纸上写下命令,然后将其传递给厨师。厨师执行命令,然后准备餐点。他把纸递给经理。
来自客户的口头订单现在已成为纸上的物品。这张纸是命令对象。命令对象包含执行请求所需的所有详细信息。
类似地,在面向对象的编程中,我们可以将请求的所有细节封装到一个对象中,并传递该对象以执行它。
在Web应用程序中,当用户在表单上键入详细信息时,这些详细信息将捕获到单个请求对象中,然后将其传递。
该接口java.lang.Runnable
也是如何实现此模式的一个很好的例子。我们通过扩展Runnable
接口在Java中创建线程,该接口具有在其start()
方法中执行的所有逻辑。当我们想要创建并启动线程时,我们将此类传递给start()
方法。
纪念法
记忆模式捕获并随后恢复对象的内部状态。
我们玩的许多游戏都提供执行中间保存的选项。在游戏中的某个时刻,你可以保存它,然后再返回到它。
为了实现这一点,我们需要保存游戏对象的内部状态,并在特定的时间点还原它们。
可以通过使用Java等语言的序列化来实现此保存还原功能。
记忆模式在实现撤消/重做操作中非常有用。
例如,如果你正在文字处理器中处理文本文档。如果你决定在某个时间撤消更改,则可以看到每次撤消,直到你满意为止。现在,你已恢复到文档的较早保存状态。
中介者模式
中介器模式用于定义类之间的简化通信。
以空中交通管制员(ATC)为例。假设在印度的任何时间点,我们都有大约500架次飞行。我们需要确定这些航班中的每条需要采取的路线。这还包括确定每个航班起飞和降落的时间。如果这500个航班中的每个航班都需要互相交谈并得出可接受的航线时间表,那将是非常复杂的情况。
这就是为什么我们有ATC的概念。航班与ATC通信,并且在吸收了所有航班的信息后,ATC做出决策并将其传达回航班。
在软件世界中,ESB(企业服务总线)是Mediator模式的一个很好的例子。在分布式系统中,应用程序不会让应用程序相互通信,而是向ESB发送消息。ESB将请求路由到需要处理该请求的应用程序。它充当调解人。
摘要
在本文中,我们快速浏览了各种设计模式。
设计模式是一种在给定上下文中解决问题的方法。我们专注于理解特定模式可能适用于实际示例的上下文。
有什么问题可以加下qq:2062583349。也可添加vx:admindesire,有java、python、web等习资料和视频课程干货”。欢迎交流!