23种设计模式(一)
1. 软件设计模式意义
设计模式的本质是对面向对象设计原则运用,是对类的封装、继承和多态以及类的关联关系和组合关系的充分理解。优点:
- 可以提高程序员的思维能力、编程能力和设计能力
- 使程序更加标准化、代码编写更加工程化,使软件开发效率大大提升,从而缩短开发周期
- 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
1.1 设计模式的基本要素
软件设计模式可以使人们更加简洁方便的复用成功的设计和体系结构。
最关键的包括如下4个主要部分
- 模式名称:有助于我们理解和记忆该模式。
- 问题:描述了该模式的应用环境,解释了设计问题的前因后果,以及必须满足一些条件。
- 解决方案:模式就像是一个模板,可应用于不同的场景,所以解决方案并不描述一个特定的设计或者实现,而是提供设计问题的抽象描述和怎样用一个具有一般意义的元素组合来解决这个问题。
- 效果:描述了模式的优缺点。
1.2 GoF 23种设计模式的分类和功能
设计模式有两种分类方法:根据目的来分和根据模式的作用范围来分。
1.2.1 根据目的来分
根据模式是用来完成什么工作目的的,这种凡是分为创建型模式、结构型模式和行为型模式3种。
- 创建型模式:用于描述“怎样创建对象”,主要特点就是“将对象的创建和使用分离”。例如:单例、原型、工厂方法、抽象工厂、建造者5中模式创建模式。
- 结构型模式:用于描述如何将类和对象按照某种布局组成更大的结构。例如:代理、适配器、桥接、装饰、外观、享元、组合等7种结构型模式。
- 行为型模式:用于描述类或者对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。例如:模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等11中行为型模式。
1.2.2 根据作用范围来分
根据模式主要用于类上还是对象上来分,可以分为类模式和对象模式。
- 类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译的时刻便确定下来。例如:工厂方法、适配器、模板方法、解释器等属于该模式。
- 对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以改变的,更具动态性。除了类模式的4中,其他模式都是对象模式。
范围\目的 |
创建型模式 |
结构型模式 |
行为型模式 |
类模式 |
工厂方法 |
(类)适配器 |
模板方法、解释器 |
对象模式 |
单例 |
代理 |
策略 |
1.3 23种设计模式的功能
- 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该类的实例,其拓展是有限多例模式。
- 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制克隆出多个和原型类似的新实例。
- 工厂方法(Factory Method)模式:定义一个创建产品的接口,由子类决定生产用什么产品。
- 抽象工厂(Abstract Factory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列的相关产品。
- 建造者(Builder)模式:将一个复杂的对象分解成多个简单的部分,然后根据不同的需要分别创建他们,最后构建成复杂的对象。
- 代理(Proxy)模式:为某个对象提供一种代理以控制对该对象的访问。即客户端通过代理间接的访问该对象,从而限制、增强或修改对象的一些特性。
- 适配器(Adapter)模式:将一个类的接口转换成客户希望的另一个类的接口,使得原本由于接口不兼容而不能一起工作的哪些类能一起工作。
- 桥接(Bridge)模式:将抽象与实现分离,使他们可以独立变化。他是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
- 装饰(Decorator)模式:动态给对象增加一些职责,即增加其额外的功能。
- 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
- 享元(Flyweight)模式:运用共享技术来有效的支持大量的细粒度对象的复用。
- 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
- 模板方法(Templete Method)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类在不改变算法结构的情况下重新定义该算法的某些特定步骤。
- 策略(Strategy)模式:定义了一些列算法,并将每个算法封装起来,使他们可以相互替换,且算法的改变不会影响使用算法的客户。
- 命令(Commond)模式:将一个请求封装成一个对象,使发出请求的责任和执行请求的责任分开。
- 职责链(Chain Of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方法除去了对象之间的耦合。
- 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
- 观察者(Observer)模式:多个对象间存在一对多的关系,当一个对象发生改变时,把这种改变通知其他各个对象,从而影响其他对象的行为。
- 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象之间的耦合度,使原有对象不比相互了解。
- 迭代器(Iterator)模式:提供一个方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
- 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者访问。
- 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
- 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
1.4 类之间的关系
在软件系统中,类不是孤立存在的,类与类之间存在各种关系。
- 依赖关系:依赖关系是一种使用关系,它是对象之间耦合度最弱的关联方式,是临时性的关联关系。在代码中,某个类的方法通过局部变量、方法的参数或者静态方法的调用来访问另一个类中的某些方法来完成一些职责。
//人打电话
public class Person{
private String name;
//打电话
public void call(MobilePhone mp){
//通过方法参数来访问另一个类
mp.call("打电话");
}
}
//电话
public class MobilePhone{
public void call(String message){
//打电话一系列操作,不详细介绍
...
}
}
- 关联关系:关联关系是对象之间的一种引用关系,用于标识一类对象跟另一类对象之间的联系。如老师和学生,师傅和徒弟…。关联关系也可以是双向的,可以是单向的。
public class Teacher{
String name;
//一个老师可以有多个学生
List<Student> students;
}
public class Student{
String name;
//一个学生可以有多个老师,语文、数学...老师
List<Teacher> teachers;
}
- 聚合关系:聚合关系是关联关系的一种,是强关联关系,是整体和部分之间的关系。聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体而单独存在。例如:学校和老师之间的关系
public class University{
String name ;
//一个大学有多个老师
List<Teacher> teachers;
}
//成员变量,老师
public class Teacher{
String name;
}
- 组合关系:组合关系也是一种关联关系,标识类之间的整体与部分的关系,但是它是一种更强烈的关系。在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也不存在了。例如:头和嘴的关系,没了头嘴也不存在了.
//头和嘴关系,头
public class Head{
Mouth mouth;
Eye eye;
}
public class Mouth{
//嘴的属性...
}
- 泛化关系:泛化关系是对象之间耦合度最大的一种,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承类的关系。例如:学生和老师都是人…
//人
public class Person{
Integer age;
String sex;
String name;
}
public class Student extends Person{
//学生编号
Integer studentNo;
//学习
public void study(){
//...学习
}
}
public class Teacher extends Person{
//老师编号
Integer teacherNo;
//教学
public void teach(){
//教学..
}
}
- 实现关系:接口与实现类之间的关系,这种关系中,类实现了接口,类中的操作实现了接口中所有生命的抽象操作。例如:交通工具 move,汽车,火车,轮船之间的关系
//交通工具
public interface Vehicle {
public void move();
}
//汽车实现交通工具接口类
public class Car implements Vehicle{
@Override
public void move() {
// TODO Auto-generated method stub
}
}
//火车实现交通工具接口类
public class Train implements Vehicle{
@Override
public void move() {
// TODO Auto-generated method stub
}
}
2.面向对象的7大原则
在软件开发过程中,为了提高系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序要都需要遵循7条原则来开发,从而提高开发效率节约研发成本和维护成本。
2.1 开闭原则
开闭原则:软件开发过程中,软件实体应当对扩展开放,对修改关闭。也就是当应用需求改变的时候,在不修改软件实体的源码或者二进制码的情况下,可以扩展模块的功能,使其满足新的需求。
// -- windows主题展示,抽象类,将接口抽象画
public abstract class Subject {
public abstract void display();
}
//具体的主题是子类
public class SubjectOne extends Subject {
public void display(){
System.out.println("第一个主题");
}
}
//具体的主题是子类
public class SubjectTwo extends Subject {
@Override
public void display() {
// TODO Auto-generated method stub
System.out.println("第二个主题");
}
}
2.2 里氏替换原则
主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,以及其中蕴含的原理。它是继承复用的基础,反应了基类与子类之间的关系,是对开闭原则的补充,是对抽象化的具体步骤的规范。
里氏替换原则的作用:
- 是实现开闭原则的重要方式之一
- 克服了父类继承当中重写父类造成的可复用性变差的缺点
- 它是动作正确性的保证。即已有的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但是不能改变父类的功能。也就是说:子类继承父类的时候,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
//动物
public class Animal {
//行走速度
private double runSpeed;
//distance公里所需时间
public double getRunTime(double distance){
return distance/ runSpeed;
}
public void setRunSpeed(double runSpeed) {
this.runSpeed = runSpeed;
}
}
//鸟类继承动物,但是鸟类是飞行,鸟类扩展了动物的特性,相当于子类的新特性,而且不会影响基类的操作
public class Bird extends Animal {
//鸟类特性 fly速度
private double flySpeed;
//鸟类飞行distance公里所需时间
public double getFlyTime(double distance){
return distance / flySpeed;
}
public void setSpeed(double flySpeed) {
this.flySpeed = flySpeed;
}
}
//燕子继承鸟类
public class Swallow extends Bird {
}
//几维鸟继承动物,几维鸟是鸟但是不会飞
public class BrownKiwi extends Animal {
}
public class Main {
public static void main(String[] args) {
Bird swallow = new Swallow();
swallow.setSpeed(120);
Animal kiwi = new BrownKiwi();
kiwi.setRunSpeed(10);
System.out.println("如果飞行或者行走300公里");
System.out.println("燕子飞行300公里用时"+swallow.getFlyTime(300)+"小时");
System.out.println("几维鸟行走300公里用时"+kiwi.getRunTime(300)+"小时");
}
}
2.3 依赖倒置原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。其核心思想就是:要面向接口编程,不要面向实现编程。这里的抽象代表接口或者抽象类,而细节指具体的实现。
依赖倒置原则是实现开闭原则的重要途径之一,降低了客户与实现模块的耦合度。
依赖倒置原则的作用:
- 降低类之间的耦合度
- 提高系统的稳定性
- 减少并行开发引起的风险
- 提高代码的可读性和可维护性
依赖倒置原则的实现方法:
- 每个类尽量提供接口或者抽象类,或者两者皆备
- 变量的声明类型应尽量是接口或者抽象类
- 任何类不应该从具体派生类
- 使用继承时应尽量遵循里氏替换原则
//顾客购物场景
//商店卖东西
//无论顾客选择那家商店,商店(Store)都有一个sell()接口,顾客(Customer)都有个shopping()购物接口。
//商店卖东西接口类
public Interface Store{
//卖东西
public String sell();
}
public class Customer{
private String name;
public String shopping(Store shop){
//购物
return "顾客购买东西"+shop.sell();
}
public Customer(String name){
this.name = name;
}
}
//鞋店也是商店,卖鞋,实现了商店接口
public class ShoeStore implements Store{
public String sell(){
return "耐克,艾迪达斯,鸿星尔克...各种各样的鞋子";
}
}
//衣服店也是商店,卖衣服,实现了商店接口
public class ClothesStore{
public String sell(){
return "卫衣,毛衣,衬衫,短袖...各种衣服";
}
}
public class Test{
public static void main(String args[]){
//顾客
Customer custom = new Customer("张三");
//购买鞋子
custom.shopping(new ShoeStore());
//购买衣服
custom.shopping(new ClothesStore());
}
}
//运行结果:
//张三:
//顾客购买东西:售卖:耐克,艾迪达斯/,鸿星尔克...各种各样的鞋子
//顾客购买东西:卫衣,毛衣,衬衫,短袖...各种衣服
2.4 单一职责原则
单一职责原则规定,一个类有且仅有一个引起变化的原因,否则这个类就应该被拆分。对象不应该承担太多职责,如果一个对象承担太多职责则会有以下两个缺点:
- 一个职责的变化可能会削弱或者抑制这个类实现其他职责的能力
- 当客户端需要这个对象的一个职责时,不得不将其他不需要的职责包含进来,从而造成冗余代码或者代码的浪费。
单一职责的有点:
- 降低类的复杂度,一个类只负责一个职责,其逻辑肯定要比负责多个类的职责简单的多。
- 提高类的可读性。复杂性降低,自然可读性会提高
- 提高系统的可维护性。可读性提高,自然维护就容易很多了。
- 变更引起的风险减低。变更是必然的,如果单一职责的原则遵守好,当修改一个功能的时候,可以显著降低对其他功能的影响。
/**
* 分析:大学学生工作主要包括学生生活辅导和学生学业指导两个方面的工作,其中生活辅导主要包括班委建设、出勤统计、心理辅导、费用催缴、班级管理等工作,学业指导主要包括专业引导、学习辅导、科研指导、学习总结等工作。如果将这些工作交给一位老师负责显然不合理,正确的做 法是生活辅导由辅导员负责,学业指导由学业导师负责。
* 总的来说:学生工作分为两块,生活辅导员,学业辅导员
**/
//学生工作
public class StudentWork{
//生活辅导员
public LifeTeacher lifeTeacher;
//学业辅导员
public TeachTeacher teachTeacher;
}
//生活辅导主要包括班委建设、出勤统计、心理辅导、费用催缴、班级管理等工作
public class LifeTeacher{
public void 班委建设(){
//..班委极限社相关内容
}
public void 出勤统计(){
//..出勤统计相关内容
}
public void 心理辅导(){
//..心理辅导相关内容
}
public void 费用催缴(){
//..费用催缴相关内容
}
public void 班级管理(){
//..班级管理相关内容
}
}
//学业辅导员主要包括专业引导、学习辅导、科研指导、学习总结等工作
public class TeachTeacher {
public void 专业引导(){
//..专业引导相关内容
}
public void 学习辅导(){
//..学习辅导相关内容
}
public void 科研指导(){
//..科研指导相关内容
}
public void 学习总结(){
//..学习总结相关内容
}
}
2.5 接口隔离原则
接口隔离原则就是将庞大臃肿的接口拆分成更小或者更具体的接口,让接口中只包含客户感兴趣的方法。
要为各个类建立他们专用的接口,而不要试图去建立一个很庞大的接口供所有的依赖它的类去调用。
接口隔离原则和单一职责原则有相同点,都是为了提高类的内聚性、降低他们之间的耦合度,体现了封装的思想。但是两者也有不同:
- 单一职责注重的是职责,接口隔离注重的是对接口的依赖隔离
- 单一职责主要是约束类,针对的是程序中的实现和细节;接口隔离主要是约束接口,主要针对抽象和程序整体框架的构建。
接口隔离原则的优点:
- 将庞大臃肿的接口拆分成多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性
- 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合度
- 如果接口的粒度大小定义合理,能够保证系统的稳定性;但是,如果接口定义的粒度过小,则会造成接口的数量过多,使得设计复杂化;如果接口定义过大,灵活性就会降低,无法提供定制服务,给整体项目带来无法预料的风险
- 使用多个专门的接口还能够体现对象的层次,因为可以实现对接口的继承,实现对总接口的定义。
- 能减少项目中代码的冗余,过大的大接口里面放置了好多无用的方法,挡视线这个接口的时候被迫设计冗余的代码。
接口隔离原则的实现方法:
- 接口尽量小,但是要有限度。一个接口只服务于一个子模块或者业务逻辑
- 为依赖接口的类定制服务。只提供调用者需要的方法,屏蔽不需要的方法。
- 了解环境,拒绝盲从。每个项目或者产品都会选定的环境因素,环境不同,接口拆分的标准就会不同,深入了解业务逻辑
- 提高内聚,减少对外交互,使得接口用最少的方法完成最多的事情
//学生成绩管理程序一般包含插入成绩、删除成绩、修改成绩、计算总分、计算均分、打印成绩信息、査询成绩信息等功能,如果将这些功能全部放到一个接口中显然不太合理,正确的做法是将它们分别放在输入模块、统计模块和打印模块等 3 个模块中
//1.输入模块:插入成绩,修改成绩,删除成绩
//2.统计模块:计算总分,计算平均分
//3.打印模块:打印成绩信息,查询成绩信息
// 插入模块
public interface InputMoudle{
//插入成绩
public void insert();
//删除成绩
public void delete();
//修改成绩
public void update();
}
// 统计模块
public interface CountMoudle{
//计算总分
public Integer countTotalScore();
//计算平均分
public double countAvgScore();
}
// 打印模块
public interface PrintMoudle{
//打印成绩信息
public void print();
//查询成绩信息
public void search();
}
// 实现类
public class StuScoreList implements InputMoudle,CountMoudle,PrintMoudle{
private StuScoreList{}
public static InputMoudle getInputMoudle(){
return (InputMoudle)new StuScoreList();
}
public static CountMoudle getCountMoudle(){
return (CountMoudle)new StuScoreList();
}
public static PrintMoudle getPrintMoudle(){
return (PrintMoudle)new StuScoreList();
}
public void insert()
{
System.out.println("输入模块的insert()方法被调用!");
}
public void delete()
{
System.out.println("输入模块的delete()方法被调用!");
}
public void modify()
{
System.out.println("输入模块的modify()方法被调用!");
}
public void countTotalScore()
{
System.out.println("统计模块的countTotalScore()方法被调用!");
}
public void countAverage()
{
System.out.println("统计模块的countAverage()方法被调用!");
}
public void printStuInfo()
{
System.out.println("打印模块的printStuInfo()方法被调用!");
}
public void queryStuInfo()
{
System.out.println("打印模块的queryStuInfo()方法被调用!");
}
}
public class ISPtest
{
public static void main(String[] args)
{
InputModule input =StuScoreList.getInputModule();
CountModule count =StuScoreList.getCountModule();
PrintModule print =StuScoreList.getPrintModule();
input.insert();
count.countTotalScore();
print.printStuInfo();
//print.delete();
}
}
2.6米迪特法则
米迪特法则的定义是:如果两个软件实体无需直接通信,那么就不应当发生直接的交互调用,可以通过第三方转发该调用。其目的是为了降低类之间的耦合度,提高模块相对独立。
米迪特法则的有点:
- 降低了类的耦合度,提高了模块的相对独立性
- 由于亲和度降低,从而提高了类的可复用率和系统的扩展性。
米迪特法则的缺点:过度使用米迪特法则会使得系统产生大量的中介类,从而增加系统的复杂度,使得模块之间的通信效率降低。所以在采用米迪特法则的时候需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。
米迪特法则的实现方法:
- 从依赖者的角度来说,只依赖该依赖的对象。
- 从被依赖的角度来说,只暴露该暴露的方法。
所以在运用米迪特法则的时候需要注意6点:
- 在累的划分上,应该创建弱耦合的类,类与类之间的耦合越弱,就越有利于实现可复用的目标。
- 在类的结构设计上,尽量减低类成员的访问权限
- 在类的设计上,优先考虑将一个类设置成不变类
- 在对其他类的引用上,将引用其他对象的次数降到最低
- 不暴露类的属性成员,而应该提供类的访问器(set 和 get方法)
- 谨慎使用序列化功能
//明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则
//明星
public class Star{
private String name ;
private String getName(){
return this.name;
}
}
//粉丝
public class Fans{
private String name;
public String getName{
//获取粉丝名称
return this.name;
}
}
//公司
public class Company{
private String name;
public String getName{
//获取公司名称
return this.name;
}
}
//经纪人
public class Agent{
private Star myStar;
private Fans myFans;
private Company myCompany;
public void setStar(Star myStar){
this.myStar = mystar;
}
public void setFans(Fans myFans){
this.myFans = myFans;
}
public void setCompany(Company myCompany){
this.myCompany = myCompany;
}
public void meeting(){
System.out.println(myFans.getName +"与明星"+myStar.getName()+"见面了");
}
public void business(){
System.out.println(myCompany.getName +"与明星"+myStar.getName()+"洽谈业务了");
}
}
public class LoDtest
{
public static void main(String[] args)
{
Agent agent=new Agent();
agent.setStar(new Star("林心如"));
agent.setFans(new Fans("粉丝韩丞"));
agent.setCompany(new Company("中国传媒有限公司"));
agent.meeting();
agent.business();
}
}
// 粉丝韩丞与明星林心如见面了。
// 中国传媒有限公司与明星林心如洽淡业务。
2.7 合成复用原则
合成复用原则要求在软件复用时,要尽量先使用组合或者聚合关系来关联实现,其次才考虑使用继承关系来实现。如果要使用继承关系,则必须严格遵循里氏替换原则。
合成复用原则的重要性,通常复用分为继承复用和合成复用两种,继承复用虽然简单和易实现的有点,但是也有缺点:
- 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
- 子类与父类耦合度高。父类的实现的改变都会导致子类的实现发生变化,这不利于扩展和维护。
- 限制了复用的灵活性。从父类继承而来的实现是静态的,在编译的时候已经定义了,所以在运行时不可能发生变化。
采用组合或者聚合复用时,可以将已有的对象纳入新的对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,有以下优点:
- 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
- 新旧类之间的耦合度低。这种复用所需要的依赖少,新对象存取成分对象的唯一方法就是通过成分对象的接口。
- 复用的灵活性高。这种复用可以在运行动态进行,新对象可以动态的引用成分对象类型相同的对象。
合成复用原则的实现方法:合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员来实现,新对象可以调用已有对象的功能,从而达到复用。
//分析:汽车按“动力源”划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这两种分类,其组合就很多
//汽车:--移动move
// : --颜色Color:
// --白色
// --黑色
// --红色
//汽车对象将颜色对象纳入到汽车里面,作为汽车的对象来使用
public class Color{
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
public class Car{
private Color color;
public void move(){
System.out.println("汽车");
}
public Car(Color color){
this.color = color;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}
public class GasoLineCar extends Car{
public GasoLineCar(Color color) {
super(color);
// TODO Auto-generated constructor stub
}
}
public class White extends Color{
}
public class Main {
public static void main(String[] args) {
Color color = new White();
color.setColor("白颜色");
Car car = new GasoLineCar(color);
System.out.println(car.getColor().getColor());
car.move();
}
}
// 白颜色
// 汽车
3 创建模式的特点和分类
3.1 创建模式的特点
创建模式主要是"将对象的创建和使用分开",这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由工厂来完成。就像我们去商场购买商品的时候,不需要知道商品是如何生产出来的一样,因为它由专门的公司生产。
创建模式分类:
- 单例模式
- 原型模式
- 工厂方法模式
- 抽象工厂模式
- 建造者模式
工厂方法模式是类模式,其他四种都是对象模式
3.1.1 单例模式
在有些系统中,为了节省内存资源、保证数据内容一致,对某些类要求只能创建一个实例,这就是单例模式。
单例模式的定义:
一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如:windows系统之只能打开一个任务管理器,这样可以避免打开多个而浪费资源,或者各个窗口显示的内容不一致导致错误。
单例模式的特点:
- 单例实例只有一个实例对象
- 该单例对象必须有该类自行创建
- 单例提供对外访问的一个全局访问点
单例模式的实现:
- 懒汉式单例
- 饿汉式单例
- 登记式单例
懒汉式单例:
该模式的特点就是类加载的时候没有生成单例,只有当第一次调用getInstance()方法的时候才会生成单例。
public class LazySingleton {
//volatile 保证instance在所有的线程中同步
private static volatile LazySingleton instance = null;
//private 避免类在外部被实例化
private LazySingleton(){
System.out.println("创建一个懒汉单例模式..");
}
// synchronized 同步操作
public static synchronized LazySingleton getInstance(){
System.out.println("模式instance="+instance);
if(null == instance){
System.out.println("开始创建..");
instance = new LazySingleton();
System.out.println("创建成功instance"+instance);
}
return instance;
}
}
public class Client {
public static void main(String[] args) {
for(int i = 0 ; i < 3 ; i++){
System.out.println(i+".================================");
LazySingleton instance = LazySingleton.getInstance();
}
}
}
运行结果:
0.================================
模式instance=null
开始创建..
创建一个懒汉单例模式..
创建成功instancecom.singleton.LazySingleton@6d06d69c
1.================================
模式instance=com.singleton.LazySingleton@6d06d69c
2.================================
模式instance=com.singleton.LazySingleton@6d06d69c
注意:如果编写多线程程序,不要删除上列代码的关键字volatile 和 synchronized ,否则会引起线程非安全问题。如果不删除这两个关键字就能保证多线程安全,但是每次访问都需要同步,会影响性能,而且消耗更多资源,这就是懒汉式单例的缺点。
饿汉单例式:该模式的特点是类一旦加载就会创建一个单例,保证在调用getInstance方法之前单例已经存在。
public class HungrySingleton {
// 类加载的时候就会创建一个单例模式
private static final HungrySingleton instance = new HungrySingleton();
// 保证实例不会被外部创建
private HungrySingleton(){
System.out.println("开始创建单例..");
}
public static HungrySingleton getInstance(){
System.out.println("获取单例..instance="+instance);
return instance;
}
}
public class Client {
public static void main(String[] args) {
for(int i = 0 ; i < 3 ; i++){
System.out.println(i+".================================");
HungrySingleton instance = HungrySingleton.getInstance();
}
}
}
运行结果:
0.================================
开始创建单例..
获取单例..instance=com.singleton.HungrySingleton@6d06d69c
1.================================
获取单例..instance=com.singleton.HungrySingleton@6d06d69c
2.================================
获取单例..instance=com.singleton.HungrySingleton@6d06d69c
饿汉式单例在类创建的时候同时也创建了一个静态对象供系统使用,以后不在改变,所以是线程安全的,可以直接用于多线程而不会出现问题。
单例模式的应用场景:
- 在应用场景中,某个类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
- 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象的访问速度。如Web中配置的对象、数据库连接池等。
- 当某个类需要频繁的实例化,而创建的对象需要频繁被销毁的时候,如线程的线程池、网络连接池等。
3.1.2 原型模式
用一个已经创建好的对象作为原型,通过复制该对象原型来创建和原型相同或者相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无需知道对象创建的细节。例如:windows操作系统的安装比较繁琐,如果复制的话就简单多了。
原型模式的结构和实现
由于java提供了对象的clone()方法,所以用java的实现原型模式很简单。
- 模式的结构:原型模式包含如下几个角色
- 抽象类原型:规定的具体的原型对象必须实现的接口
- 具体原型类:实现类抽象类原型的clone()方法,它是可被复制的对象。
- 访问类:使用具体原型类中的clone()方法来复制新的对象。
- 原型模式的实现
原型模式的克隆分为浅克隆和深克隆,java中的Object类提供了浅克隆的clone()方法,具体原型类只要实现Cloneable接口就可以实现对象的浅克隆,这里的Cloneable就是抽象原型类。
- 浅克隆:当被克隆的类中有引用对象(String、Integer等)基本类型除外时,克隆出来的类中的引用变量存储的还是之前对象的内存地址。
//浅克隆的例子
//用原型模式生成“三好学生”奖状。
//分析:同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,属于相似对象的复制,同样可以用原型模式创建,然后再做简单修改就可以了。
public class Citation implements Cloneable{
private String name;
private String info;
private String college;
//对象的引用
private Address address;
public Citation(String name, String info, String college) {
super();
this.name = name;
this.info = info;
this.college = college;
System.out.println("奖状创建成功 name="+name);
}
public Citation(String name, String info, String college, Address address) {
super();
this.name = name;
this.info = info;
this.college = college;
this.address = address;
System.out.println("奖状创建成功 name="+name+",address="+address.getName());
}
public void display(){
System.out.println(name+info+college+address.getName());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public String getCollege() {
return college;
}
public void setCollege(String college) {
this.college = college;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
System.out.println("奖状拷贝成功name="+name+",address="+address.getName());
return (Citation)super.clone();
}
public class Address implements Serializable{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address();
address.setName("北京");
Citation citation = new Citation("张三", "这一学期成绩优异,特此颁布三好学生奖状", "北京大学",address);
citation.display();
Citation citation2 = (Citation)citation.clone();
citation2.setName("李四");
//克隆完成之后,重新设置address为shanghai,发现张三的地址也变了,也就是指向的元素地址是指向的原来张三的地址,也就是引用对象还是用的原来的地址
citation2.getAddress().setName("shanghai");
citation2.display();
citation.display();
}
}
//运行结果
奖状创建成功 name=张三,address=北京
张三这一学期成绩优异,特此颁布三好学生奖状北京大学北京
奖状拷贝成功name=张三,address=北京
李四这一学期成绩优异,特此颁布三好学生奖状北京大学shanghai
张三这一学期成绩优异,特此颁布三好学生奖状北京大学shanghai
- 深克隆:当被克隆的类中有引用对象(String、Integer等)基本类型除外时,克隆出来的类中的引用对象也需要单独的去克隆,也就是引用对象内部也需要单独实现Cloneable类,实现clone()方法。注意:用final关键字修饰的变量不能被克隆。而且深度克隆还可以通过序列化和反序列化来实现。
深度克隆-:普通克隆
//普通深度克隆列子
public class Person implements Cloneable{
private String name;
private String age;
//兴趣爱好,对象的引用
private Interest interest;
//构造方法
public Person(String name, String age, Interest interest) {
super();
this.name = name;
this.age = age;
this.interest = interest;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
Person person = (Person)super.clone();
person.interest = (Interest)this.interest.clone();
return person;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public Interest getInterest() {
return interest;
}
public void setInterest(Interest interest) {
this.interest = interest;
}
public void display(){
System.out.println(name +","+age+",兴趣爱好 (instrest)"+interest.getInterest_name());
}
}
//引用对象需要实现Cloneable接口
public class Interest implements Cloneable{
private String interest_name;
public Interest(String interest_name) {
super();
this.interest_name = interest_name;
}
//引用对象内部也要重写clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
Interest interest = (Interest)super.clone();
return interest;
}
public String getInterest_name() {
return interest_name;
}
public void setInterest_name(String interest_name) {
this.interest_name = interest_name;
}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Interest interest = new Interest("喝咖啡");
Person p1 = new Person("张三", "18", interest);
p1.display();
Person p2 = (Person)p1.clone();
p2.setName("李四");
p2.getInterest().setInterest_name("逛街");
p2.display();
p1.display();
}
}
//运行结果
张三,18,兴趣爱好 (instrest)喝咖啡
李四,18,兴趣爱好 (instrest)逛街
张三,18,兴趣爱好 (instrest)喝咖啡
//从运行结果中分析,李四克隆了张三的属性,但是兴趣爱好也克隆了,此时李四跟张三的兴趣指向的是不同的地址,修改李四的兴趣爱好(interset)的时候不会印象张三的兴趣爱好(interset)
深度克隆-:序列化克隆
//使用序列化克隆的时候,被克隆的类必须实现序列化接口类Serializable,否则就会出现如下错误
Exception in thread "main" java.io.NotSerializableException: com.yuanxing.Person
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
at java.io.ObjectOutputStream.writeObject(Unknown Source)
at com.yuanxing.Main.main(Main.java:39)
public class Person implements Serializable{
private String name;
private String age;
//兴趣爱好,对象的引用
private Interest interest;
//构造方法
public Person(String name, String age, Interest interest) {
super();
this.name = name;
this.age = age;
this.interest = interest;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public Interest getInterest() {
return interest;
}
public void setInterest(Interest interest) {
this.interest = interest;
}
public void display(){
System.out.println(name +","+age+",兴趣爱好 (instrest)"+interest.getInterest_name());
}
}
public class Interest implements Serializable{
private String interest_name;
public Interest(String interest_name) {
super();
this.interest_name = interest_name;
}
public String getInterest_name() {
return interest_name;
}
public void setInterest_name(String interest_name) {
this.interest_name = interest_name;
}
}
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Interest interest = new Interest("打篮球");
Person p1 = new Person("张三", "18", interest);
p1.display();
//使用序列化和反序列化实现深克隆
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(p1);
byte[] bytes = out.toByteArray();
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(in);
Person p2 = (Person)ois.readObject();
p2.getInterest().setInterest_name("看电影");
p2.setName("王麻子");
p2.display();
p1.display();
}
}
//运行结果
张三,18,兴趣爱好 (instrest)打篮球
王麻子,18,兴趣爱好 (instrest)看电影
张三,18,兴趣爱好 (instrest)打篮球
3.1.3 工厂模式
定义一个创建产品对象的工厂接口,将产品创建的实际创建工作推迟到子工厂类当中。这种模式满足典型的“创建对象与使用对象分离”的特点。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫做“简单工厂模式”,它不属于23种设计模式,缺点是增加产品时会违背“开闭原则”。
工厂模式的主要优点:
- 用户只需要知道具体工厂的名称就可以得到所需要的产品,无需知道产品的具体创建过程。
- 在系统增加新产品时候,只需要添加具体的品类和具体的工厂类,无需对原工厂进行修改,满足开闭原则
工厂模式的缺点:每增加一个产品就需要增加一个具体的产品类和具体工厂类,增加了系统的复杂度。
工厂模式的模式结构:
- 抽象工厂:提供了创建产品的接口,调用者通过访问具体的工厂创建产品。
- 具体工厂:主要实现了抽象工厂,来完成创建具体产品的操作
- 抽象产品:定义了产品的范围,描述了产品的主要特性和功能。
- 具体产品:实现了抽象产品角色所定义的接口,有具体的工厂来创建,同具体的工厂一一对应。
//例子:汽车和工厂,卡车和公共汽车都属于汽车,汽车属于抽象产品,卡车和公共汽车属于具体产品
//工厂属于抽象工厂,卡车工厂和公共汽车工厂属于具体工厂
public interface Car {
public void show();
}
public interface CarFactory {
public Car createCar();
}
public class Truck implements Car{
@Override
public void show() {
// TODO Auto-generated method stub
System.out.println("我是一辆卡车");
}
}
public class Bus implements Car{
@Override
public void show() {
// TODO Auto-generated method stub
System.out.println("我是一辆公共汽车。|-|。");
}
}
public class TruckFactory implements CarFactory{
@Override
public Car createCar() {
// TODO Auto-generated method stub
System.out.println("进入卡车工厂,开始创建卡车");
return new Truck();
}
}
public class BusFactory implements CarFactory{
@Override
public Car createCar() {
// TODO Auto-generated method stub
System.out.println("进入公共汽车工厂,开始创建公共汽车");
return new Bus();
}
}
public class Main {
public static void main(String[] args) {
Car car ;
CarFactory factory;
factory = new BusFactory();
car = factory.createCar();
car.show();
factory = new TruckFactory();
car = factory.createCar();
car.show();
}
}
//运行结果
进入公共汽车工厂,开始创建公共汽车
我是一辆公共汽车。|-|。
进入卡车工厂,开始创建卡车
我是一辆卡车
3.1.4 抽象工厂
是一种访问类提供一个创建一组相关或者相互依赖对象的接口,且访问类无需指定所需要产品的具体类就能得到同族的不同等级的产品的模式结构。
抽象工厂是工厂方法模式的升级版,工厂方法只生产一个等级的产品,而抽象工厂模式可以生产多个等级的产品。
抽象工厂模式一般满足以下条件
- 系统中有多个产品族,每个具体的工厂创建统一族但是不同等级的产品。
- 系统一次可能消费其中某一族的产品,即同族的产品一起使用。
产品登记结构与产品族
- 产品登记结构:产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品的等级结构,抽象电视机是父类,而具体品牌的电视机是子类。
- 产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同的产品等级结构中的一组产品。如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机的产品等级结构中,海尔电冰箱位于电冰箱的产品结果中,海尔电视机、海尔电冰箱构成了一个产品族。
抽象工厂模式拒了具有工厂模式的特点外, 其主要有以下优点:
- 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
- 当增加一个新的产品族时不需要修改原代码,满足开闭原则。
抽象工厂的缺点:当产品族中需要增加一个新的产品时,所有的共产类都需要进行修改。
抽象工厂模式的结构与实现
抽象工厂同工厂方法模式一样,也具有四个要素:抽象工厂、具体工厂、抽象产品、具体产品4个要素组成。但是抽象工厂中的方法个数不同,抽象产品的个数也不同。
模式的结构:
- 抽象工厂:提供了产品的创建接口,包含多个创建产品的方法newProduct(),可以创建多个不同等级的产品。
- 具体工厂:主要是实现抽象工厂的多个抽象方法,完成具体的产品创建
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品:实现了抽象产品角色所定义的所有产品,由具体的工厂来创建,它同具体工厂之间是一对多的关系,一个工厂可以生产多个不同等级的产品。
例子:每个网站都有不同的皮肤,每个皮肤包含按钮(button),输入框(textFiled),边框(comboBox)..等
//抽象产品-按钮类
public interface Button {
public void display();
}
//具体产品 - Spring按钮
public class SpringButton implements Button {
@Override
public void display() {
// TODO Auto-generated method stub
System.out.println("浅绿色Springbutton");
}
}
//具体产品 - SummerButton
public class SummerButton implements Button {
@Override
public void display() {
// TODO Auto-generated method stub
System.out.println("绿色按钮SummerButton");
}
}
//输入框
public interface TextFiled {
public void display();
}
//具体产品 - SpringTextFiled
public class SpringTextFiled implements TextFiled {
@Override
public void display() {
// TODO Auto-generated method stub
System.out.println("浅绿色textFiled");
}
}
//具体产品 - SummerTextFiled
public class SummerTextFiled implements TextFiled {
@Override
public void display() {
// TODO Auto-generated method stub
System.out.println("绿色的textFiled");
}
}
//抽象产品 - ComboBox
public interface ComboBox {
public void display();
}
//具体产品 - SpringComboBox
public class SpringComboBox implements ComboBox {
@Override
public void display() {
// TODO Auto-generated method stub
System.out.println("浅绿色Combobox");
}
}
//具体产品- SummerComboBox
public class SummerComboBox implements ComboBox {
@Override
public void display() {
// TODO Auto-generated method stub
System.out.println("绿色Combobox");
}
}
//抽象工厂
public interface SkinFactory {
public Button createButton();
public TextFiled createTextFiled();
public ComboBox createComboBox();
}
//具体工厂 - 创建SpringSkin
public class SpringSkinFactory implements SkinFactory {
@Override
public Button createButton() {
// TODO Auto-generated method stub
return new SpringButton();
}
@Override
public TextFiled createTextFiled() {
// TODO Auto-generated method stub
return new SpringTextFiled();
}
@Override
public ComboBox createComboBox() {
// TODO Auto-generated method stub
return new SpringComboBox();
}
}
//具体工厂 - 创建SummerSkin
public class SummerSkinFactory implements SkinFactory {
@Override
public Button createButton() {
// TODO Auto-generated method stub
return new SpringButton();
}
@Override
public TextFiled createTextFiled() {
// TODO Auto-generated method stub
return new SpringTextFiled();
}
@Override
public ComboBox createComboBox() {
// TODO Auto-generated method stub
return new SpringComboBox();
}
}
public class Main {
public static void main(String[] args) {
SkinFactory factory ;
Button bt;
TextFiled tf;
ComboBox cb;
//根据工厂的不同,实例化不同的具体工厂,用工厂来创建统一族的不同等级的产品
factory = new SpringSkinFactory();
bt = factory.createButton();
tf = factory.createTextFiled();
cb = factory.createComboBox();
bt.display();
tf.display();
cb.display();
}
}
//运行结果:
//浅绿色Springbutton
//浅绿色textFiled
//浅绿色Combobox
3.1.5 建造者(Builder)模式
建造者模式指将一个复杂的对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示。它是将一个复杂的对象分解成多个简单的对象,然后一步一步构建而成。即:产品的组成是不变的,但是每一部分都可以灵活选择。
建造者模式的优点:
- 各个具体的建造者相互独立,有利于系统的扩展
- 客户端不必知道产品的内部组成细节,便于控制细节风险
建造者模式的缺点:
- 产品的组成部分必须相同,这限制了其使用范围
- 如果产品的内部变化复杂,该模式会增加许多建造者类。
建造者模式跟工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法更注重零部件的创建过程,但是两者可以结合使用。
建造者模式的结构和实现
建造者模式的结构:建造者模式由产品角色、抽象建造者、具体建造者、指挥者等4个要素组成。
- 产品角色:它是包含多个组件的复杂对象,由具体的建造者来创建各个组件。
- 抽象建造者:它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法getResult();
- 具体建造者:实现了抽象建造者(Builder)的接口,完成复杂产品组件的的具体创建方法。
- 指挥者:它调用抽象建造者中的部件构造与装配方法完成复杂对象的创建,在只会中不涉及具体产品的信息。
用建造者(Builder)模式描述客厅装修。
分析:客厅装修是一个复杂的过程,它包含墙体的装修、电视机的选择、沙发的购买与布局等。客户把装修要求告诉项目经理,项目经理指挥装修工人一步步装修,最后完成整个客厅的装修与布局,所以本实例用建造者模式实现比较适合。
这里客厅是产品,包括墙、电视和沙发等组成部分。具体装修工人是具体建造者,他们负责装修与墙、电视和沙发的布局。项目经理是指挥者,他负责指挥装修工人进行装修。
- 1
- 2
- 3
// 产品角色
public class Parlour {
private String wall;
private String tv;
private String sofa;
public void show(){
System.out.println("展示客厅的装修结果....");
System.out.println(wall);
System.out.println(tv);
System.out.println(sofa);
}
public String getWall() {
return wall;
}
public void setWall(String wall) {
this.wall = wall;
}
public String getTv() {
return tv;
}
public void setTv(String tv) {
this.tv = tv;
}
public String getSofa() {
return sofa;
}
public void setSofa(String sofa) {
this.sofa = sofa;
}
}
// 抽象的建造者
public abstract class Builder {
public Parlour parlour = new Parlour();
//构建组件
public abstract void buildWall();
//构建组件
public abstract void buildTv();
//构建组件
public abstract void buildSofa();
// 获取结果
public Parlour getResult(){
return parlour;
}
}
// 具体建造者
public class ConcreateDecoratorFirst extends Builder {
private String des = "建造者First,开始构建";
@Override
public void buildWall() {
// TODO Auto-generated method stub
System.out.println(des +"wall");
parlour.setWall("wall");
}
@Override
public void buildTv() {
// TODO Auto-generated method stub
System.out.println(des +"tv");
parlour.setTv("tv");
}
@Override
public void buildSofa() {
// TODO Auto-generated method stub
System.out.println(des +"sofa");
parlour.setSofa("sofa");
}
}
//具体建造者
public class ConcreateDecoratorSecond extends Builder {
private String des = "建造者second,开始构建";
@Override
public void buildWall() {
// TODO Auto-generated method stub
System.out.println(des +"wall");
parlour.setWall("wall");
}
@Override
public void buildTv() {
// TODO Auto-generated method stub
System.out.println(des +"tv");
parlour.setTv("tv");
}
@Override
public void buildSofa() {
// TODO Auto-generated method stub
System.out.println(des +"sofa");
parlour.setSofa("sofa");
}
}
//指挥者
public class Manager {
private Builder builder;
public Manager(Builder builder) {
this.builder = builder;
}
public Parlour decorate(){
builder.buildSofa();
builder.buildTv();
builder.buildWall();
return builder.getResult();
}
public Builder getBuilder() {
return builder;
}
public void setBuilder(Builder builder) {
this.builder = builder;
}
}
public class Main {
public static void main(String[] args) {
Builder builder = new ConcreateDecoratorFirst();
Manager manager = new Manager(builder);
Parlour parlour = manager.decorate();
parlour.show();
}
}
//运行结果:
建造者First,开始构建sofa
建造者First,开始构建tv
建造者First,开始构建wall
展示客厅的装修结果....
wall
tv
sofa
建造者模式创建的复杂对象,其产品的各个组成部分经常面领着剧烈的变化,但将他们组合在一起的算法相对稳定:
- 创建比较发复杂的对象,由多个部件组成,各部件面领着复杂的变化,但各个部件的建造顺序是稳定的。
- 创建复杂对象的算法独立于该对象的组成部分以及他们的装配方式,即产品的构建和最终的表示是相对独立的。
3.2结构型模式概述
结构型模式描述如何将类或者对象按照某种布局组成更大的结构。它分为类结构模型和对象结构模型,类结构模型采用继承机制来组织接口和类,对象结构模型采用组合或聚合来组合对象。
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构模型比类结构模型具有更大的灵活性。
结构型模式分为7种:
- 代理模式
- 适配器模式
- 桥接模式
- 装饰模式
- 外观模式
- 享元模式
- 组合模式
3.2.1 代理模式
由于某些原因需要给对象提供一个代理以控制该对象的访问权限。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象的中介。例如:12306代售点、找工作的中介等。。
代理模式的优点:
- 代理模式在客户端与目标对象之间起到一个中介的作用和保护目标对象的作用
- 代理模式可以扩展目标对象的功能
- 代理模式将客户端和目标对象分离,在一定程度上降低了系统的耦合度。
代理模式的缺点:
- 客户端和目标对象多了一个代理,会造成请求速度变慢
- 增加了系统的复杂度
代理模式的结构和实现:代理模式结构比较简单,主要是通过定义一个继承抽象主题的代理包含真实主题,从而实现对真实主题的访问。
模式结构:
- 抽象类:通过接口或者抽象类声明真实主题和代理对象的业务方法
- 真实主题:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,他可以访问、控制或者扩展真实主题的功能。
例子:奔驰中国大区代理
真实主题:奔驰
代理:中国大区总代理,可以对奔驰进行加价,漏油等操作
// 抽象主题:卖车
public interface Car {
public void sell();
}
//真实主题:卖奔驰
public class BenZ implements Car{
public void sell(){
System.out.println("访问卖奔驰车的方法...");
}
}
//中国大区代理
public class CNProxy implements Car{
private BenZ benz;
@Override
public void sell() {
// TODO Auto-generated method stub
if(benz == null){
benz = new BenZ();
}
addMoney();
//利用代理类,卖奔驰
benz.sell();
afterSell();
}
//扩展
public void addMoney(){
System.out.println("加价1000$");
}
//扩展
public void afterSell(){
System.out.println("卖出去之后开始漏油...");
}
}
public class Main {
public static void main(String[] args) {
CNProxy proxy = new CNProxy();
proxy.sell();
}
}
//运行结果
先加价1000$
访问卖奔驰车的方法...
卖出去之后开始漏油...
代理模式应用场景
- 远程代理:这种方式通常是为了隐藏目标对象在于不同的地址空间的事实。例如:申请某些网盘空间时,会在用户的文件系统中建议一个虚拟的硬盘,用户访问虚拟硬盘时实际上访问的是网盘空间。
- 虚拟代理:这种方式通常用于要创建的目标对象开销很大。例如:下载一部电影需要很长时间,因某种计算比较发咋而无法短时间完成,这时可以用小比例的虚拟代理替换真实对象,消除用户对服务器的缓慢感觉。
- 安全代理,这种方式通常用于控制不同类型客户对真实对象的访问权限设置。
- 智能指引,主要用于调用目标对象时,代理附加一些额外的功能。例如:增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
- 延迟加载,为了提高系统的性能,延迟目标的加载。
代理模式的扩展:SpringAOP原理其实就是用的动态代理模式。
3.2.2 适配器模式
适配器模式,将一个类的接口转换成客户希望的另一个类的接口,使得原本由于接口不兼容而不能一起工作的那些类能在一起工作。适配器模式分为类结构模式和对象结构模式两种,前者的耦合度比后者的高,且要求程序员了解现有组建中的相关的内部结构,所以应用相对较少。
适配器模式的有点:
- 客户通过适配器可以透明的调用目标接口
- 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类
- 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致问题
适配器模式的缺点:
- 对类适配器来说,更换适配器的实现过程比较复杂。
模式的结构和实现
结构:
- 目标接口:当前系统业务所期待的接口,它可以是抽象类或者接口
- 适配者类:它是被访问和适配的现存组件库中的组件接口
- 适配器类:它是一个转换器,通过继承或者引用适配者对象,把适配者接口转换成目标接口,让客户按照目标接口的格式访问适配者
新能源汽车的发动机有电能发动机(Electric Motor)和光能发动机(Optical Motor)等,各种发动机的驱动方法不同,例如,电能发动机的驱动方法 electricDrive() 是用电能驱动,而光能发动机的驱动方法 opticalDrive() 是用光能驱动,它们是适配器模式中被访问的适配者。
客户端希望用统一的发动机驱动方法 drive() 访问这两种发动机,所以必须定义一个统一的目标接口 Motor,然后再定义电能适配器(Electric Adapter)和光能适配器(Optical Adapter)去适配这两种发动机
分析:用统一的接口去实现两种方式的切换
目标接口(统一接口):Motor
适配者:电动发动机,光力发动机
适配器:电力适配器,光力适配器
//目标接口:target
public interface Motor {
public void drive();
}
//适配者 :adaptee
public class ElectricMotor{
public void electricDrive() {
// TODO Auto-generated method stub
System.out.println("电力发动机...");
}
}
//适配者:adaptee
public class OpticalMotor {
public void opticalDrive(){
System.out.println("光力发动机发动...");
}
}
//适配器 :adapter电能适配器
public class ElectricAdapter implements Motor{
private ElectricMotor eMotor;
public ElectricAdapter(ElectricMotor eMotor) {
this.eMotor = eMotor;
}
@Override
public void drive() {
// TODO Auto-generated method stub
eMotor.electricDrive();
}
public ElectricMotor geteMotor() {
return eMotor;
}
public void seteMotor(ElectricMotor eMotor) {
this.eMotor = eMotor;
}
}
//适配器 :adapter光能适配器
public class OpticalAdapter implements Motor {
private OpticalMotor oMotor;
public OpticalAdapter(OpticalMotor oMotor) {
this.oMotor = oMotor;
}
@Override
public void drive() {
// TODO Auto-generated method stub
oMotor.opticalDrive();
}
public OpticalMotor getoMotor() {
return oMotor;
}
public void setoMotor(OpticalMotor oMotor) {
this.oMotor = oMotor;
}
}
public class Main {
public static void main(String[] args) {
System.out.println("适配器模式开始测试...");
OpticalMotor opMotor = new OpticalMotor();
Motor motor = new OpticalAdapter(opMotor);
motor.drive();
}
}
运行结果:
适配器模式开始测试...
光力发动机发动...
3.2.3 桥接模式
桥接模式:将抽象与实现分离,使他们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
桥接模式的有点:
- 由于抽象和实现分离,扩展性能强
- 实现细节对客户透明
桥接模式缺点:
- 由于聚合关系建立的抽象层,要求开发者针对抽象进行设计与编程,这增加了系统的理解与设计难度。
桥接模式的结构和实现
可以将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系
桥接模式的结构:
- 抽象化角色:定义抽象类,并包含一个对实现化对象的引用
- 扩展抽象化角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
- 实现化角色:定义实现化角色的接口,供扩展抽象化角色的调用
- 具体实现化角色:给出实现化角色接口的具体实现
分析:女士皮包有很多种,可以按用途分、按皮质分、按品牌分、按颜色分、按大小分等,存在多个维度的变化,所以采用桥接模式来实现女士皮包的选购比较合适
颜色类:Color是一个维度,定义为实现化角色,它有两个具体实现化角色:黄色和红色,通过 getColor() 方法可以选择颜色
包类:是另一个维度,定义为抽象化角色,它有两个扩展抽象化角色:挎包和钱包,它包含了颜色类对象,通过 getName() 方法可以选择相关颜色的挎包和钱包
//实现化角色:color
public interface Color {
public String getColor();
}
//具体实现化角色:red
public class RedColor implements Color {
@Override
public String getColor() {
// TODO Auto-generated method stub
String color = "具体实现化角色:red";
return color;
}
}
//具体实现化角色:yellow
public class YellowColor implements Color {
@Override
public String getColor() {
// TODO Auto-generated method stub
String color = "具体实现化角色:yellow";
return color;
}
}
public abstract class Bag {
private Color color;
public abstract String getName();
public void setColor(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
}
//扩展抽象化角色:bag
public class HandBag extends Bag {
@Override
public String getName() {
// TODO Auto-generated method stub
String bag = "手包";
return bag;
}
}
//扩展抽象化角色:bag
public class WalletBag extends Bag {
@Override
public String getName() {
// TODO Auto-generated method stub
String bag = "钱包";
return bag;
}
}
public class Main {
public static void main(String[] args) {
Color color = new RedColor();
Bag bag = new HandBag();
bag.setColor(color);
System.out.println(bag.getName());
System.out.println(bag.getColor().getColor());
}
}
运行结果:
手包
具体实现化角色:red
3.2.4 装饰模式
在不改变现有对象的结构下,动态的给对象增加一些职责。例如:房子的装修,相片增加相框等。
装饰模式的有点:
- 采用装饰模式来扩展对象的功能比继承更加灵活
- 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。
装饰模式的缺点:
- 增加了许多子类,如果过度使用会使的程序变得更加复杂
装饰模式的结构和实现
通常情况下,扩展一个类的功能会使用继承的方式来实现。但是继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(装饰对象)来包裹真实对象,并且保持真实对象的结构不改变的前提下,为其提供额外的功能,这就是装饰模式的目标。
模式的结构:
- 抽象构件角色:定义一个抽象接口以规范准备接受附加对象的责任。
- 具体构建角色:实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰角色:继承抽象构件,并包含具体构件的实例可以通过其子类扩展具体构件的功能。
- 具体装饰角色:实现抽象装饰的相关方法,并给具体构件对象添加附加责任。
分析:在《恶魔战士》中,游戏角色“莫莉卡·安斯兰”的原身是一个可爱少女,但当她变身时,会变成头顶及背部延伸出蝙蝠状飞翼的女妖,当然她还可以变为穿着漂亮外衣的少女。这些都可用装饰模式来实现,在本实例中的“莫莉卡”原身有 setImage(String t) 方法决定其显示方式,而其 变身“蝙蝠状女妖”和“着装少女”可以用 setChanger() 方法来改变其外观,原身与变身后的效果用 display() 方法来
抽象构件角色:莫莉卡·安斯兰
具体构件角色:原身
抽象装饰角色:变形
具体装饰角色:女妖
//抽象构件角色:morrigan
public interface Morrigan {
public void display();
}
//具体装饰角色:原身
public class Original implements Morrigan{
@Override
public void display() {
// TODO Auto-generated method stub
System.out.println("莫丽卡原身...");
}
public Original() {
System.out.println("莫丽卡初始化原身...");
}
}
// 抽象装饰角色:change
public class Change implements Morrigan{
private Morrigan morrigan;
@Override
public void display() {
// TODO Auto-generated method stub
morrigan.display();
}
public Change(Morrigan morrigan) {
this.morrigan = morrigan;
}
public Morrigan getMorrigan() {
return morrigan;
}
public void setMorrigan(Morrigan morrigan) {
this.morrigan = morrigan;
}
}
public class Girl extends Change{
public Girl(Morrigan morrigan) {
super(morrigan);
}
@Override
public void display() {
// TODO Auto-generated method stub
super.display();
setChange();
}
//为具体的构建添加一些责任
public void setChange(){
System.out.println("你是一个漂亮的美女");
}
}
public class Main {
public static void main(String[] args) {
Morrigan morrigan = new Original();
morrigan.display();
System.out.println("--------开始进行装饰------------");
Change change = new Girl(morrigan);
change.display();
}
}
运行结果:
莫丽卡初始化原身...
莫丽卡原身...
--------开始进行装饰------------
莫丽卡原身...
你是一个漂亮的美女
装饰模式应用场景:
- 当需要给一个现有类添加附加职责,而又不能采用子类生成的方法进行扩充时。例如:该类被隐藏或者该类是终极类或者采用继承的方式会生成大量的子类。
- 当需要通过对现有的一组基本功能进行排列组合而产生非常多非常多的功能时,采用继承关系很难实现,而采用装饰模式则很好实现
- 当对象的功能要求可以动态添加、也可以再动态的撤销时。
装饰模式在java源码中体现在:InputStream 的子类 FilterInputStream,OutputStream 的子类 FilterOutputStream,Reader 的子类 BufferedReader 以及 FilterReader,还有 Writer 的子类 BufferedWriter、FilterWriter 以及 PrintWriter 等,它们都是抽象装饰类。
23种设计模式(一)相关推荐
- Java开发中的23种设计模式详解(转)
设计模式(Design Patterns) --可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...
- 23 种设计模式实战 pdf(很全)
今天分享一份学习资料:23 种设计模式实战教程.pdf,助你快速上手设计模式,写出各种高端代码,文末附下载地址. 设计模式一般分为三大类: 实战教程: 教程共 96 页PDF,太全了!纯粉丝福利,非广 ...
- 从追MM谈23种设计模式
从追MM谈Java的23种设计模式 1.FACTORY-追MM少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西,虽然口味有所不同,但不管你带MM去麦当劳或肯德基,只管向服务员说&quo ...
- 【设计模式】Java 23种设计模式对比总结
一.设计模式的分类 创建型模式,共五种(1-5):工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种(6-12):适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组 ...
- java 的23种设计模式 单例模式
23种设计模式友情链接: 点击打开链接 单例模式: A.饿汉式单例模式 具体步骤: 1.声明一个私有的静态的最终的本类类型的对象并实例化 private static final Person ins ...
- 23种设计模式C++实现UML+源码汇总
设计模式-汇总 代码开源仓库地址 23种设计模式C++实现 C++中的开闭原则使用C++多态功能实现附源码 C++基于多态实现依赖颠倒原则附源码 C++ 的静态成员变量为什么一定要在类外定义 23种设 ...
- 23种设计模式C++源码与UML实现--外观模式
外观模式 facade模式也叫外观模式,是由GoF提出的23种设计模式中的一种,facade模式为一组具有类似功能的类群,比如类库,子系统等等,提供一个一致的简单界面.这个一致的简单的界面被称为fac ...
- 23种设计模式C++源码与UML实现--建造者模式
建造者模式 代码仓库地址:建造者模式 Builder模式也叫建造者模式或者生成器模式,是由GoF提出的23种设计模式中的一种.Builder模式是一种对象创建模式之一,用来隐藏复合对象的创建过程.他把 ...
- 【java】java开发中的23种设计模式详解
设计模式(Design Patterns) --可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...
- java 23种设计模式及具体例子 收藏有时间慢慢看
设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代 码可靠性. 毫无疑问,设计模式 ...
最新文章
- 进入docker容器之后,找不到rosbag命令(ros已经安装)
- Thread-方法以及wait、notify简介
- gevent+django并发资料调研
- 基于JindoFS+OSS构建高效数据湖
- (46)HTML网页开发流程
- 微服务升级_SpringCloud Alibaba工作笔记0021---Nacos之DataId配置
- python后台架构Django教程——连接读写mysql数据库
- 浅谈内联元素inline
- 电脑练习打字软件_极速字根练习软件电脑版下载-极速字根练习软件免费版v0.1 最新版...
- 智慧体育训练考核系统软件-智能体能综合测评系统
- cad解除块的快捷命令_47个快捷键50个CAD技巧,快收藏起来
- 使用easypoi导出excel设置表头样式
- 路由器芯片和服务器,软路由就是软路由,还是回归它本该有的身份吧。一个越折腾越迷茫者的经历...
- 2022年阿里云双12服务器活动内容规则汇总及价格表
- Android 11 内置原生壁纸!速度来取
- Python基于imageio库制作gif动图
- 三星android se干啥得,结果竟然这样!看iPhone SE与最强Android旗舰三星S7对比!
- 临河三中宏志班2021年高考成绩查询,临河三中名教师简介一
- Spark-000讲:What is Spark ?
- 耶鲁大学Michel H. Devoret教授荣获2021年“墨子量子奖”
热门文章
- 单片机中灯泡显示miss_基于单片机的交通灯计 参考文献1.doc
- 南京邮电大学电工电子(数电)实验报告——周期信号的频谱分析 连续时间系统模拟
- 【go语言阻塞唤醒底层实现之sync_runtime_SemacquireMutex和runtime_Semrelease】
- 中软国际有限公司c语言笔试,【求助】中软国际C++程序员笔试题
- 计算机组成原理-电大mooc
- 如果金融男和IT男同时追你,你选谁?女孩的回答80%的人猜错了
- Spring boot 实现 Elasticsearch 动态创建索引
- elasticsearch集群搭建
- java原生Excel单元格合并自定义导出
- 计算机主板优缺点,不同的电脑主板有什么区别 不同类型主板优缺点介绍[多图]...