设计模式
目录
1.1 设计模式的发展
模式的诞生
模式(Pattern)起源于建筑业而非软件业。
模式是从大量的建筑和规划实践中精心提炼出来的经验。
模式的三要素:
1.Context(模式可适用的前提条件)
2.Theme 或 Problem (在特定条件下要解决的目标问题)
3.Solution (对目标问题求解过程中过程中各种物理关系的记述)
模式之父——美国加州大学伯克利分校的终身教授Christopher Alexander博士。
简单来说,模式就是在特定环境下,人们解决某类重复出现问题的一套成功或者有效的解决方案。
模式的作用:
模式是一种参照性指导方略。在一个良好的指导下,有助于按照既定思路快速做出一个优良的设计方案,得到解决问题的最佳方法,高效完成任务,达到事半功倍的效果。
当一个领域成熟时会出现很多模式,不同的领域有相应的模式,例如建筑领域有建筑模式,软件设计领域有设计模式。
四人帮提出的设计模式主是基于七个面向对象设计原则。在1994年发表了23中在软件开发中使用频率较高的设计模式,旨在用模式来统一沟通面向对象方法在分析、设计和实现间的鸿沟。
总而言之,设计模式就是指导你如何写出可维护、可复用、可扩展及灵活的代码。
1.2 设计模式的概念
软件模式
软件模式:一定条件下软件开的发问题及其解法。
软件模式包括设计模式,还包括架构模式,例如MVC模式、B/S模式和C/S模式等,实际上,在软件生存周期的每一个阶段都存在者一些被认同的模式。
软件模式由4个部分构成:
1.问题描述
2.前提条件(环境或约束条件)
3.解法
4.效果
软件模式与具体的应用领域无关,在模式发现过程中需要遵循大三定律(Rule of Three),即只有经过三个以上不同类型(或不同领域)的系统校验,一个解决方案才能从候选模式升格为模式。
GRASP设计原则
GRASP,全称为General Responsibility Assignment Software Pattern,即通用职责分配软件模式。另外GRASP称之为设计原则而不是设计模式。GoF模式针对特定问题而提出的解决方案。GRASP站在一个更高的角度来看待面向对象软件的设计,它是GoF设计模式的基础。
GRASP描述了对象设计和职责分配的基本原则,其核心思想是职责分配(Responsibility Assignment),用职责设计对象(Designing Objects with Responsibility)。
它包含了九个基本原则:
①创建者Creator;
②信息专家Information Expert;
③低耦合Low Coupling;
④控制器Contorller;
⑤高内聚High Cohesions;
⑥多态性Polymorphism;
⑦纯虚构Pure Fabrication;
⑧间接性Indirection;
⑨防止变异Protected Variations;
GoF设计模式
设计模式(Design Pattern)含义:
①一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结;
②是一种用于对软件系统中不断重现的设计问题的解决方案进行文档化的技术;
③是一种共享专家设计经验的技术;
设计模式的四个关键要素:
1.模式名称 Pattern Name
2.问题 Problem
3.解决方案 Solution
4.效果 Consequences
根据目的(模式是用来做什么的)可分为三类:
①创建型模式(Creational):创建对象
②结构型模式(Structural):处理类或对象的组合。
③行为型模式(Behavioral):描述类或对象如何交互和怎样分配职责
根据模式主要是处理类之间的关系还是处理对象之间的关系(范围),分为两种:
①类模式处理类和子类之间的关系,这些关系通过继承建立,在编译时刻就被确定下来,是一种静态关系;
②对象模式处理对象间的关系,这些关系在运行时变化,更具动态性。
设计模式的优点:
①融合了众多专家的经验,并以一种标准的形式供广大开发人员所用;
②提供了一套通用的设计词汇和一种通用的语言,方便开发人员之间进行沟通和交流,使得设计方案跟家通俗易懂,让人们可以更加简单方便地复用成功的设计和体系结构;
③使得设计方案更加灵活,且易于修改,提高软件系统的开发效率和软件质量,且在一定程度上节约设计成本;
④有助于初学者更深入地理解面向对象思想,方便阅读和学习现有类库与其它系统中的源代码,还可以提高软件的设计水平和代码质量。
设计模式的十要素:
1.模式名称(pattern name)
设计模式的名称简洁的描述了设计模式的问题、解决方案和效果。
2.问题(problem)
问题描述了应该在何时使用模式。它解释了实际问题和问题存在的前因后果。
3.环境或初始环境(context或initial context)
环境说明模式的使用范围,也是模式应用之前的起始条件。(也叫前提条件)
4.解决方案(solution)
解决方案描述了设计的组成部分,以及他们之间的相互关系及各自的职责和协作方式。模式就是一个模板,可应用于多种不同场合,因此解决方案并不描述一个特定而具体的设计或实现。
5.效果(consequences)
效果描述了模式应用和使用模式应权衡的问题。效果用来描述设计模式的利弊,它往往是衡量模式是否可用的重要因素。
6.举例(举例)
通常举例使用一个或多个示意性的应用来说明特定的真实环境,并说明模式如何应用到环境中、改变环境并且给出当模式结束时的末态环境。
7.末态环境(resulting context)
末态环境是模式应用到系统之后的状态。末态环境是模式的末态条件和可能有的副作用。它包括模式带来的好结果和坏结果。
8.推理(rationale)
推理解释模式的步骤、规则,以及此模式作为一个整体是如何以特定的方式解决模式的。推理让使用者知道模式是如何工作的,为什么可以工作,以及使用此模式的优点是什么。
9.其他有关模式(related pattern)
与他有关模式描述在现有的系统中某种模式与其他模式的静态和动态的关系。
10.已知应用(known uses)
已知应用是指在已有的系统中出现或应用的模式例子,它有助于证明此模式确实是对一个重复发生的问题的可行性解答。
2.1 UML简介
建模
模式是一种高效的沟通手段,特别是在那些不需要详细信息的场合,可提高开发速度和质量等。在软件开发过程中,建模是一项非常重要的活动,与其他工程规范一样,在真正实现系统之前,软件工程师需要开发不同抽象层次上的模型。
简单来说,模型就是对现实的抽象,建模就是去粗取精,对现实系统进行抽象的过程。
建模的优点:
①建模可以帮助理解用户需求;
②建模可以帮助进行系统的分析和设计;
③建模可以帮助团队交流和项目的协同开发;
④建模可以帮助提高开发速度和质量
但是建模还需要一系列统一规范的建模符号、一套标准通用的加农规则、一些灵活高效的建模工具以及抽象思维。
软件分析建模体现了软件设计的思想,在系统需求和系统实现之间架起了一座桥梁。软件工程师按照设计人员建立模型,开发处符合设计目标的软件系统,而且软件的维护、改进也基于软甲你分析的模型。
面向对象思维就是一切皆可Object 运用思维能力来使用工具的能力。
软件系统的三个模型:功能模型、业务模型和数据模型。
1).功能模型FM(Function Model)
描述系统能做什么,即对系统的功能、性能、接口和界面进行定义。用UML的用例图描述。
2).业务模型OM(Operation Model)
描述系统再何时、何地、由何角色、按什么业务规则去做,以及做的步骤或流程,即对系统的操作流程进行定义。在UML中,业务模型由时序图、交互图、状态图、活动图来表述。
3).数据模型DM(Data Model)
描述系统工程前的数据来自何处,工作中的数据暂存什么地方,工作后的数据放到何处,以及这些数据之间的关联,即对系统的数据结构进行定义。在UML中用类图来描述。
UML
UML是OMG发布的建模标准。对象管理组织(Object Management Group,OMG)是一个国际性的非盈利协会。
统一建模语言(Unified Modeling Language,UML)是一种为面向对象系统进行说明、可视化和编制文档的一种标准语言。
UML是针对面向对象设计的建模工具,独立于任何具体程序设计语言。
介绍UML的特点:
1).UML统一了各种方法对不同类型的系统、不同开发阶段以及不同内部概念的不同观点,从而有效消除了各种建模语言之间的不必要的差异。它实际上是一种通用的建模语言,能够被面向对象建模方法的用户广泛使用。
2).UML建模功能比其他面向对象建模方法更强。它不仅适合一般系统的开发,而且对并行、分布式系统的建模有位适宜。
3).UML是一种建模语言,而不是一个开发过程。
UML系统的三个主要模型:
1).功能模型:从用户的角度展示系统的功能,包括用例图。
2).对象模型:采用对象、属性、操作、关联等概念展示系统的结构和基础,包括类图、对象图、包图。
3).动态模型:展示系统的内部行为。包括序列图、活动图、状态图。
类图
描述系统中类(接口),以及类(接口)之间协作关系的静态模型图。能够让程序员在编写代码之前对系统有一个全面的认识。
UML类图基本元素符号:
类Class:
使用长方形描述类的主要构成,将长方形垂直地分为三层。
第一层是名字层,类名字是常规字形表明该类是具体类,类名字是斜体字表明该类是抽象类。
第二层是变量层,也称属性层,列出类的成员变量级类型。
第三层是方法层,也称操作层,列出类的方法及返回类型。
接口Interface:
使用长方形描述类的主要构成,将长方形垂直地分为三层。
第一层是名字层,接口的名字是斜体字,而且需要用修饰名字,并且该修饰该修饰和名字分列在2行。
第二层是常量层,列出接口中的常量及类型。
第三层是方法层,也称操作层,列出接口中的方法及返回类型。
类之间的关系有:依赖、泛化(继承)、实现、关联、聚合与组合
2.2 类之间六种关系
类之间的关系:继承、实现、依赖、关联、聚合、组合
继承关系
继承(泛化):指一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口),子类增加新功能。
继承是类与类或接口与接口之间最常见的关系,继承关系也称为“is-a”关系。
UML图中,继承关系用带空心三角形的实线来表示,空心三角形指向父类,实线开始端指向子类。
实现关系
实现关系:类B实现接口A(可以是多个),那么类B和接口A的关系就是实现关系。
在UML图中,用带空心三角形的虚线连接类和它实现的接口,虚线起始端是类,空心三角形指向接口。
依赖关系
依赖关系:对于类A和类B,如果出现下面情况,称为类A依赖类B:
(1)类A中某个方法的形参是类B类型;
(2)类A中某个方法的返回类型是类B类型;
(3)类A中某个方法中局部变量是类B类型。
依赖关系是一种使用关系,特定事物的改变有可能会影响该事物的其他事物,在需要表示一个事物使用另一个事物时使用依赖关系。
在UML图中,用带用箭头的虚线表示依赖关系,虚线起始端为依赖类A,箭头指向的类B是被依赖类。
关联关系
关联关系:如果B类中某个成员变量的类型是A类(接口),那么A和B是关联关系,称B关联与A。
关联关系是对象之间的拥有关系,即“has a”关系。例如工人拥有一辆汽车,学生拥有很多课本等。
UML图中,利用带有箭头的实线表示关联关系。实线的起始端是关联类B,箭头指向被关联类A。
聚合关系
聚合关系:如果B类中某个成员变量的类型是类(接口)A,表示类A与类B之间是整体与部分的关系,那么A和B是聚合关系。
在聚合关系中,成员对象是整体对象的一部分,但是成员对象可以脱离整体对象独立存在。
通常在定义一个整体类后,那么球队就是整体类,学生类就是成员类;一台计算机由CPU、硬盘、内存等构成,那么计算机就是整体类,各个部件就是成员类。
UML图中,聚合关系用带空心菱形的有箭头的实线表示。空心菱形指向整体类,箭头指向成员类。
组合关系
组合关系:类之间整体和部分的关系,但是组合关系中部分和整体具有统一的生存期。一旦整体对象不存在,部分对象也不存在,部分对象与整体对象之间具有同生共死的关系。
UML图中,组合关系用带实心菱形的有箭头的实线表示。
类图建模步骤
(1)按照面向对象开发原则,分析系统的需求,找出系统中的各个对象个体;
(2)从对象个体中抽象出描述对象的类,为每个类确定属性和方法;
(3)分析各个类之间的关系,看能否进一步抽象,得到抽象类或接口;
(4)画出每个类的图形和类之间关系的图,并检查、修改、重组、优化、美化UML类图。
3.1 设计原则简介
软件设计的七宗罪
- 僵化性(Rigidity)
若单一改动会导致有依赖关系的模块的**连锁改动,**那么设计就是僵化的,需要连锁改动的模块较多,设计越僵化。 - 脆弱性(Fragility)
脆弱性指改动程序时,程序中许多与改动位置并没有概念上关联的其它地方也可能出现问题,即设计易于遭受破坏。 - 牢固性(Immobility)
设计中包含了对其它系统有用的部分,但要想把这些部分分离出来所需要的努力和风险是巨大的,即设计难以复用。
比如,一段代码、函数、模块的功能可以在新模块或者新系统中使用,但是发现现存的代码、函数、模块依赖于一大推其他的东西,以至于很难将它们抽取出来移动到其它地方使用。
- 粘滞性(Viscosity)
某种情况下,一个改动可以保持原有设计意图和设计框架下进行,也可以在破坏原始意图和框架下进行。
第一种办法对系统的未来有利,第二种办法是权宜之计,可以解决短期问题,但会牺牲中长期利益。若第二种办法比第一种办法更简单,程序员可能牺牲长期利益,采取权宜之计,在一个通用的逻辑中建立一种特例,以便解决眼前的问题。
一个系统设计,**若总是使得第二种办法比第一种办法来得容易,说明粘滞性过高。**一个粘滞性过高的系统会诱使维护它的程序员采取错误的维护方案。 - 不必要的重复(Needless Repetition)
程序设计中,若开发人员忽略抽象,滥用“剪切”和“粘贴”等操作,阿静造成大量的不必要的重复代码。这种情况不仅仅增加理解系统的难度,而且使改动系统变得困难,不利于系统维护。
- 不必要的复杂性(Needless Complexity)
若软件设计中包含了当前没有用的成分,即过度设计,将增加软件的复杂性。
比如,过度追求逻辑复杂和先进技术,导致了技术框架虽看似华丽却复杂难用。此外,若在设计产品功能或界面交互时,过度追求体验完美和满足需求却导致实际体验下降、很多功能没人用。所以,软件设计应“有所为有所不为”。 - 晦涩性(Opacity)
其他人以及软件设计人员自己难以理解所设计的软件,并且代码随时间而不断演化,往往会变得越来越晦涩、可读性差。
一个好的设计
可维护性(Maintainability):指软件能够被容易理解、改正、适应及扩展。
可复用性(Reuability):指软件能够被重复使用的难易程度。
面向对象设计的一个重要目标在于支持可维护性和可复用性,一方面需要实现设计方案或者源代码的复用,另一方面要确保系统能够易于扩展和修改,具有良好的可维护性。
面向对象设计原则作用
1.面向对象设计原则为支持软件可维护性和可复用性而诞生。
2.面向对象设计原则是用于评价一个设计模式使用模型的重要指标之一。
七个设计原则时指导性原则,非强制性原则。每个设计模式都符合一个或多个面向对象设计原则。
七个设计原则
单一职责原则(SRP)
一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。
开闭原则(OCP)
软件实体应当对扩展开放,对修改关闭。
里氏代换原则(LSP)
所有引用基类的地方必须能透明地使用其子类的对象。
依赖倒置原则(DIP)
高层模块不应该依赖底层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
接口隔离原则(ISP)
客户端不应该依赖那些它不需要的接口。
合成复用原则(CRP)
优先使用对象组合,而不是继承来达到复用的目的。
迪米特法则(LoD)
一个软件实体应当尽可能少的与其他实体发生相互作用,而且局限于那些与本实体密切相关的软件实体。简单来说使“不要和陌生人说话”。
3.2 单一职责原则
单一职责原则的概念(Single Responsibility Principle,SRP)
一个类应该仅有一个引起它变化的原因。即,一个对象只包含一个职责,并且这个职责被完整的封装在一个类中。
所谓职责是指类变化的原因。若一个类有多于一个的动机被改变,那么这个类就具有多余一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。
这是简单、最容易理解却最不容易做到的一个设计原则,用于控制类的颗粒大小。
单一职责原则的作用
- 类的复杂性降低。实现的职责都有清晰明确的定义
- 可读性提高
- 可维护性提高
- 变更引起的风险降低。软件系统变更是必不可少的,若单一职责做得好,一个类(接口、方法)修改只对相应的实现类有影响,对其他的类无影响,这对系统的扩展性、维护性都有非常大的帮助。
案例:
StudentManager类
学生类管理类StudentManager包含如下功能,包括连接数据库方法getConnection()、操作数据库方法如search和delete、显示学生信息的方法display。
public class StudentManager {
public void getConnection(String url){ //连接
System.out.println("与"+url+"建立连接。");
} //连接
public void searchStudent(String condition){ //数据操作
System.out.println("根据"+condition+"查询学生。");
}
public void deleteStudnet(String condition){ //数据操作
System.out.println("根据"+condition+"“删除学生”");
}
public void display(){ //显示
System.out.println("显示学生信息!");
}
}
//学生类有3个职责,连接数据库,传·查询、删除数据,显示查询结果
//存在问题
//1.如果需要修改连接的数据库服务器,需要修改这个类
//2.如果需要修改查询方式。或者增加插入学生数据。需要修改这个类
//有两个引起变化的原因,职责不单一
使用单一职责的模式:
//数据库工具类,指责单一
public class DBUtil {
public void getConnection(String url){
System.out.println("与"+url+"建立连接。");
}
}
//数据库操作类,职责单一
public class StudentDAO {
private DBUtil db;
public StudentDAO(DBUtil db) {
super();
this.db = db;
}
public void searchStudent(String condition){ //数据操作
System.out.println("根据"+condition+"查询学生。");
}
public void deleteStudnet(String condition){ //数据操作
System.out.println("根据"+condition+"“删除学生”");
}
}
//学生管理类,仅仅显示学生信息,职责单一
public class StudentManager {
private StudentDAO dao;
public StudentManager(StudentDAO dao) {
super();
this.dao = dao;
}
public void display(){ //显示
System.out.println("显示学生信息!");
}
}
//测试类
public class Demo {
public static void main(String[] args) {
DBUtil db = new DBUtil();
StudentDAO dao = new StudentDAO(db);
StudentManager sm = new StudentManager(dao);
db.getConnection("MySQL");
dao.searchStudent("马文鑫");
sm.display();
}
}
//优点:
//1.DBUtil封装了数据库连接操作,更改数据库服务器仅仅修改这个类或者配置
//2.StudentDAO封装了数据操作的方法,增减插入、更改查询方法等方法比较容易
案例结果:
UML图:
3.3 开闭原则
开闭原则(Open Closed Principle,OCP)
软件实体应对扩展开放,对修改关闭。
“对扩展开放”:表示实体的行为是可以扩展的。当应用的需求改变时,可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。
“对修改关闭”:表示对实体进行扩展时,不必改动模块的源代码或者二进制代码。
开闭原则是面向对象可复用设计的第一块基石,是最重要的面向对象设计原则。
实现开闭原则的关键在于“抽象”,采用相对稳定的抽象层 + 灵活的具体层。把系统所有可能的行为抽象成一个抽象底层,这个抽象底层规定所有具体实现必须提供的方法特征。
作为系统设计的抽象层,要预见所有可能的扩展,从而使得在任何扩展情况下,系统的抽象底层不需修改;同时,由于可以从抽象底层导出一个或多个新的具体实现,可以改变系统的行为,因此系统设计对扩展是开放的。
开闭原则的作用
开闭原则是一个最基本的原则,其它原则都是开闭原则的具体形态,是指导设计的工具和方法,开闭原则才是精神领袖。
- 开闭原则有利于进行单元测试
- 开闭原则可以提高复用性
- 开闭原则可以提高可维护性
- 面向对象开发的要求
案例:
设计汽车类Car,有start、run和stop方法,现在需要增加导航功能。
反例:
//========开闭原则
//设计类Car,有stop、start、run方法
public class Car {
public void stop(){
System.out.println("停车!");
}
public void start(){
System.out.println("启动汽车");
}
public void run(){
//增加导航功能
System.out.println("开始导航");
System.out.println("汽车在行驶");
//直接修改代码
//增加导肮功能,违反了开闭原则
}
}
//改变来了
//需要增加导航功能,汽车跟着导航行驶
//在run方法种增加导航功能,违反开闭原则
public class Demo {
public static void main(String[] args) {
Car gelly = new Car();
gelly.start();
gelly.run();
gelly.stop();
}
}
正例:
//开闭原则正例
//设计类Car,有stop、start、run方法
//需要给汽车增肌导航功能,汽车可以跟导航走
public class Car {
public void stop(){
System.out.println("停车!");
}
public void start(){
System.out.println("启动汽车");
}
public void run(){
System.out.println("汽车在行驶");
}
}
//对类Car进行扩展,没有修改源代码,没有违反开闭原则
public class CarNavigation extends Car{
private void navigation(){
System.out.println("开始导航");
}
@Override
public void run() {
super.run();
this.navigation();//调用导航功能
System.out.println("汽车跟着导航走");
}
}
public class Demo {
public static void main(String[] args) {
CarNavigation gelly = new CarNavigation();
gelly.start();
gelly.run();
gelly.stop();
System.out.println("=======没有导航的汽车=======");
Car qq = new Car();
qq.start();
qq.run();
qq.stop();
}
}
案例结果:
UML图:
3.4 接口隔离原则
接口隔离原则(Interface Segregation Principle,ISP)
- 客户端不应该依赖它不需要的接口
- 类之间的依赖关系应该建立在最小的接口上
接口隔离原则是对接口的使用进行约束的一个原则。
接口分为两种:
- 类接口:使用interface定义的接口
- 实例接口:使用class定义的类也是一种接口
隔离:
- 客户端和它不需要的接口隔离,即客户端不使用它不需要的接口,使用该接口的客户端仅需指导与之相关的方法
- 每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干,接口中没有多余的方法
接口隔离原则表明,在设计类(接口)时,若把无关方法定义在一个接口中,导致该接口变得臃肿,而应该根据实际需要,把一个大的接口分割成一些更细小的接口,即细化接口。
接口隔离原则的作用
- 避免接口污染:一个类若要实现一个接口,那么就要实现这个接口的所有方法,若这个接口包含这个类不需要的方法,就会造成接口污染,为系统留下隐患。
- 提高灵活性:一个类可以同时实现多个接口,所以将一个臃肿的接口分割为若干个小接口,通过小接口的不同组合可以满足更多的需求。
- 提供定制服务:通过细化接口为访问者提供需要的方法。
- 实现高内聚:高内聚就是接口、类、模块中定义了一组相关的行为。接口是对外界的承诺,承诺越少对系统的开发就越有利,变更的风险也就越少,同时也有利于降低成本。通过细化接口实现高内聚。
反例:
//学生成绩管理反例
public interface HandleScore {
void updata();
void delete();
void search();
void print();
}
public class Student implements HandleScore {
@Override
public void updata() { }
@Override
public void delete() { }
@Override
public void search() { }
@Override
public void print() {
System.out.println("输出学生成绩!");
}
}
//缺点:存在接口污染
//因为学生只能查询成绩,而不能修改、修改成绩
//如果学生类Student实现HandleScore接口,存在Student不需要的方法
正例:
//学生成绩管理正例
//对HandleScore接口进行测试
//修改接口
public interface Modifyable {
void delete();
void insert();
}
//输出学生成绩
public interface Printable {
void print();
}
//查询学生成绩接口
public interface Searchable {
void search();
}
public interface Updateable {
void update();
}
//定义学生类,实现查询接口
public class Student implements Searchable{
@Override
public void search() {
}
}
//教师类能够查询、更新、打印学生成绩
public class Teacher implements Searchable,Updateable,Printable{
@Override
public void print() {
}
@Override
public void search() {
}
@Override
public void update() {
}
}
//教务秘书类能够修改、打印和查询功能
public class AcademicSecretary implements Modifyable,Printable,Searchable{
@Override
public void delete() {
}
@Override
public void insert() {
}
@Override
public void print() {
}
@Override
public void search() {
}
}
UML图:
3.5 依赖倒置原则
依赖倒置原则(Dependency Inversion Principle,DIP)
高层模块不应该依赖底层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
依赖倒置原则的本质就是通过抽象(接口或抽象类)是各个类或者模块的实现彼此独立,不互相影响,实现模块间的松耦合。
依赖倒置原则基于这样一个事实:抽象稳定,而细节具有多变性,要针对抽象(接口、抽象类)编程,不要针对实现(具体类)编程。
以抽象为基础搭建起来的框架比以细节为基础搭建起来的架构要稳定。
利用接口或者抽象类来定制规范和锲约,具体细节由实现类完成。
在传递参数(依赖关系)或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等。
依赖倒置实现的三种方式:
- 构造注入。通过构造方法声明依赖对象。
- 设值注入(setter注入)。在类中通过Setter方法声明依赖关系。
- 接口注入。在接口的方法中声明依赖对象。
依赖倒置原则的作用
依赖倒置原则的核心思想是面向接口编程
- 减少类间的耦合性
- 提高系统的稳定,降低并行开发引起的风险
- 提高代码的可读性和可维护性
案例:
//依赖倒置的反例
//文件管理系统中,文件管理器需要读取txt、doc、xsl等文件
//采用传统的设计方法
public class Demo {
public static void main(String[] args) {
AutoRead autoRead = new AutoRead();
autoRead.read(new TXTRead());
autoRead.read(new DocRead());
autoRead.read(new ExcelRead());
autoRead.read(new XMLRead());
}
}
//改变
//现在需要增加读取XML文件的功能
//解决办法是,增加XMLRead类,然后在AutoRead方法中增加read方法读取xml文件,违反开闭原则
//自动读取文件
public class AutoRead {
public void read(TXTRead r){
r.read();
}
public void read(DocRead r){
r.read();
}
public void read(ExcelRead r){
r.read();
}
//修改源代码 存在安全隐患 违反开闭原则
//增加读取XML文件的功能
public void read(XMLRead r) {
r.read();
}
}
//读取doc文件类
public class DocRead {
public void read() {
System.out.println("读取doc文件");
}
}
//读取Excel文件类
public class ExcelRead {
public void read() {
System.out.println("读取xsl文件");
}
}
//读取txt文件类
public class TXTRead {
public void read() {
System.out.println("读取txt文件");
}
}
//增加读取XML文件类
public class XMLRead {
public void read() {
System.out.println("读取XML文件");
}
}
案例结果:
UML图:
正例:
//依赖导致原则的正例
//文件管理系统中,文件管理器需要读取txt、xsl、doc等文件
//采用依赖导致原则设计
public class Demo {
public static void main(String[] args) {
AutoRead autoRead = new AutoRead();
autoRead.read(new DocRead());
autoRead.read(new TXTRead());
autoRead.read(new ExcelRead());
autoRead.read(new XMlRead());
}
}
//现在需要扩展,读取xml文件
//定义类XMLRead实现Readable接口,就能实现这个功能,不需要修改源代码
//自动读取文件类,依赖抽象
public class AutoRead {
public void read(Readable readable) {
readable.read();
}
}
//读取接口
public interface Readable {
void read();
}
//实现读取接口
public class DocRead implements Readable{
@Override
public void read() {
System.out.println("读取doc文件");
}
}
//实现读取接口
public class ExcelRead implements Readable{
@Override
public void read() {
System.out.println("读取xsl文件");
}
}
//实现读取接口
public class TXTRead implements Readable{
@Override
public void read() {
System.out.println("读取txt文件");
}
}
//实现读取接口
public class XMlRead implements Readable{
@Override
public void read() {
System.out.println("读取xml文件");
}
}
3.6 里氏替换原则
里氏替换原则(Liskov Substitution Principle,LSP)
所有引用基类的地方必须能透明地使用其子类的对象,即替换成子类之后,系统的行为不会发生改变。
继承的优点:
- 提高代码的重用性,子类拥有父类的方法和属性
- 提高代码的可扩展性,子类可形似于父类,但异于父类,保留自己的特性
继承的缺点
- 继承是侵入性的。继承关系中,子类拥有父类的所有方法和属性,在一定程度上约束了子类,降低了代码的灵活性
- 增加了耦合。当父类的常量、变量或者方法被修改了,需要考虑子类的修改,所有一旦父类有了改动,很可能会造成非常糟糕的结果,要重构大量的代码
- 降低代码的灵活性。子类必须拥有父类的属性和方法,增强了对子类的约束 。
继承关系要遵守的四个规则:
- 子类可以实现父类的抽象方法,但不能覆写父类的非抽象方法
- 子类中可以增加自己特有的方法,体现子类的个性
- 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的前置条件(即方法的输入参数)要比父类方法的输入参数更宽松或或或相等
- 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或相等
里氏替换原则的作用:
- 约束继承泛滥,开闭原则的一种体现
- 加强程序的健壮性,在程序变化时可以做到非常好的兼容性,提高程序的维护性、扩展性
- 降低需求变更时引入的法风险
反例:
//里氏替换原则
//子类重写父类的非抽象方法,出现意外情况
public class Demo {
public static void readFile(ReadFile readFile,String fileName) {
readFile.read(fileName);
}
public static void main(String[] args) {
//父类读取Excel文件
readFile(new ReadFile(), "a.xsl");
//用子类替换父类,子类能够读取doc文件,但传入的是xsl文件
//类的使用者不知所措
readFile(new ReadDoc(), "b.xsl");
}
}
//父类
public class ReadFile {
public void read(String fileName) {
System.out.println("读取Excel文件:"+fileName);
}
}
//子类继承父类,并重写父类中的方法
public class ReadDoc extends ReadFile{
public void read(String fileName) {
System.out.println("读取doc文件:"+fileName);
}
}
正例:
//里氏替换原则正例
public class Demo {
public static void readFile(Readable readable, String fileName) {
readable.read(fileName);
}
public static void main(String[] args) {
Readable readable = new ReadDoc();
readFile(readable, "a.doc");//读取doc文件
ReadExcel readExcel = new ReadExcel();
readFile(readExcel, "b.xsl");
//子类透明替换父类(接口)
}
}
//父类是接口
public interface Readable {
void read(String fileName);
}
//子类实现了接口Readable
public class ReadDoc implements Readable{
@Override
public void read(String fileName) {
System.out.println("读取doc文件:" + fileName);
}
}
//子类实现接口Readable,覆写父接口的抽象方法
public class ReadExcel implements Readable{
@Override
public void read(String fileName) {
System.out.println("读取Excel文件:" + fileName);
}
}
案例结果:
UML图:
3.7 组合复用原则
组合复用原则(Composite Reuse Principle,CRP)
优先使用组合(聚合),而不是继承来达到复用的目的。
在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分,新对象通过委派调用已有对象的方法达到复用功能的目的。
即尽量使用组合(contains-a)和聚合(has-a),而不是继承(is-a)达到复用的目的。
组合复用原则的作用
继承复用的优点:
- 实现简单。通过继承,子类拥有父类的功能。
- 易于扩展。子类通过增加新的属性和方法对父类进行扩展。
继承复用的缺点:
- 继承将基类的实现细节暴露给子类,继承复用破坏来封装性,是**“白箱”复用**。
- 若基类的实现发生改变,则子类的实现也不得不发生改变。
- 从超类继承而来的实现是静态的,不可能在运行时间内发生改变,因此没有足够的灵活性。
使用继承时需要考虑里氏替换原则。
组合复用原则的作用:
- 新对象存取对象的唯一途径是通过成员对象的接口,这种复用是黑箱复用,使系统更加灵活,降低类与类之间的耦合度;
- 成员对象的内部细节对新对象是不可见的,这种复用支持封装,减少依赖,一个雷的变化对其他造成的影响相对较少;
- 每一个新的类可以将焦点集中在一个任务上;
- 这种复用可以在运行时动态进行,新对象可动态引用与成员对象类型相同的对象。
反例:
//组合复用原则反例
//教育局人员管理系统,学生包括,小学生、初中、高中生
//老师有小学教师、初中教师和高中教师
//采用继承实现
class Person{}
class Teacher extends Person{}
class Student extends Person{}
//小学教师
class PrimarySchoolTeacher extends Teacher { }
//初中教师
class JuniorSchoolTeacher extends Teacher { }
//高中教师
class HighSchoolTeacher extends Teacher{ }
//学生类的定义
//小学生
class PrimaryStudent extends Student {
public void say() {
System.out.println("我是小学生");
}
}
//初中生
class JuniorHighStudent extends Student {
public void say() {
System.out.println("我是初中生");
}
}
//高中生
class HighStudent extends Student {
public void say() {
System.out.println("我是高中生");
}
}
//缺点
//若要增加后勤管理人员Logistics,则需要继承Person类,并且也有小学、初中和高中级别
class Logistics extends Person{}
//小学后勤人员
class primaryLogistics extends Logistics { }
//初中后勤人员
class JuniorHighLogistics extends Logistics{ }
//高中后勤人员
class HighLogistics extends Logistics { }
//继承,增加了四个类
public class Demo {
public static void main(String[] args) {
}
}
正例:
//组合复用原则正例
//教育局人员管理系统,学生包括,小学生、初中、高中生
//老师有小学教师、初中教师和高中教师
//采用组合复用原则实现
abstract class Person {
private String name;
private SchoolLevel level;//学校层次与人是关联关系(组合)
public Person(String name, SchoolLevel level) {
this.name = name;
this.level = level;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLevel() {
return this.level.getLevel();
}
public void setLevel(SchoolLevel level) {
this.level = level;
}
}
class Teacher extends Person {
public Teacher(String name, SchoolLevel level) {
super(name, level);
}
public void say() {
System.out.println(super.getName()+"是"+super.getLevel()+"老师");
}
}
//学生类
class Student extends Person {
public Student(String name, SchoolLevel level) {
super(name, level);
}
public void say() {
System.out.println(super.getName()+"是"+super.getLevel()+"学生");
}
}
//学校层次
interface SchoolLevel{
String getLevel();
}
//小学
class PrimaryLevel implements SchoolLevel {
@Override
public String getLevel() {
return "小学";
}
}
//初中
class JuniorLevel implements SchoolLevel {
@Override
public String getLevel() {
return "初中";
}
}
//高中
class SeniorLevel implements SchoolLevel {
@Override
public String getLevel() {
return "高中";
}
}
//优点
//类的规模减少了,若增加后勤人员Logistics,也分为小学、初中、高中
//后勤人员
class Logistics extends Person {
public Logistics(String name, SchoolLevel level) {
super(name, level);
}
public void say() {
System.out.println(super.getName()+"是"+super.getLevel()+"后勤人员");
}
}
//========测试========
public class Demo {
public static void main(String[] args) {
SchoolLevel level = new SeniorLevel();//高中层次
Student swk = new Student("孙悟空", level);
swk.say();
Teacher puti = new Teacher("菩提老祖", new PrimaryLevel());
puti.say();
Logistics cattle = new Logistics("牛魔王", new JuniorLevel());
cattle.say();
}
}
3.8 迪米特法则
迪米特法则(Law of Demeter,LoD)
一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。或者:每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
含义:
- 迪米特法则要求在设计系统时,应该尽可能减少对象之间的交互;
- 若两个对象之间不必彼此直接通信,那么这两个对象不应该发生任何直接的相互作用;
- 若其中一个对象需要调用另一个对象的方法,可以通过“第三者”转发这个调用;
- 通过引入一个合理的“第三者”来降低现有对象之间的耦合度。
迪米特法则强调不和“陌生人”说话,若满足下面条件之一,就是当前对象的“朋友”;否则就是“陌生人”。
- 当前对象本身(this);
- 当前对象方法中的参数;
- 当前对象的实例变量直接引用的对象;
- 当前对象的实例变量若是一个聚集,那么聚集中的元素也都是朋友;
- 当前对象方法中所创建的对象。
迪米特法则对设计类提出的四个要求
- 优先考虑一个类设置成不变类;
- 尽量降低一个类的访问权限;
- 谨慎使用Serializable(序列化);
- 尽量降低成员的访问权限。
迪米特法则的作用
迪米特法则的作用:降低类之间的耦合。
利用迪米特法则虽然可以采用“中介”实现当前类与非直接的类通信,但是过分的使用迪米特法则,会产生大量中介和传递者,导致系统复杂度变大。
所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。
反例:
// 迪米特法则反例
// 客户端程序
public class Client {
public static void main(String[] args) {
ExcelToDB excelToDB = new ExcelToDB("a.xsl", "mysql");
String str = excelToDB.read();
excelToDB.connectionDB();
excelToDB.save(str);
}
}
//缺点
//ExcelToDB客户端暴露太多内容,存在一定的安全隐患,即客户端可能完成其他不能预料的任务
正例:
//把Excel文件信息转换成数据库信息保存
public class ExcelToDB {
private String fileName;
private String db;
public ExcelToDB(String fileName, String db) {
this.fileName = fileName;
this.db = db;
}
//私有化方法,避免暴露,符合迪米特法则的最低权限要求
private String read() {
return this.fileName + "文件内容";
}
//私有化方法
private void connectionDB() {
System.out.println("连接" + this.db + "数据库系统");
}
//私有化方法
private void save(String conten) {
System.out.println("把" + this.read() + "保存在数据库中");
}
//向外部暴露一个方法
public void perform() {
String str = this.read();
this.connectionDB();
this.save(str);
}
}
//中介者.或第三者
public class UtilConvert {
private ExcelToDB toDB;
public UtilConvert(ExcelToDB toDB) {
this.toDB = toDB;
}
//工具类方法向外暴露
public void convert() {
toDB.perform();
}
}
// 客户端类
public class Client {
public static void main(String[] args) {
ExcelToDB todb = new ExcelToDB("a.xsl", "MySQL");
UtilConvert convert = new UtilConvert(todb);
convert.convert();
}
}
//优点
//客户端不需要指导该如何转换,仅仅调用工具类的转换方法就能完成信息的转换过程,客户端利用最少知识完成任务
4.1 简单工厂模式
简单工厂模式
简单工厂模式(静态工厂方法模式)。在简单工厂模式中,定义一个工厂类,该类能够根据不同参数返回不同类的实例,被创建的实例有共同的父类
又称为静态工厂方法(Static Factory Method)模式
特点:通过给工厂方法传入参数就能够获得一个对象,不需要指导创建细节。
模式结构
简单工厂模式结构中包括三个角色:
- 抽象产品类(Product)。负责定义所有具体产品的公共接口。
public abstract class Product{
//产品类的公共方法
public void methodSame(){
//公共方法的实现
}
//抽象方法
public abstract void methodDiff();
}
- 具体产品类(ConcreteProduct)。抽象产品的子类,简单工厂模式的创建目标,所有被创建的对象都是某个具体产品类的实例。
public class ConcreteProduct extends Product{
//实现业务方法
public void methodDiff(){
//业务方法的实现
}
}
- 工厂类(Factory)。负责实现创建所有具体产品类的实例内部逻辑,工厂类可以被外界直接调用。
//典型工厂类的代码
public class Factory {
//静态工厂方法
public static Product getProduct(String arg){
Product product = null;
if(arg.equalsIgnoreCase("A")){
product = new ConcreteProductA();
//初始化设置product
}
else if(arg.equalsIgnoreCase("B")){
product = new ConcreteProductB();
//初始化设置product
}
return product;
}
}
模式的优点
- 用户不需要艰难记住具体类名,仅仅记住枪械名就可以了,减轻了编程负担。
- 创建对象由工厂完成,把类的定义与应用进行解耦,增加灵活性。
应用场景
- 定义的具体类产品类不需要扩展;
- 工厂类负责创建的对象比较少,不会造成工厂方法中的业务逻辑太过复杂;
- 用户不需要关心创建对象细节(类名都不需要知道),只要传入工厂方法的参数就能够创建需要的对象;
- 用户需要一个类的子类的实例,但不希望该类的子类形成耦合。
反例:
//简单工厂反例
/*===具体描述===
一座现代化兵工厂能根据需要生产各种武器,例如机关枪、手枪和狙击步枪等等,但是不能生产子弹
*/
//抽象产品
abstract class Weapon{
abstract void display();
}
//机关枪继承Weapon
class MachineGun extends Weapon {
@Override
void display() {
System.out.println("机关枪");
}
}
//手枪继承Weapon
class Pistol extends Weapon {
@Override
void display() {
System.out.println("手枪");
}
}
//客户端
public class DemoN {
public static void main(String[] args) {
Weapon w1 = new MachineGun();
w1.display();
Weapon w2 = new Pistol();
w2.display();
}
}
//缺点
//1.客户端需要知道具体子类的名称,增加了用户编程的难度
//若由很多武器,客户端需要进行选择
//2.客户端需要直接创建对象,没有实现定义于应用的解耦,缺乏灵活性
//改进措施,采用简单工厂模式
正例:
// 简单工厂正例
/*===具体描述===
一座现代化兵工厂能根据需要生产各种武器,例如机关枪、手枪和狙击步枪等等,但是不能生产子弹
*/
// 1.抽象产品
abstract class Weapon{
abstract void display();//显示兵器名称
}
// 2.具体产品类
// 2.1 机关枪继承Weapon
class MachineGun extends Weapon {
@Override
void display() {
System.out.println("机关枪");
}
}
// 2.2 手枪继承Weapon
class Pistol extends Weapon{
@Override
void display() {
System.out.println("手枪");
}
}
// 3.定义工厂类(核心)
class WeaponFactory{
//静态方法生产武器,参数表示武器类型
public static Weapon createWeapon(String type) {
Weapon w = null;
switch (type) {
case "手枪":
w = new Pistol();
break;
case "机关枪":
w = new MachineGun();
break;
default:
System.out.println("不能生产该兵器:"+type);
}
return w;
}
}
//客户端
public class DemoP {
public static void main(String[] args) {
//兵工厂生产手枪
Weapon pistol = new WeaponFactory().createWeapon("手枪");
pistol.display();
//兵工厂生产机关枪
Weapon mg = new WeaponFactory().createWeapon("机关枪");
mg.display();
//兵工厂生产AK47-B
Weapon ak47 = new WeaponFactory().createWeapon("AK47-B");
ak47.display();
}
}
//优点
//1.用户不需要记住具体类产品类,仅仅记住创建的对象名就可以,减轻了编程人员的负担
//2.创建对象由工厂完成,把类的定义与应用进行解耦,增加了灵活性
//缺点
//1.系统扩展比较困难,当添加新产品(具体枪械)时需要修改工厂逻辑(工厂类的静态方法switch(),违反开闭原则)
//2.简单工厂模式是用了static方法,造成工厂角色无法形成基于继承的等级结构
结果:
UML图:
4.2 工厂方法模型
工厂方法模式(Factory Method Pattern)
工厂方法模式(Factory Method Pattern),定义一个用于创建对象的接口,让子类决定实例化那一个类。工厂方法模式使一个类的实例化延迟到其子类。
又称之为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式。
在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的使将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
模式结构
工厂方法模式结构中包括四个角色:
- 抽象产品类(Product):负责定义所有具体产品的公共接口。
public abstract class Product{
//公共方法
public void methodA();
//抽象方法
public abstract void methidB();
}
- 具体产品类(ConcreteProduct):抽象产品的子类,工厂方法模式中工厂方法所创建的对象使某个具体产品类的实例。具体产品与具体工厂之间需要一一对应,即具体产品由对应的具体工厂所创建。
public class ConcreteProduct extends Product{
//实现业务方法
public void methodB(){
//代码块
}
}
- 抽象工厂类(AbstractFactory):一个接口(或者抽象类)声明的工厂方法(Factory Method)返回一个抽象产品。抽象工厂是工厂方法模式的核心,所有创建具体产品的具体工厂类都需要实现该接口。
public interface Factory{
public Product createProduct();
}
- 具体工厂(ConcreteFactory)类:抽象工厂类(接口)的子类,实现了抽象工厂中定义的工厂方法,返回一个具体产品类的实例。
public class ConcreteFactory implements Factory{
public Product createProduct(){
//代码块
//return new ConcreteProduct();
}
}
模式优点
- 在工厂方法模式中,具体工厂方法(createWeapon)创建客户端所需要的具体产品,客户端只需了解所需具体产品对应的工厂,无须关心创建细节,也不需要知道产品类的类名。
工厂方法将客户和实际创建具体产品的代码解耦。 - 利用多态原理,该模式能够使抽象工厂自主确定创建何种产品对象,而创建具体产品对象的细节被封装在具体工厂内部(所有具体工厂类继承(实现)相同的抽象父类工厂,因此该模式又称为多态工厂模式)。
工厂方法模式的关键使设计抽象工厂角色和抽象产品角色。 - 利用该模式,当向系统加入新产品时,仅仅添加一个具体工厂和相应的具体产品就能够满足要求。系统的可扩展性非常好,符合“开闭原则”。
模式缺点
- 在添加新产品时,需要设计新产品的具体类以及相应的具体工厂类,类的个数成对增加在一定程度上增加了系统复杂性。
- 为了增强系统的可扩展性,引入了抽象产品类和抽象工厂类,抽象层增加了系统的抽象性和理解难度。
- 若需要增加产品簇,例如武器工厂需要制造与武器匹配的子弹,工厂方法模式不能很好解决这个问题。
应用场景
- 第一种情况,调用者知道,应该使用哪个工厂实例化具体工厂对象,由该工厂对象生产出具体产品对象。
- 第二种情况。客户需要一种产品,但并不关心工厂生产的具体细节,也就是说,系统选用的具体工厂对客户来说时透明的。
反例:
//工厂方法反例
/*===具体描述===
一座现代化兵工厂能根据需要生产各种武器,例如机关枪、手枪和狙击步枪等等,但是不能生产子弹
现在根据国防需求变化,在残酷的反恐作战中狙杀恐怖分子
*/
//采用简单工厂实现增加狙击步枪功能
// 1.抽象产品
abstract class Weapon{
abstract void display();//显示兵器名称
}
// 2.具体产品类
// 2.1 机关枪继承Weapon
class MachineGun extends Weapon {
@Override
void display() {
System.out.println("机关枪");
}
}
// 2.2 手枪继承Weapon
class Pistol extends Weapon {
@Override
void display() {
System.out.println("手枪");
}
}
//====增加狙击步枪
//2.3 狙击步枪继承Weapon
class SniperRifer extends Weapon {
@Override
void display() {
System.out.println("狙击步枪");
}
}
// 3.定义工厂类(核心)
class WeaponFactory{
//静态方法生产武器,参数表示武器类型
public static Weapon createWeapon(String type) {
Weapon w = null;
switch (type) {
case "手枪":
w = new Pistol();
break;
case "机关枪":
w = new MachineGun();
break;
//修改原代码,违反开闭原则
case "狙击步枪":
w = new SniperRifer();
break;
default:
System.out.println("不能生产该兵器:"+type);
}
return w;
}
}
//客户端
public class DemoN {
public static void main(String[] args) {
//兵工厂生产手枪
Weapon pistol = new WeaponFactory().createWeapon("手枪");
pistol.display();
//兵工厂生产机关枪
Weapon mg = new WeaponFactory().createWeapon("机关枪");
mg.display();
//生产狙击步枪
Weapon awm = new WeaponFactory().createWeapon("狙击步枪");
awm.display();
//兵工厂生产AK47-B
Weapon ak47 = new WeaponFactory().createWeapon("AK47-B");
ak47.display();
}
}
/*
缺点:
1.系统扩展相当困难,当添加新产品(具体枪械)时需要修改工厂逻辑(工厂类中的静态方法的switch语句),破坏了"开闭原则"
若需要增加一个新兵器,需要继承Weapon抽象类,并且修改createWeapon方法中的代码
2.简单工厂模式使用了static工厂方法,造成工厂角色无法形成基于继承的等级结构
*/
//解决办法 采用工厂方法模式
正例:
//工厂方法模式 正例
/*===具体描述===
一座现代化兵工厂能根据需要生产各种武器,例如机关枪、手枪和狙击步枪等等,但是不能生产子弹
现在根据国防需求变化,在残酷的反恐作战中狙杀恐怖分子
*/
//工厂方法模式模拟兵器工厂
// 1.抽象产品
abstract class Weapon{
abstract void display();//显示兵器名称
}
// 2.具体产品类
// 2.1 机关枪继承Weapon
class MachineGun extends Weapon {
@Override
void display() {
System.out.println("机关枪");
}
}
// 2.2 手枪继承Weapon
class Pistol extends Weapon {
@Override
void display() {
System.out.println("手枪");
}
}
//2.3 狙击步枪类
class SniperRifer extends Weapon {
@Override
void display() {
System.out.println("狙击步枪");
}
}
// 3. 定义工厂接口
interface WeaponFactory{
Weapon createWeapon();
}
//4. 实现武器工厂接口创建不同武器
//4.1 创建机关枪工厂
class MachineGunFactory implements WeaponFactory {
@Override
public Weapon createWeapon() {
return new MachineGun();//返回机关枪
}
}
//4.2 创建手枪工厂
class PistolFactory implements WeaponFactory {
@Override
public Weapon createWeapon() {
return new Pistol();//返回手枪
}
}
//4.3 创建狙击步枪工厂
class SniperRiferFactory implements WeaponFactory {
@Override
public Weapon createWeapon() {
return new SniperRifer();
}
}
//客户端
public class DemoP {
public static void main(String[] args) {
//兵工厂生产机关枪
WeaponFactory wf1 = new MachineGunFactory();
Weapon w1 = wf1.createWeapon();
w1.display();
//兵工厂生产手枪
WeaponFactory wf2 = new PistolFactory();
Weapon w2 = wf2.createWeapon();
w2.display();
//生产狙击步枪
WeaponFactory wf3 = new SniperRiferFactory();
Weapon w3 = wf3.createWeapon();
w3.display();
}
}
//若需要增加狙击步枪生产线生产狙击步枪
//不用修改源代码
//1. 定义狙击步枪子类 继承Weapon
//2. 定义狙击步枪工厂SniperRiferFactory实现WeaponFactory,制造狙击步枪
4.3 抽象工厂模式
抽象工厂模式
产品等级结构
产品等级结构即产品的继承结构。
例如,抽象武器类,其子类有机关枪、手枪和狙击步枪等等,那么抽象武器与具体的武器(机关枪、手枪和狙击步枪)构成了一个产品等级结构。抽象子弹类,其子类有机关枪子弹、手枪字典和狙击步枪子弹都能德国,抽象子弹类与具体子弹类之间构成了一个产品等级结构。
产品簇