1、设计模式七大原则
1、目的
编写软件过程中,程序员面临着来自 耦合性,内聚性以及可维护性,可扩展性,重 用性,灵活性 等多方面的挑战,设计模式是为了让程序(软件),具有更好
(1)代码重用性 (即:相同功能的代码,不用多次编写) 。
(2) 可读性 (即:编程规范性, 便于其他程序员的阅读和理解) 。
(3)可扩展性 (即:当需要增加新的功能时,非常的方便,称为可维护) 。
(4)可靠性 (即:当我们增加新的功能后,对原来的功能没有影响)。
(5)使程序呈现高内聚,低耦合的特性。
分享金句:
设计模式包含了面向对象的精髓,“懂了设计模式,你就懂了面向对象分析和设计 (OOA/D)的精要。” Scott Mayers 在其巨著《Effective C++》就曾经说过:C++老手和 C++新手的区别就是 前者手背上有很多伤疤。
2、七大原则
设计模式原则,其实就是程序员在编程时,应当遵守的原则,也是各种设计模 式的基础(即:设计模式为什么这样设计的依据)。
设计模式常用的七大原则有:
(1)单一职责原则
(2)接口隔离原则
(3)依赖倒转(倒置)原则
(4)里氏替换原则
(5)开闭原则
(6)迪米特法则
(7)合成复用原则
2、单一职责原则
1、基本介绍
对类来说的,即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。 当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为 类A1,类A2。
2、细节
(1)低类的复杂度,一个类只负责一项职责。
(2)提高类的可读性,可维护性。
(3)降低变更引起的风险。
(4)通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违 反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则。
3、代码
com/atguigu/principle/singleresponsibility/SingleResponsibility1.java
3、接口隔离原则(Interface Segregation Principle)
1、介绍
客户端不应该依赖它不需要的接 口,即一个类对另一个类的依赖 应该建立在最小的接口上。
2、接口隔离原则
(1)类A通过接口Interface1依赖类B,类C通过接口Interface1依赖类D,如果接口 Interface1对于类A和类C来说不是最小接口,那么类B和类D必须去实现他们不 需要的方法。
(2)将接口Interface1拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
(3)接口Interface1中出现的方法,根据实际情况拆分为三个接口。
3、代码
com/atguigu/principle/segregation/Segregation1.java
4、依赖倒转原则(Dependence Inversion Principle)
1、介绍
(1)高层模块不应该依赖低层模块,二者都应该依赖其抽象。
(2)抽象不应该依赖细节,细节应该依赖抽象。
(3)依赖倒转(倒置)的中心思想是面向接口编程。
(4)依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的 多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象 指的是接口或抽象类,细节就是具体的实现类。
(5)使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的 任务交给他们的实现类去完成。
2、细节
(1)低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好。
(2)变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在 一个缓冲层,利于程序扩展和优化。
(3)继承时遵循里氏替换原则。
2、代码
com/atguigu/principle/inversion/DependecyInversion.java
5、里氏替换原则(Liskov Substitution Principle)
1、介绍
继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契 约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实 现的方法任意修改,就会对整个继承体系造成破坏。
继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承, 则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子 类的功能都有可能产生故障
麻省理工学院的里女士提出的里氏替换原则(Liskov Substitution Principle)如下:
如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序 P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1 的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。
以上的里氏替换原则告诉我们,在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法,同时也告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题。
2、代码
com/atguigu/principle/liskov/improve/Liskov.java
6、开闭原则(Open Closed Principle)
1、介绍
(1)一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用 方)。用抽象构建框架,用实现扩展细节。
(2)当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已 有的代码来实现变化。
(3) 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。
2、案例
画图形的功能。
使用开闭原则的思路:
把创建Shape类做成抽象类,并提供一个抽象的draw方法,让子类去实现即可, 这样当我们有新的图形种类时,只需要让新的图形类继承Shape,并实现draw方法即可, 使用方的代码就不需要修该,即满足了开闭原则。
3、代码
com/atguigu/principle/ocp/Ocp.java
7、迪米特法则(Demeter Principle)
1、介绍
(1)一个对象应该对其他对象保持最少的了解。
(2)类与类关系越密切,耦合度越大。
(3)迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的 越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内 部。对外除了提供的public 方法,不对外泄露任何信息。
(4)迪米特法则还有个更简单的定义:只与直接的朋友通信。
(5)直接的朋友
每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系, 我们就说这两个对象之间是朋友关系。
耦合的方式很多,依赖,关联,组合,聚合 等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而 出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量 的形式出现在类的内部。
2、细节
(1)迪米特法则的核心是降低类之间的耦合。
(2)由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系, 并不是要求完全没有依赖关系。
3、代码
com/atguigu/principle/demeter/improve/Demeter1.java
8、合成复用原则(Composite Reuse Principle)
设计原则核心思想:
(1)找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
(2)针对接口编程,而不是针对实现编程。
(3)为了交互对象之间的松耦合设计而努力。
2、设计模式概述和分类
1、掌握设计模式的层次
(1)刚开始学编程不久,听说过什么是设计模式
(2)有很长时间的编程经验,自己写了很多代码,其中用到了设计模式,但 是自己却不知道
(3)学习过了设计模式,发现自己已经在使用了,并且发现了一些新的模式 挺好用的
(4)阅读了很多别人写的源码和框架,在其中看到别人设计模式,并且能够 领会设计模式的精妙和带来的好处。
(5)代码写着写着,自己都没有意识到使用了设计模式,并且熟练的写了出来。
2、概述
(1)设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验, 模式不是代码,而是某类问题的通用解决方案,设计模式(Design pattern) 代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时 间的试验和错误总结出来的。
(2)设计模式的本质提高 软件的维护性,通用性和扩展性,并降低软件的复杂度。
3、类型
(1)创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。
(2)结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
(3)行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模 式、策略模式、职责链模式(责任链模式)。
3、适配器模式
1、介绍
1、概述
适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper) 。
适配器模式属于结构型模式 ,主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。
2、工作原理
从用户的角度看不到被适配者,是解耦的。用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法。
用户收到反馈结果,感觉只是和目标接口交互,如图:
3、使用介绍
Adapter类,通过继承 src类(被适配者),实现 dst 类(目标类)接口,完成src->dst的适配。
2、案例
需求
以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter,220V交流电 相当于src (即被适配者),我们的目dst(即目标)是5V直流电,使用对象适配器模式完成。
1、被适配的类
package com.atguigu.adapter.objectadapter;
/**
* 对象适配器-被适配的类
*/
public class Voltage220V {
//输出220V的电压,不变
public int output220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src;
}
}
2、适配接口
package com.atguigu.adapter.objectadapter;
//适配接口
public interface IVoltage5V {
public int output5V();
}
3、适配器类
package com.atguigu.adapter.objectadapter;
/**
* 持有src类的实例,以解决兼容性的问题。 即:持有 src类,实现 dst 类接口,
* 完成src->dst的适配
*/
//适配器类 拿到220v电压,变成5V
public class VoltageAdapter implements IVoltage5V {
//持有src类的实例
private Voltage220V voltage220V; // 关联关系-聚合
//通过构造器,传入一个 Voltage220V 实例
public VoltageAdapter(Voltage220V voltage220v) {
this.voltage220V = voltage220v;
}
@Override
public int output5V() {
int dst = 0;
if (null != voltage220V) {
int src = voltage220V.output220V();//获取220V 电压
System.out.println("使用对象适配器,进行适配~~");
dst = src / 44;
System.out.println("适配完成,输出的电压为=" + dst);
}
return dst;
}
}
4、目标使用类
package com.atguigu.adapter.objectadapter;
public class Phone {
//充电 适配完成后。传入一个接口,依赖被适配器类Voltage220
public void charging(IVoltage5V iVoltage5V) {
if(iVoltage5V.output5V() == 5) {
System.out.println("电压为5V, 可以充电~~");
} else if (iVoltage5V.output5V() > 5) {
System.out.println("电压大于5V, 不能充电~~");
}
}
}
5、测试类-客户端
package com.atguigu.adapter.objectadapter;
public class Client {
public static void main(String[] args) {
System.out.println(" === 对象适配器模式 ====");
Phone phone = new Phone();
//完成充电任务
phone.charging(new VoltageAdapter(new Voltage220V()));
}
}
6、细节
(1)三种命名方式,是根据 src是以怎样的形式给到Adapter(在Adapter里的形式)来 命名的。
(2)对象适配器:以对象给到,在Adapter里,将src作为一个对象,持有。
4、装饰着模式
1、介绍
(1)概述
装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更 有弹性,装饰者模式也体现了开闭原则(ocp)
(2)原理
装饰者模式就像打包一个快递。
主体:比如:陶瓷、衣服 (Component) // 被装饰者。
包装:比如:报纸填充、塑料泡沫、纸板、木板(Decorator)。
ConcreteComponent:具体的主体, 比如前面的各个单品咖啡。
在如图的Component与ConcreteComponent之间,如果 ConcreteComponent类很多,还可以设计一个缓冲层,将共有的部分提取出来, 抽象层一个类。
2、案例
装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
1、Drink类
package com.atguigu.decorator;
/**
* 装饰者模式
*/
public abstract class Drink {
public String des; // 描述
private float price = 0.0f; //价格
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
//核心方法-计算费用的抽象方法
//子类(这样才可以计算出价格)来实现
public abstract float cost();
}
2、coffer类
public class Coffee extends Drink {
//单品咖啡就是它的价格
@Override
public float cost() {
return super.getPrice();
}
}
3、单品咖啡-LongBlack
public class LongBlack extends Coffee {
public LongBlack() {
setDes(" longblack ");
setPrice(5.0f);
}
}
4、装饰者
//装饰者
public class Decorator extends Drink {
//将接口继承和组合
private Drink obj;
public Decorator(Drink obj) {
//组合
this.obj = obj;
}
@Override
public float cost() {
// getPrice 自己价格。将单品咖啡的架构也要统计进去
return super.getPrice() + obj.cost();
}
@Override
public String getDes() {
// obj.getDes() 输出被装饰者的信息
return super.des + " " + getPrice() + " && " + obj.getDes();
}
}
5、具体装饰者
//具体的Decorator, 这里就是调味品
public class Chocolate extends Decorator {
public Chocolate(Drink obj) {
super(obj);
setDes(" 巧克力 ");
setPrice(3.0f); // 调味品 的价格
}
}
public class Milk extends Decorator {
public Milk(Drink obj) {
super(obj);
setDes(" 牛奶 ");
setPrice(2.0f);
}
}
6、客户端测试
public class Client {
public static void main(String[] args) {
//装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
//1、点一份LongBlack
Drink order = new LongBlack();
System.out.println("费用1:" + order.cost());
System.out.println("描述:" + order.getDes());
//2、order中加入一份牛奶
order = new Milk(order);
//3、order中加入一份巧克力
order = new Chocolate(order);
//4、order中再加入一份巧克力
order = new Chocolate(order);
// 13 = 5+ 2 + 3 + 3
System.out.println("总费用:" + order.cost());
//巧克力 3.0 && 巧克力 3.0 && 牛奶 2.0 && longblack
System.out.println("描述:" + order.getDes());
}
}
5、组合模式
1、介绍
1、概述
组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结 构,将对象组合成树状结构以表示“整体-部分”的层次关系。
组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象
2、原理图
(1)Component :这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component 子部件, Component 可以是抽象类或者接口。
(2)Leaf:在组合中表示叶子节点,该节点没有子节点。
(3)composite:非叶子节点,用于存储子部件,在component中实现子部件的相关操作,比如增加,删除等。
3、解决的问题
当我们的要处理的对象可以生成一颗树形结构,而 我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑 它是节点还是叶子。
2、案例
1、需求
展示一个学校院系结构,在一个页面中展示出学校的院系组 成,一个学校有多个学院,一个学院有多个系。
2、代码
(1)组合接口类
package com.atguigu.composite;
/**
* 组合模式
* 组织
*/
public abstract class OrganizationComponent {
private String name; // 名字
private String des; // 说明
//添加
protected void add(OrganizationComponent organizationComponent) {
//默认实现
throw new UnsupportedOperationException();
}
protected void remove(OrganizationComponent organizationComponent) {
//默认实现
throw new UnsupportedOperationException();
}
//构造器
public OrganizationComponent(String name, String des) {
super();
this.name = name;
this.des = des;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
//方法print, 做成抽象的, 子类都需要实现
protected abstract void print();
}
(2)大学类
package com.atguigu.composite;
import java.util.ArrayList;
import java.util.List;
//University 就是 Composite , 可以管理College
public class University extends OrganizationComponent {
List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();
// 构造器
public University(String name, String des) {
super(name, des);
}
// 重写add
@Override
protected void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}
// 重写remove
@Override
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
// print方法,就是输出University 包含的学院
@Override
protected void print() {
System.out.println("--------------" + getName() + "--------------");
//遍历 organizationComponents(list集合中存放的是college,会调用college的print()方法)
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
}
(3)学院类
package com.atguigu.composite;
import java.util.ArrayList;
import java.util.List;
//学院-Composite
public class College extends OrganizationComponent {
//List 中 存放的Department
List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();
// 构造器
public College(String name, String des) {
super(name, des);
}
// 重写add
@Override
protected void add(OrganizationComponent organizationComponent) {
//将来实际业务中,college的add和university的add不一定一样
organizationComponents.add(organizationComponent);
}
// 重写remove
@Override
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
// print方法,就是输出University 包含的学院
@Override
protected void print() {
System.out.println("--------------" + getName() + "--------------");
//遍历 organizationComponents 存放的是department,会调用department的print()方法
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
}
(4)专业系类
package com.atguigu.composite;
//系类-叶子节点leaf
public class Department extends OrganizationComponent {
//没有集合
public Department(String name, String des) {
super(name, des);
}
//add , remove 就不用写了,因为他是叶子节点
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
@Override
protected void print() {
System.out.println(getName());
}
}
(5)客户端
package com.atguigu.composite;
public class Client {
public static void main(String[] args) {
//从大到小创建对象 学校
OrganizationComponent university = new University("清华大学", " 中国顶级大学 ");
//创建学院
OrganizationComponent computerCollege = new College("计算机学院", " 计算机学院 ");
OrganizationComponent infoEngineercollege = new College("信息工程学院", " 信息工程学院 ");
//创建各个学院下面的系(专业)
computerCollege.add(new Department("软件工程", " 软件工程不错 "));
computerCollege.add(new Department("网络工程", " 网络工程不错 "));
infoEngineercollege.add(new Department("通信工程", " 通信工程不好学 "));
infoEngineercollege.add(new Department("信息工程", " 信息工程好学 "));
//将学院加入到 学校
university.add(computerCollege);
university.add(infoEngineercollege);
//打印输出
university.print();
// computerCollege.print();
}
}
控制台输出:
--------------清华大学--------------
--------------计算机学院--------------
软件工程
网络工程
--------------信息工程学院--------------
通信工程
信息工程
3、细节
1、方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点 或者叶子从而创建出复杂的树形结构。
2、需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式。
3、要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性 都不一样,不适合使用组合模式。
6、外观模式(Facade)
1、介绍
1、概述
外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供 一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节。
2、原理类图
(1)外观类(Facade): 为调用端提供统一的调用接口, 外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当 子系统对象。
(2)调用者(Client): 外观接口的调用者。
(3)子系统的集合:指模块或者子系统,处理Facade 对象指派的任务,他是功能的实际提供者。
3、外观模式解决影院管理
外观模式可以理解为转换一群接口,客户只 要调用一个接口,而不用调用多个接口才能 达到目的。比如:手机的重启功能 (把关机和启动合为一个操作)。
2、案例
1、需求
组建一个家庭影院:
DVD播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的
功能,其过程为:
• 直接用遥控器:统筹各设备开关
• 开爆米花机
• 放下屏幕
• 开投影仪
• 开音响
• 开DVD,选dvd
• 去拿爆米花
• 调暗灯光
• 播放
• 观影结束后,关闭各种设备
2、代码
(1)DVDPlayer
package com.atguigu.facade;
public class DVDPlayer {
//使用单例模式, 使用饿汉式
private static DVDPlayer instance = new DVDPlayer();
public static DVDPlayer getInstanc() {
return instance;
}
public void on() {
System.out.println(" dvd on ");
}
public void off() {
System.out.println(" dvd off ");
}
public void play() {
System.out.println(" dvd is playing ");
}
public void pause() {
System.out.println(" dvd pause ..");
}
}
(2)爆米花Popcorn
package com.atguigu.facade;
//爆米花
public class Popcorn {
private static Popcorn instance = new Popcorn();
public static Popcorn getInstance() {
return instance;
}
public void on() {
System.out.println(" popcorn on ");
}
public void off() {
System.out.println(" popcorn ff ");
}
public void pop() {
System.out.println(" popcorn is poping ");
}
}
(3)外观类
package com.atguigu.facade;
//中间做一个外观类
public class HomeTheaterFacade {
//定义各个子系统对象
private TheaterLight theaterLight;
private Popcorn popcorn;
private Stereo stereo;
private Projector projector;
private Screen screen;
private DVDPlayer dVDPlayer;
//构造器
public HomeTheaterFacade() {
super(); //获取实例
this.theaterLight = TheaterLight.getInstance();
this.popcorn = Popcorn.getInstance();
this.stereo = Stereo.getInstance();
this.projector = Projector.getInstance();
this.screen = Screen.getInstance();
this.dVDPlayer = DVDPlayer.getInstanc();
}
//操作分成 4 步
public void ready() {
popcorn.on();
popcorn.pop();
screen.down();
projector.on();
stereo.on();
dVDPlayer.on();
theaterLight.dim();
}
public void play() {
dVDPlayer.play();
}
public void pause() {
dVDPlayer.pause();
}
public void end() {
popcorn.off();
theaterLight.bright();
screen.up();
projector.off();
stereo.off();
dVDPlayer.off();
}
}
(4)调用者client
public class Client {
public static void main(String[] args) {
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
//拿到外观类,有选择地调用方法
homeTheaterFacade.ready();
homeTheaterFacade.play();
homeTheaterFacade.end();
}
}
3、细节
(1)外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性。
(2)外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展。
(3)通过合理的使用外观模式,可以帮我们更好的划分访问的层次。
(4)当系统需要进行分层设计时,可以考虑使用Facade模式。
(5)在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时 可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口, 让新系统与Facade类交互,提高复用性。
(6)不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。 要以让系统有层次,利于维护为目的。
7、享元模式
1、介绍
1、概述
享元模式(Flyweight Pattern) 也叫 蝇量模式: 运 用共享技术有效地支持大量细粒度的对象。
常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个。
享元模式能够解决重复对象的内存浪费的问题, 当系统中有大量相似对象,需要缓冲池时。不需 总是创建新对象,可以从缓冲池里拿。这样可以 降低系统内存,同时提高效率。
2、原理类图
(1)FlyWeight:抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态和内部状态的接口或实现。
(2)ConcreteFlyWeight:具体的享元角色,是具体的产品类,实现抽象角色定义相关业务。
(3)UnsharedConcreteFlyweight:不可共享的角色。一般不会出现在共享工厂中。
(4)FlyweightFactory:共享工厂类,用于创建一个池容量(集合),同时提供从池中获取对象的方法。
3、内/外部状态
享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态 了,即将对象的信息分为两个部分:内部状态和外部状态。
(1)内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变。
(2)外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
比如围棋,它们都有大量的棋子对象跳棋颜色多一 点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后, 落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态。
2、案例
1、需求
小型的外包项目,给客户A做一个产品展示网站,客户A的朋友也希望做这样的产品展示网站,但是要求都有些不同:
(1)有客户要求以新闻的形式发布
(2)有客户人要求以博客的形式发布
2、代码
(1)WebSite抽象类
public abstract class WebSite {
public abstract void use(User user);//抽象方法
}
(2)具体网站
public class ConcreteWebSite extends WebSite {
//共享的部分,内部状态
private String type = ""; //网站发布的形式(类型)
//构造器-将具体的网站类型传进来
public ConcreteWebSite(String type) {
this.type = type;
}
@Override
public void use(User user) {
System.out.println("网站的发布形式为:" + type + " 在使用中 .. 使用者是" + user.getName());
}
}
(3)用户-外部状态
public class User {
private String name;
public User(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
(4)客户端
public class Client {
public static void main(String[] args) {
// 创建一个工厂类
WebSiteFactory factory = new WebSiteFactory();
// 客户要一个以新闻形式发布的网站
WebSite webSite1 = factory.getWebSiteCategory("新闻");
webSite1.use(new User("tom"));
// 客户要一个以博客形式发布的网站
WebSite webSite2 = factory.getWebSiteCategory("博客");
webSite2.use(new User("jack"));
System.out.println("网站的分类共=" + factory.getWebSiteCount());
}
}
3、细节
(1)在享元模式这样理解,“享”就表示共享,“元”表示对象。
(2)系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时, 我们就可以考虑选用享元模式。
(3)用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable存储。
(4)享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率。
(5)享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有 固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方。
(6)使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
(7)享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池。