目录
五、23 种设计模式
1、单例模式
1.1、单例模式的定义与特点
1.2、单例模式的优点和缺点
1.3、单例模式的应用场景
1.4、单例模式的结构与实现
1.5、八种方式详解
1.6、单例模式在JDK应用中的源码分析
1.7、单例模式注意事项和细节说明
2、工厂模式
2.1、简单工厂模式
2.2、工厂方法模式
2.3、抽象工厂模式
3、原型模式
3.1、原型模式的定义与特点
3.2、原型模式的结构与实现
3.3、原型模式的应用场景
3.4、原型模式的扩展
3.5、应用实例
3.6、原型模式在Spring框架中源码分析
3.7、浅拷贝与深拷贝
3.8、原型模式的注意事项的细节
4、建造者模式
4.1、模式的定义与特点
4.2、模式的结构与实现
4.3、盖房项目需求
4.4、建造者模式在JDK中应用以及源码解析
4.5、建造者模式的注意事项和细节
5、适配器模式
5.1、模式的定义与特点
5.2、模式的结构与实现
5.3、类适配器模式
5.4、对象适配器模式
5.5、接口适配器模式
5.6、适配器在SpringMVC框架应用以及源码分析
5.7、适配器模式的注意事项和细节
6、桥接模式
6.1、桥接模式的定义与特点
6.2、桥接模式的结构与实现
6.3、桥接模式在 JDBC的源码剖析
6.4、注意事项
6.5、桥接模式其他应用场景
7、装饰模式
7.1、装饰模式的定义与特点
7.2、装饰模式的结构与实现
7.3、装饰者模式(Decorator)原理
7.4、装饰者模式在JDK中源码分析
8、组合模式
8.1、组合模式的定义与特点
8.2、组合模式的结构与实现
8.3、组合模式在JDK集合的源码分析
8.4、组合模式的注意细节和事项
9、外观模式
9.1、外观模式的定义与特点
9.2、外观模式的结构与实现
9.3、外观模式在Mybatis框架应用的源码分析
9.4、外观模式的注意事项和细节
10、享元模式
10.1、享元模式的定义与特点
10.2、享元模式的结构与实现
10.3、享元模式在JDK-Interger的应用源码分析
10.4、享元模式注意事项和细节
单例(Singleton)模式的定义:就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。
单例模式在现实生活中的应用也非常广泛,例如公司 CEO、部门经理等都属于单例模型。J2EE 标准中的 ServletContext 和 ServletContextConfig、Spring 框架应用中的 ApplicationContext、数据库中的连接池、Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够了,这时就会使用到单例模式。等也都是单例模式。
单例模式有 3 个特点:
单例模式的优点:
单例模式的缺点:
单例模式看起来非常简单,实现起来也非常简单。单例模式在面试中是一个高频面试题。希望大家能够认真学习,掌握单例模式,提升核心竞争力,给面试加分,顺利拿到 Offer。
对于 Java 来说,单例模式可以保证在一个 JVM 中只存在单一实例。单例模式的应用场景主要有以下几个方面。
单例模式是设计模式中最简单的模式之一。通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。
单例模式有八种方式:
1. 饿汉式(静态常量);
2. 饿汉式(静态代码块);
3. 懒汉式(线程不安全);
4. 懒汉式(线程安全,同步方法);
5. 懒汉式(线程安全,同步代码块);
6. 双重检查;
7. 静态内部类;
8. 枚举
1)、饿汉式(静态常量)
代码演示:
public class SingletonTest01 {
public static void main(String[] args) {
Singleton01 instance = Singleton01.getInstance();
Singleton01 instance2 = Singleton01.getInstance();
System.out.println(instance == instance2);
System.out.println("SingletonTest01-instance.hashcode()="+instance.hashCode());
System.out.println("SingletonTest01-instance2.hashCode()="+instance2.hashCode());
}
}
// 饿汉式(静态常量)
class Singleton01 {
// 1、构造器私有化,外部不能new
private Singleton01() {
}
// 2、本类内部创建对象实例
private final static Singleton01 instance = new Singleton01();
// 3、提供一个公有的静态方法,返回实例对象
public static Singleton01 getInstance() {
return instance;
}
}
优缺点说明:
2)、饿汉式(静态代码块)
代码演示:
public class SingletonTest02 {
public static void main(String[] args) {
Singleton02 instance = Singleton02.getInstance();
Singleton02 instance2 = Singleton02.getInstance();
System.out.println(instance == instance2);
System.out.println("SingletonTest02-instance.hashcode()="+instance.hashCode());
System.out.println("SingletonTest02-instance2.hashCode()="+instance2.hashCode());
}
}
// 饿汉式(静态代码块)
class Singleton02 {
//1、构造器私有化,外部不能new
private Singleton02() {
}
// 2、静态代码块
private static Singleton02 instance;
static {
instance = new Singleton02();
}
// 3、提供一个公有的静态方法,返回实例对象
public static Singleton02 getInstance() {
return instance;
}
}
结果:
优缺点说明:
3)、懒汉式(线程不安全)
代码演示:
public class SingletonTest03 {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2);
System.out.println("SingletonTest03-instance.hashcode()="+instance.hashCode());
System.out.println("SingletonTest03-instance2.hashCode()="+instance2.hashCode());
}
}
// 懒汉式(线程不安全)
class Singleton {
// 定义变量添加final必须初始化,因为这里没有初始化,所以不需要添加final
private static Singleton instance;
private Singleton() {
}
// 提供一个静态的公有方法,当使用到该方法时,才去创建instance
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
结果:
优缺点说明:
4)、懒汉式(线程安全,同步方法)
代码演示:
public class SingletonTest04 {
public static void main(String[] args) {
Singleton04 instance = Singleton04.getInstance();
Singleton04 instance2 = Singleton04.getInstance();
System.out.println(instance == instance2);
System.out.println("SingletonTest04-instance.hashcode()="+instance.hashCode());
System.out.println("SingletonTest04-instance2.hashCode()="+instance2.hashCode());
}
}
// 懒汉式(线程安全,同步方法)
class Singleton04 {
private static Singleton04 instance;
private Singleton04() {
}
// 提供一个静态的公有方法,加入同步处理的代码(synchronized),解决线程安全问题
public static synchronized Singleton04 getInstance() {
if (instance == null) {
instance = new Singleton04();
}
return instance;
}
}
结果:
优缺点说明:
5)、懒汉式(线程安全,同步代码块)
代码演示:
public class SingletonTest05 {
public static void main(String[] args) {
Singleton05 instance = Singleton05.getInstance();
Singleton05 instance2 = Singleton05.getInstance();
System.out.println(instance == instance2);
System.out.println("SingletonTest05-instance.hashcode()="+instance.hashCode());
System.out.println("SingletonTest05-instance2.hashCode()="+instance2.hashCode());
}
}
// 懒汉式(线程安全,同步代码块)
class Singleton05 {
private static Singleton05 instance;
private Singleton05() {
}
public static Singleton05 getInstance() {
if (instance == null) {
synchronized (Singleton05.class) {
instance = new Singleton05();
}
}
return instance;
}
}
结果:
优缺点说明:
6)、双重检查
代码演示:
public class SingletonTest06 {
public static void main(String[] args) {
Singleton06 instance = Singleton06.getInstance();
Singleton06 instance2 = Singleton06.getInstance();
System.out.println(instance == instance2);
System.out.println("SingletonTest06-instance.hashcode()="+instance.hashCode());
System.out.println("SingletonTest06-instance2.hashCode()="+instance2.hashCode());
}
}
//双重检查
class Singleton06 {
private static volatile Singleton06 instance;
private Singleton06() {
}
// 提供一个静态的公有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题
public static synchronized Singleton06 getInstance() {
if (instance == null) {
synchronized (Singleton06.class) {
if (instance == null) {
instance = new Singleton06();
}
}
}
return instance;
}
}
结果:
优缺点说明:
7)、静态内部类
代码演示:
public class SingletonTest07 {
public static void main(String[] args) {
Singleton07 instance = Singleton07.getInstance();
Singleton07 instance2 = Singleton07.getInstance();
System.out.println(instance == instance2);
System.out.println("SingletonTest07-instance.hashcode()="+instance.hashCode());
System.out.println("SingletonTest07-instance2.hashCode()="+instance2.hashCode());
}
}
// 静态内部类
class Singleton07 {
private Singleton07() {
}
// 写一个静态内部类,该类中有一个静态属性Singleton
private static class singleInstance {
private static final Singleton07 INSTANCE = new Singleton07();
}
//提供一个静态的公有方法,直接返回对象
public static synchronized Singleton07 getInstance() {
return singleInstance.INSTANCE;
}
}
结果:
优缺点说明:
8)、枚举
代码演示:
public class SingletonTest08 {
public static void main(String[] args) {
Singleton08 instance = Singleton08.INSTANCE;
Singleton08 instance2 = Singleton08.INSTANCE;
System.out.println(instance == instance2);
System.out.println("SingletonTest08-instance.hashcode()="+instance.hashCode());
System.out.println("SingletonTest08-instance2.hashCode()="+instance2.hashCode());
instance.sayOK();
}
}
// 使用枚举,可以实现单例
enum Singleton08 {
INSTANCE;//属性
public void sayOK() {
System.out.println("ok");
}
}
结果:
优缺点说明:
1) 我们JDK中,java.lang.Runtime就是经典的单例模式(饿汉式)
2) 代码分析+Debug源码+代码说明
现实生活中,原始社会自给自足(没有工厂),农耕社会小作坊(简单工厂,民间酒坊),工业革命流水线(工厂方法,自产自销),现代产业链代工厂(抽象工厂,富士康)。我们的项目代码同样是由简到繁一步一步迭代而来的,但对于调用者来说,却越来越简单。
在日常开发中,凡是需要生成复杂对象的地方,都可以尝试考虑使用工厂模式来代替。
注意:上述复杂对象指的是类的构造函数参数过多等对类的构造有影响的情况,因为类的构造过于复杂,如果直接在其他业务类内使用,则两者的耦合过重,后续业务更改,就需要在任何引用该类的源代码内进行更改,光是查找所有依赖就很消耗时间了,更别说要一个一个修改了。
工厂模式的定义:定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。
按实际业务场景划分,工厂模式有 3 种不同的实现方式,分别是简单工厂模式、工厂方法模式和抽象工厂模式。
我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”。
在简单工厂模式中创建实例的方法通常为静态(static)方法,因此简单工厂模式(Simple Factory Pattern)又叫作静态工厂方法模式(Static Factory Method Pattern)。
简单来说,简单工厂模式有一个具体的工厂类,可以生成多个不同的产品,属于创建型设计模式。简单工厂模式不在 GoF 23 种设计模式之列。
简单工厂模式每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度,违背了“开闭原则”。
“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。
1)、优点和缺点
优点:
缺点:
应用场景
对于产品种类相对较少的情况,考虑使用简单工厂模式。使用简单工厂模式的客户端只需要传入工厂类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。
2)、模式的结构与实现
简单工厂模式的主要角色如下:
其结构图如下图所示。
根据上图写出该模式的代码如下:
public class Client {
public static void main(String[] args) {
}
//抽象产品
public interface Product {
void show();
}
//具体产品:ProductA
static class ConcreteProduct1 implements Product {
public void show() {
System.out.println("具体产品1显示...");
}
}
//具体产品:ProductB
static class ConcreteProduct2 implements Product {
public void show() {
System.out.println("具体产品2显示...");
}
}
final class Const {
static final int PRODUCT_A = 0;
static final int PRODUCT_B = 1;
static final int PRODUCT_C = 2;
}
// 简单工厂
static class SimpleFactory {
public static Product makeProduct(int kind) {
switch (kind) {
case Const.PRODUCT_A:
return new ConcreteProduct1();
case Const.PRODUCT_B:
return new ConcreteProduct2();
}
return null;
}
}
}
3)、应用实例
看一个披萨的项目:要便于披萨种类的扩展,要便于维护。
使用传统的方式完成
// 把Pizza类做成抽象类
public abstract class Pizza {
private String name;//名字
// 准备原材料,不同的披萨不一样,因此,我们做成抽象方法
public abstract void prepare();
public void bake() {
System.out.println(name + " baking");
}
public void cut() {
System.out.println(name + " cutting");
}
public void box() {
System.out.println(name + " boxing");
}
public void setName(String name) {
this.name = name;
}
}
public class GreekPizza extends Pizza {
@Override
public void prepare() {
System.out.println("给GreekPizza准备原材料");
}
}
public class CheesePizza extends Pizza {
@Override
public void prepare() {
System.out.println("给CheessPizza准备原材料");
}
}
public class OrderPizza {
// 构造方法
public OrderPizza() {
Pizza pizza = null;
String orderType;//订购披萨的类型
do {
orderType = getType();
if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName("GreekPizza");
} else if (orderType.equals("cheese")) {
pizza = new CheesePizza();
pizza.setName("CheessPizza");
} else {
break;
}
// 输出pizza制作过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} while (true);
}
// 写一个方法,可以获取客户希望订购的披萨种类
private String getType() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza type:");
String str = null;
str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
//相当于客户端
public class OrderStore {
public static void main(String[] args) {
OrderPizza orderPizza = new OrderPizza();
}
}
结果:
传统方式的优缺点:
改进的思路分析:
简单工厂模式介绍
简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式。
简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为。
在软件开发中,当我们会用到大量的创建某种、某类或者谋批对象时,就会使用到工厂模式。
简单工厂模式的设计方案:定义一个可以实例化Pizza对象的类,封装创建对象的代码。
// 简单工厂类
// 简单工厂模式,也叫静态工厂模式,可以用static修饰该方法
public class SimpleFactory {
// 根据orderType返回对应的pizza对象。
public Pizza createPizza(String orderType) {
Pizza pizza = null;
if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName("GreekPizza");
} else if (orderType.equals("cheese")) {
pizza = new CheesePizza();
pizza.setName("CheessPizza");
}
return pizza;
}
}
public class OrderPizza2 {
// 定义一个简单工厂对象
SimpleFactory simpleFactory;
// 构造器
public OrderPizza2(SimpleFactory simpleFactory) {
setFactory(simpleFactory);
}
Pizza pizza = null;
public void setFactory(SimpleFactory simpleFactory) {
String orderType = ""; //用户输入的
this.simpleFactory = simpleFactory; //设置简单工厂对象
do {
orderType = getType();
pizza = this.simpleFactory.createPizza(orderType);
// 输出pizza制作过程
if (pizza != null) { // 订购成功
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("订购失败");
break;
}
} while (true);
}
// 写一个方法,可以获取客户希望订购的披萨种类
private String getType() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza type:");
String str = null;
str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
// 相当于客户端
public class PizzaStore2 {
public static void main(String[] args) {
OrderPizza2 orderPizza = new OrderPizza2(new SimpleFactory());
}
}
结果:
在现实生活中社会分工越来越细,越来越专业化。各种产品有专门的工厂生产,彻底告别了自给自足的小农经济时代,这大大缩短了产品的生产周期,提高了生产效率。同样,在软件开发中能否做到软件对象的生产和使用相分离呢?能否在满足“开闭原则”的前提下,客户随意增删或改变对软件相关对象的使用呢?这就是本节要讨论的问题。
在《简单工厂模式》一节我们介绍了简单工厂模式,提到了简单工厂模式违背了开闭原则,而“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。
优点:
缺点:
应用场景:
1)、模式的结构与实现
工厂方法模式由抽象工厂、具体工厂、抽象产品和具体产品等4个要素构成。本节来分析其基本结构和实现方法。
1. 模式的结构
工厂方法模式的主要角色如下。
2.模式的实现
该模式的代码如下:
// 抽象产品角色
public interface Moveable {
void run();
}
// 具体产品角色
public class Plane implements Moveable {
@Override
public void run() {
System.out.println("plane....");
}
}
public class Broom implements Moveable {
@Override
public void run() {
System.out.println("broom.....");
}
}
// 抽象工厂
public abstract class VehicleFactory {
abstract Moveable create();
}
// 具体工厂
public class PlaneFactory extends VehicleFactory {
public Moveable create() {
return new Plane();
}
}
public class BroomFactory extends VehicleFactory {
public Moveable create() {
return new Broom();
}
}
// 测试类
public class Test {
public static void main(String[] args) {
VehicleFactory factory = new BroomFactory();
Moveable m = factory.create();
m.run();
}
}
程序运行结果如下:
broom.....
2)、应用实例
披萨项目新的需求:客户在点披萨时,可以点不同口味的披萨,比如“北京的奶酪披萨”、“北京的胡椒披萨”或者是“伦敦的奶酪披萨”、“伦敦的胡椒披萨”。
思路一:使用简单工厂模式,创建不同的简单工厂类,比如BJPizzaSimpleFactory、LDPizzzaSimpleFactory等等,从当前这个案例来说,也是可以的,但是考虑到项目的规模,以及软件的可维护性 、可扩展性并不是特别好。
思路二:使用工厂方法模式。将披萨项目的实例化功能抽象成抽象方法,在不同口味点餐子类中具体实现。工厂方法模式定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
披萨项目新的需求:客户在点披萨时,可以点不同口味的披萨,比如“北京的奶酪披萨”、“北京的胡椒披萨”或者是“伦敦的奶酪披萨”、“伦敦的胡椒披萨”。
思路一:使用简单工厂模式,创建不同的简单工厂类,比如BJPizzaSimpleFactory、LDPizzzaSimpleFactory等等,从当前这个案例来说,也是可以的,但是考虑到项目的规模,以及软件的可维护性 、可扩展性并不是特别好。
思路二:使用工厂方法模式。将披萨项目的实例化功能抽象成抽象方法,在不同口味点餐子类中具体实现。
工厂方法模式定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
1、抽象产品
// 抽象产品
//把Pizza类做成抽象类
public abstract class Pizza {
private String name;//名字
//准备原材料,不同的披萨不一样,因此,我们做成抽象方法
public abstract void prepare();
public void bake() {
System.out.println(name + " baking");
}
public void cut() {
System.out.println(name + " cutting");
}
public void box() {
System.out.println(name + " boxing");
}
public void setName(String name) {
this.name = name;
}
}
2、具体产品
// 具体产品
public class BJCheessPizza extends Pizza {
@Override
public void prepare() {
setName("北京的CheessPizza");
System.out.println(" 北京的CheessPizza 准备原材料");
}
}
// 具体产品
public class BJPepperPizza extends Pizza {
@Override
public void prepare() {
setName("北京的Pepper");
System.out.println(" 北京的Pepper 准备原材料");
}
}
// 具体产品
public class LDCheessPizza extends Pizza {
@Override
public void prepare() {
setName("伦敦的CheessPizza");
System.out.println(" 伦敦的CheessPizza 准备原材料");
}
}
// 具体产品
public class LDPepperPizza extends Pizza {
@Override
public void prepare() {
setName("伦敦的Pepper");
System.out.println(" 伦敦的Pepper 准备原材料");
}
}
3、抽象工厂
// 抽象工厂
public abstract class OrderPizza {
//构造方法
public OrderPizza() {
Pizza pizza = null;
String orderType;//订购披萨的类型
do {
orderType = getType();
//调用方法
pizza = creatPizza(orderType);
//输出pizza制作过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} while (true);
}
//定义一个抽象方法,让各个工厂子类自己实现
abstract Pizza creatPizza(String orderType);
//写一个方法,可以获取客户希望订购的披萨种类
private String getType() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza type:");
String str = null;
str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
4、具体工厂
public class BJOrderPizza extends OrderPizza {
@Override
Pizza creatPizza(String orderType) {
Pizza pizza=null;
if (orderType.equals("Cheess")){
pizza=new BJCheessPizza();
}else if(orderType.equals("Pepper")){
pizza=new BJPepperPizza();
}
return pizza;
}
}
public class LDOrderPizza extends OrderPizza {
@Override
Pizza creatPizza(String orderType) {
Pizza pizza = null;
if (orderType.equals("Cheess")) {
pizza = new LDCheessPizza();
} else if (orderType.equals("Pepper")) {
pizza = new LDPepperPizza();
}
return pizza;
}
}
//披萨店
public class PizzaStore {
public static void main(String[] args) {
new BJOrderPizza();//创建北京各种口味的披萨
new LDOrderPizza();//创建伦敦各种口味的披萨
}
}
抽象工厂模式:与工厂方法模式不同的是,工厂方法模式中的工厂只生产单一的产品,而抽象工厂模式中的工厂生产多个产品
同种类称为同等级,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如农场里既养动物又种植物,电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。
本节要介绍的抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,图 1 所示的是海尔工厂和 TCL 工厂所生产的电视机与空调对应的关系图。
图1 电器工厂的产品等级与产品族
1)、模式的定义与特点
抽象工厂(AbstractFactory)模式的定义:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
使用抽象工厂模式一般要满足以下条件。
抽象工厂模式除了具有工厂方法模式的优点外,其他主要优点如下。
其缺点是:当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度。
2)、模式的结构与实现
抽象工厂模式同工厂方法模式一样,也是由抽象工厂、具体工厂、抽象产品和具体产品等 4 个要素构成,但抽象工厂中方法个数不同,抽象产品的个数也不同。现在我们来分析其基本结构和实现方法。
1. 模式的结构
抽象工厂模式的主要角色如下。
抽象工厂模式的结构图如图 2 所示。
图2 抽象工厂模式的结构图
2. 模式的实现
从图 2 可以看出抽象工厂模式的结构同工厂方法模式的结构相似,不同的是其产品的种类不止一个,所以创建产品的方法也不止一个。下面给出抽象工厂和具体工厂的代码。
(1) 抽象工厂:提供了产品的生成方法。
interface AbstractFactory {
public Product1 newProduct1();
public Product2 newProduct2();
}
(2) 具体工厂:实现了产品的生成方法。
class ConcreteFactory1 implements AbstractFactory {
public Product1 newProduct1() {
System.out.println("具体工厂 1 生成-->具体产品 11...");
return new ConcreteProduct11();
}
public Product2 newProduct2() {
System.out.println("具体工厂 1 生成-->具体产品 21...");
return new ConcreteProduct21();
}
}
3)、模式的应用实例
【例1】用抽象工厂模式设计农场类。
分析:农场中除了像畜牧场一样可以养动物,还可以培养植物,如养马、养牛、种菜、种水果等,所以本实例比前面介绍的畜牧场类复杂,必须用抽象工厂模式来实现。
本例用抽象工厂模式来设计两个农场,一个是韶关农场用于养牛和种菜,一个是上饶农场用于养马和种水果,可以在以上两个农场中定义一个生成动物的方法 newAnimal() 和一个培养植物的方法 newPlant()。
对马类、牛类、蔬菜类和水果类等具体产品类,由于要显示它们的图像(点此下载图片),所以它们的构造函数中用到了 JPanel、JLabel 和 ImageIcon 等组件,并定义一个 show() 方法来显示它们。
客户端程序通过对象生成器类 ReadXML 读取 XML 配置文件中的数据来决定养什么动物和培养什么植物(点此下载 XML 文件)。其结构图如图 3 所示。
// 抽象产品角色
public interface Food {
void createFood();
}
public interface Vehicle {
void createVehicle();
}
public interface Weapon {
void createWeapon();
}
// 具体产品角色
public class Apple implements Food {
@Override
public void createFood() {
System.out.println("Apple....");
}
}
public class Car implements Vehicle {
@Override
public void createVehicle() {
System.out.println("Car ....");
}
}
public class AK47 implements Weapon {
@Override
public void createWeapon() {
System.out.println("AK47 ....");
}
}
//抽象工厂类
public abstract class AbstractFactory {
public abstract Vehicle createVehicle();
public abstract Weapon createWeapon();
public abstract Food createFood();
}
//具体工厂类,其中Food,Vehicle,Weapon是抽象类,
public class DefaultFactory extends AbstractFactory{
@Override
public Food createFood() {
return new Apple();
}
@Override
public Vehicle createVehicle() {
return new Car();
}
@Override
public Weapon createWeapon() {
return new AK47();
}
}
//测试类
public class Test {
public static void main(String[] args) {
AbstractFactory f = new DefaultFactory();
Vehicle v = f.createVehicle();
v.run();
Weapon w = f.createWeapon();
w.shoot();
Food a = f.createFood();
a.printName();
}
}
1、抽象产品
//把Pizza类做成抽象类
public abstract class Pizza {
private String name;//名字
//准备原材料,不同的披萨不一样,因此,我们做成抽象方法
public abstract void prepare();
public void bake() {
System.out.println(name + " baking");
}
public void cut() {
System.out.println(name + " cutting");
}
public void box() {
System.out.println(name + " boxing");
}
public void setName(String name) {
this.name = name;
}
}
2、具体产品
public class BJCheessPizza extends Pizza {
@Override
public void prepare() {
setName("北京的CheessPizza");
System.out.println(" 北京的CheessPizza 准备原材料");
}
}
public class BJPepperPizza extends Pizza {
@Override
public void prepare() {
setName("北京的Pepper");
System.out.println(" 北京的Pepper 准备原材料");
}
}
public class LDCheessPizza extends Pizza {
@Override
public void prepare() {
setName("伦敦的CheessPizza");
System.out.println(" 伦敦的CheessPizza 准备原材料");
}
}
public class LDPepperPizza extends Pizza {
@Override
public void prepare() {
setName("伦敦的Pepper");
System.out.println(" 伦敦的Pepper 准备原材料");
}
}
3、抽象工厂
//一个抽象工厂模式的抽象层(接口)
public interface AbsFactory {
//让下面的工厂子类来具体实现
Pizza createPizza(String orderType);
}
4、具体工厂
//这是一个工厂子类
public class BJFactory implements AbsFactory {
@Override
public Pizza createPizza(String orderType) {
Pizza pizza = null;
if (orderType.equals("Cheess")) {
pizza = new BJCheessPizza();
}
if (orderType.equals("Pepper")) {
pizza = new BJPepperPizza();
}
return pizza;
}
}
public class LDFactory implements AbsFactory {
@Override
public Pizza createPizza(String orderType) {
Pizza pizza=null;
if (orderType.equals("Cheess")){
pizza=new LDCheessPizza();
}if (orderType.equals("Pepper")){
pizza=new LDPepperPizza();
}
return pizza;
}
}
public class OrderPizza {
AbsFactory factory;
//构造器
public OrderPizza(AbsFactory factory) {
setFactory(factory);
}
private void setFactory(AbsFactory factory) {
Pizza pizza = null;
String orderType = "";//用户输入
this.factory = factory;
do {
orderType = getType();
//factory可能是北京的工厂子类,也可能是伦敦的工厂子类
pizza = factory.createPizza(orderType);
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.box();
pizza.cut();
} else {
System.out.println("订购失败");
break;
}
} while (true);
}
//写一个方法,可以获取客户希望订购的披萨种类
private String getType() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza type:");
String str = null;
str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
public class PizzaStore {
public static void main(String[] args) {
new OrderPizza(new BJFactory());
new OrderPizza(new LDFactory());
}
}
4)、工厂模式小结
工厂模式的意义:将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦,从而提高项目的扩展和维护性。
三种工厂模式:简单工厂模式、工厂方法模式、抽象工厂模式。
设计模式的依赖抽象原则:
原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。例如,Windows 操作系统的安装通常较耗时,如果复制就快了很多。在生活中复制的例子非常多,这里不一一列举了。
工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即“对象.clone( )”。
原型模式的优点:
原型模式的缺点:
由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。
1. 模式的结构
原型模式包含以下主要角色。
其结构图如图 1 所示。
2. 模式的实现
原型模式的克隆分为浅克隆和深克隆。
Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆,这里的 Cloneable 接口就是抽象原型类。其代码如下:
//具体原型类
class Realizetype implements Cloneable {
Realizetype() {
System.out.println("具体原型创建成功!");
}
public Object clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!");
return (Realizetype) super.clone();
}
}
//原型模式的测试类
public class PrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
Realizetype obj1 = new Realizetype();
Realizetype obj2 = (Realizetype) obj1.clone();
System.out.println("obj1==obj2?" + (obj1 == obj2));
}
程序的运行结果如下:
具体原型创建成功!
具体原型复制成功!
obj1==obj2?false
【例1】用原型模式生成“三好学生”奖状。
分析:同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,属于相似对象的复制,同样可以用原型模式创建,然后再做简单修改就可以了。图 4 所示是三好学生奖状生成器的结构图。
public class ProtoTypeCitation {
public static void main(String[] args) throws CloneNotSupportedException {
citation obj1 = new citation("张三", "同学:在2016学年第一学期中表现优秀,被评为三好学生。", "韶关学院");
obj1.display();
citation obj2 = (citation) obj1.clone();
obj2.setName("李四");
obj2.display();
}
}
//奖状类
class citation implements Cloneable {
String name;
String info;
String college;
citation(String name, String info, String college) {
this.name = name;
this.info = info;
this.college = college;
System.out.println("奖状创建成功!");
}
void setName(String name) {
this.name = name;
}
String getName() {
return (this.name);
}
void display() {
System.out.println(name + info + college);
}
public Object clone() throws CloneNotSupportedException {
System.out.println("奖状拷贝成功!");
return (citation) super.clone();
}
}
程序运行结果如下:
奖状创建成功!
张三同学:在2016学年第一学期中表现优秀,被评为三好学生。韶关学院
奖状拷贝成功!
李四同学:在2016学年第一学期中表现优秀,被评为三好学生。韶关学院
原型模式通常适用于以下场景。
在 Spring 中,原型模式应用的非常广泛,例如 scope='prototype'、JSON.parseObject() 等都是原型模式的具体应用。
原型模式可扩展为带原型管理器的原型模式,它在原型模式的基础上增加了一个原型管理器 PrototypeManager 类。该类用 HashMap 保存多个复制的原型,Client 类可以通过管理器的 get(String id) 方法从中获取复制的原型。其结构图如图 5 所示。
【例2】用带原型管理器的原型模式来生成包含“圆”和“正方形”等图形的原型,并计算其面积。分析:本实例中由于存在不同的图形类,例如,“圆”和“正方形”,它们计算面积的方法不一样,所以需要用一个原型管理器来管理它们,图 6 所示是其结构图。
程序代码如下:
import java.util.*;
interface Shape extends Cloneable {
public Object clone(); //拷贝
public void countArea(); //计算面积
}
class Circle implements Shape {
public Object clone() {
Circle w = null;
try {
w = (Circle) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("拷贝圆失败!");
}
return w;
}
public void countArea() {
int r = 0;
System.out.print("这是一个圆,请输入圆的半径:");
Scanner input = new Scanner(System.in);
r = input.nextInt();
System.out.println("该圆的面积=" + 3.1415 * r * r + "\n");
}
}
class Square implements Shape {
public Object clone() {
Square b = null;
try {
b = (Square) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("拷贝正方形失败!");
}
return b;
}
public void countArea() {
int a = 0;
System.out.print("这是一个正方形,请输入它的边长:");
Scanner input = new Scanner(System.in);
a = input.nextInt();
System.out.println("该正方形的面积=" + a * a + "\n");
}
}
class ProtoTypeManager {
private HashMap ht = new HashMap();
public ProtoTypeManager() {
ht.put("Circle", new Circle());
ht.put("Square", new Square());
}
public void addshape(String key, Shape obj) {
ht.put(key, obj);
}
public Shape getShape(String key) {
Shape temp = ht.get(key);
return (Shape) temp.clone();
}
}
public class ProtoTypeShape {
public static void main(String[] args) {
ProtoTypeManager pm = new ProtoTypeManager();
Shape obj1 = (Circle) pm.getShape("Circle");
obj1.countArea();
Shape obj2 = (Shape) pm.getShape("Square");
obj2.countArea();
}
}
运行结果如下所示:
这是一个圆,请输入圆的半径:3
该圆的面积=28.2735
这是一个正方形,请输入它的边长:3
该正方形的面积=9
1)、克隆羊问题
现在有一只羊,姓名为:tom,年龄为:1,颜色为:白色,请编写程序创建和tom羊属性完全相同的10只羊。
2)、传统方式解决克隆羊问题
传统的方式的优缺点:
改进的思路分析:Java中Object类是所有类的根类,Object类提供了一个clone( )方法,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力(原型模式)。
public class Sheep implements Cloneable{
private String name;
private int age;
private String color;
public Sheep(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
'}';
}
//克隆该实例,使用默认的clone方法来完成
@Override
protected Object clone() {
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.getMessage());
}
return sheep;
}
}
public class Client {
public static void main(String[] args) {
Sheep sheep = new Sheep("tom", 1, "白色");
Sheep sheep2 = (Sheep) sheep.clone();
Sheep sheep3 = (Sheep)sheep.clone();
//......
System.out.println(sheep);
System.out.println(sheep2);
System.out.println(sheep3);
}
}
1) Spring中原型bean的创建,就是原型模式的应用
2) 代码分析+Debug源码
beans.xml
Test.java
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取monster[通过id获取monster]
Object bean1 = applicationContext.getBean("id01");
System.out.println("bean1" + bean2);
Object bean2 = applicationContext.getBean("id01");
System.out.println("bean1" + bean2);
System.out.println(bean1 = bean2); // FALSE
}
1)、浅拷贝
- 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
- 对于数据类型是引用类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响另一个对象的该成员变量值。
- 前面我们的克隆羊案例就是浅拷贝。
- 浅拷贝是使用默认的clone( )方法来实现。
2)、深拷贝
- 复制对象的所有基本数据类型的成员变量值。
- 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象,也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝。
深拷贝的实现方式1:重写clone方法。
深拷贝实现方式2:通过对象序列化实现(推荐)。
public class DeepCloneAbleTarget implements Serializable, Cloneable {
private String cloneName;
private String cloneClass;
// 构造函数
public DeepCloneAbleTarget(String cloneName, String cloneClass) {
this.cloneName = cloneName;
this.cloneClass = cloneClass;
}
// 因为该类的属性,都是String,因此我们这里使用默认的clone方法即可
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class DeepProtoType implements Serializable, Cloneable {
public String name; // String 属性
public DeepCloneAbleTarget deepCloneAbleTarget; // 应用类型
public DeepProtoType() {
super();
}
// 深拷贝 - 方式一使用clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
// 这里完成对基本类型(属性)和String的克隆
deep = super.clone();
// 对引用类型的属性单独处理
DeepProtoType deepProtoType = (DeepProtoType) deep;
deepProtoType.deepCloneAbleTarget = (DeepCloneAbleTarget) deepCloneAbleTarget.clone();
return deepProtoType;
}
// 方式二:通过对象的序列化实现深拷贝(推荐)
public Object deepClone(){
// 创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
// 序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); // 当前的这个对象以对象流的方式输出
// 反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepProtoType copyObj = (DeepProtoType) ois.readObject();
return copyObj;
} catch (Exception e) {
e.printStackTrace();
return null;
}finally {
// 关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public class Client {
public static void main(String[] args) throws Exception {
DeepProtoType p1 = new DeepProtoType();
p1.name = "宋江";
p1.deepCloneAbleTarget = new DeepCloneAbleTarget("大牛", "大黑牛");
// 方式一:完成深拷贝
DeepProtoType p2 = (DeepProtoType) p1.clone();
System.out.println("-----------------方式一:完成深拷贝-------------------");
System.out.println("d.name = " + p1.name + "d.deepCloneAbleTarget=" + p1.deepCloneAbleTarget.hashCode());
System.out.println("d2.name = " + p2.name + "d.deepCloneAbleTarget=" + p2.deepCloneAbleTarget.hashCode());
// 方式二:完成深拷贝
DeepProtoType p3 = (DeepProtoType) p1.deepClone();
System.out.println("-----------------方式二:完成深拷贝-------------------");
System.out.println("p2.name=" + p1.name + "p.deepCloneableTarget=" + p1.deepCloneAbleTarget.hashCode());
System.out.println("p2.name=" + p3.name + "p2.deepCloneableTarget=" + p3.deepCloneAbleTarget.hashCode());
}
}
运行结果
在软件开发过程中有时需要创建一个复杂的对象,这个复杂对象通常由多个子部件按一定的步骤组合而成。例如,计算机是由 CPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的,采购员不可能自己去组装计算机,而是将计算机的配置要求告诉计算机销售公司,计算机销售公司安排技术人员去组装计算机,然后再交给要买计算机的采购员。
生活中这样的例子很多,如游戏中的不同角色,其性别、个性、能力、脸型、体型、服装、发型等特性都有所差异;还有汽车中的方向盘、发动机、车架、轮胎等部件也多种多样;每封电子邮件的发件人、收件人、主题、内容、附件等内容也各不相同。
以上所有这些产品都是由多个部件构成的,各个部件可以灵活选择,但其创建步骤都大同小异。这类产品的创建无法用前面介绍的工厂模式描述,只有建造者模式可以很好地描述该类产品的创建。
建造者(Builder)模式的定义:指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
该模式的主要优点如下:
其缺点如下:
建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。
建造者(Builder)模式由产品、抽象建造者、具体建造者、指挥者等 4 个要素构成,现在我们来分析其基本结构和实现方法。
1. 模式的结构
建造者(Builder)模式的主要角色如下。
需求描述:
传统方式解决盖房需求
public abstract class AbstractHouse {
public abstract void buildbasic();//打地基
public abstract void buildWalls();//砌墙
public abstract void roofed();//封顶
public void build() {
buildbasic();
buildWalls();
roofed();
}
}
public class CommonHouse extends AbstractHouse {
@Override
public void buildbasic() {
System.out.println("普通房子打地基");
}
@Override
public void buildWalls() {
System.out.println("普通房子砌墙");
}
@Override
public void roofed() {
System.out.println("普通房子封顶");
}
}
public class Client {
public static void main(String[] args) {
AbstractHouse commonHouse = new CommonHouse();
commonHouse.build();
}
}
传统方式问题分析:
下面我们使用建造者模式来完成需求。
//产品-->Product
public class House {
private String baise;
private String wall;
private String roofed;
public String getBaise() {
return baise;
}
public void setBaise(String baise) {
this.baise = baise;
}
public String getWall() {
return wall;
}
public void setWall(String wall) {
this.wall = wall;
}
public String getRoofed() {
return roofed;
}
public void setRoofed(String roofed) {
this.roofed = roofed;
}
}
//抽象的建造者
public abstract class HouseBuilder {
protected House house = new House();
//将建造的流程写好,抽象的方法
public abstract void buildBasic();
public abstract void buildWalls();
public abstract void roofed();
//建造房子后,将产品(房子)返回
public House buildHouse() {
return house;
}
}
public class CommonHouse extends HouseBuilder {
@Override
public void buildBasic() {
System.out.println("普通房子打地基 5米");
}
@Override
public void buildWalls() {
System.out.println("普通房子砌墙10cm");
}
@Override
public void roofed() {
System.out.println("普通房子屋顶");
}
}
public class HighBuilding extends HouseBuilder {
@Override
public void buildBasic() {
System.out.println("高楼的打地基100米");
}
@Override
public void buildWalls() {
System.out.println("高楼的砌墙20cm");
}
@Override
public void roofed() {
System.out.println("高楼的透明屋顶");
}
}
//指挥者,指定制作流程
public class HouseDirector {
HouseBuilder houseBuilder = null;
//通过构造器传入houseBuilder
public HouseDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
//或者通过set方法传入houseBuilder
public void setHouseBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
//如何处理建造房子的流程?交给指挥者
public House constructHouse() {
houseBuilder.buildBasic();
houseBuilder.buildWalls();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}
}
public class Client {
public static void main(String[] args) {
CommonHouse commonHouse = new CommonHouse();//盖普通房子
HouseDirector houseDirector = new HouseDirector(commonHouse);//创建房子的指挥者
House house = houseDirector.constructHouse();//完成盖房,返回产品(房子)
System.out.println("-------------------------------");
HighBuilding highBuilding = new HighBuilding();//盖高房子
houseDirector.setHouseBuilder(highBuilding);//重置建造者
houseDirector.constructHouse();
}
}
Appendable 接口定义了多个append方法(抽象方法), 即Appendable 为抽象建 造者, 定义了抽象方法
AbstractStringBuilder 实现了 Appendable 接口方法,这里的 AbstractStringBuilder 已经是建造者,只是不能实例化
StringBuilder 即充当了指挥者角色,同时充当了具体的建造者,建造方法的 实现是由 AbstractStringBuilder 完成, 而StringBuilder 继承了AbstractStringBuilder
1)、客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
2)、每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。
3)、可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
4)、增加新的具体建造者无需修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”。
5)、建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
6)、如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此这种情况下,要考虑是否选择建造者模式。
7)、抽象工厂模式VS建造者模式:抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
在现实生活中,经常出现两个对象因接口不兼容而不能在一起工作的实例,这时需要第三者进行适配。例如,讲中文的人同讲英文的人对话时需要一个翻译,用直流电的笔记本电脑接交流电源时需要一个电源适配器,用计算机访问照相机的 SD 内存卡时需要一个读卡器等。
在软件设计中也可能出现:需要开发的具有某种业务功能的组件在现有的组件库中已经存在,但它们与当前系统的接口规范不兼容,如果重新开发这些组件成本又很高,这时用适配器模式能很好地解决这些问题。
适配器模式(Adapter)的定义如下:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
该模式的主要优点如下。
其缺点是:
类适配器模式可采用多重继承方式实现,如 C++ 可定义一个适配器类来同时继承当前系统的业务接口和现有组件库中已经存在的组件接口;Java 不支持多继承,但可以定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。现在来介绍它们的基本结构。
1. 模式的结构
适配器模式(Adapter)包含以下主要角色。
类适配器模式介绍
基本介绍:Adapter类,通过继承 src类,实现 dst 类接口,完成src->dst的适配。
类适配器模式应用实例
1)、以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter,220V交流电 相当于src (即被适配者),我们的目dst(即 目标)是5V直流电
2)、思路分析(类图)
3)、代码实现
//被适配的类
public class Voltage220V {
//输出220V的电压
public int output220V(){
int src=220;
System.out.println("电源电压="+src+"伏");
return src;
}
}
//适配接口
public interface IVoltage5V {
int output5V();
}
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
int srcV = output220V();//获取220V的电压
int dstV = srcV / 44;//进行处理
return dstV;
}
}
public class Phone {
//充电
public void charging(IVoltage5V iVoltage5V){
if (iVoltage5V.output5V()==5){
System.out.println("现在电压为5V,可以充电");
}else if (iVoltage5V.output5V()>5){
System.out.println("现在电压大于5V,可以充电");
}
}
}
public class Client {
public static void main(String[] args) {
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}
什么是对象适配器模式
基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题。即:持有src类,实现dst类接口,完成src>dst的适配。
根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。
对象适配器模式是适配器模式常用的一种。
思路分析
代码实现
//被适配的类
public class Voltage220V {
//输出220V的电压
public int output220V(){
int src=220;
System.out.println("电源电压="+src+"伏");
return src;
}
}
//适配接口
public interface IVoltage5V {
int output5V();
}
public class VoltageAdapter implements IVoltage5V {
private Voltage220V voltage220V;
//通过构造器传入
public VoltageAdapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
@Override
public int output5V() {
int dst = 0;
if (null != voltage220V) {
int src = voltage220V.output220V();
dst = src / 44;
}
return dst;
}
}
public class Phone {
//充电
public void charging(IVoltage5V iVoltage5V){
if (iVoltage5V.output5V()==5){
System.out.println("现在电压为5V,可以充电");
}else if (iVoltage5V.output5V()>5){
System.out.println("现在电压大于5V,可以充电");
}
}
}
public class Client {
public static void main(String[] args) {
System.out.println("==============对象适配器模式================");
Phone phone = new Phone();
phone.charging(new VoltageAdapter(new Voltage220V()));
}
}
对象适配器注意事项和细节
对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承src的局限性问题,也不再要求dst必须是接口。
使用成本更低更灵活。
什么是接口适配器模式
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。
适用于一个接口不想使用其所有的方法的情况。
应用实例
思路分析
代码实现
public interface Interface4 {
void m1();
void m2();
void m3();
void m4();
}
//在AbsAdapter中我们将Interface4的方法进行默认实现
public abstract class AbsAdapter implements Interface4 {
@Override
public void m1() {
}
@Override
public void m2() {
}
@Override
public void m3() {
}
@Override
public void m4() {
}
}
public class Client {
public static void main(String[] args) {
AbsAdapter absAdapter= new AbsAdapter(){
@Override
public void m1() {
System.out.println("使用了m1的方法");
}
};
absAdapter.m1();
}
}
1) SpringMvc中的HandlerAdapter, 就使用了适配器模式
2) SpringMVC处理请求的流程回顾
3) 使用HandlerAdapter 的原因分析:
可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用 Controller方法,需要调用的时候就得不断是使用if else来进行判断是哪一种子类然后执行。那么如果后面要扩展Controller,就得修改原来的代码,这样违背了OCP原则。
4) 代码分析+Debug源码
5) 动手写SpringMVC通过适配器设计模式获取到对应的Controller的源码
说明:
• Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类
• 适配器代替controller执行相应的方法
• 扩展Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了,
• 这就是设计模式的力量
//多种Controller实现
public interface Controller {
}
class HttpController implements Controller {
public void doHttpHandler() {
System.out.println("http...");
}
}
class SimpleController implements Controller {
public void doSimplerHandler() {
System.out.println("simple...");
}
}
class AnnotationController implements Controller {
public void doAnnotationHandler() {
System.out.println("annotation...");
}
}
//定义一个Adapter接口
public interface HandlerAdapter {
public boolean supports(Object handler);
public void handle(Object handler);
}
// 多种适配器类
class SimpleHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((SimpleController) handler).doSimplerHandler();
}
public boolean supports(Object handler) {
return (handler instanceof SimpleController);
}
}
class HttpHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((HttpController) handler).doHttpHandler();
}
public boolean supports(Object handler) {
return (handler instanceof HttpController);
}
}
class AnnotationHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((AnnotationController) handler).doAnnotationHandler();
}
public boolean supports(Object handler) {
return (handler instanceof AnnotationController);
}
}
import java.util.ArrayList;
import java.util.List;
public class DispatchServlet {
public static List handlerAdapters = new ArrayList();
public DispatchServlet() {
handlerAdapters.add(new AnnotationHandlerAdapter());
handlerAdapters.add(new HttpHandlerAdapter());
handlerAdapters.add(new SimpleHandlerAdapter());
}
public void doDispatch() {
// 此处模拟SpringMVC从request取handler的对象,
// 适配器可以获取到希望的Controller
HttpController controller = new HttpController();
// AnnotationController controller = new AnnotationController();
//SimpleController controller = new SimpleController();
// 得到对应适配器
HandlerAdapter adapter = getHandler(controller);
// 通过适配器执行对应的controller对应方法
adapter.handle(controller);
}
public HandlerAdapter getHandler(Controller controller) {
//遍历:根据得到的controller(handler), 返回对应适配器
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(controller)) {
return adapter;
}
}
return null;
}
public static void main(String[] args) {
new DispatchServlet().doDispatch(); // http...
}
}
三种命名方式是根据src是以怎样的形式给到Adapter(在Adapter里的形式)来命名的。
Adapter模式最大的作用还是将原本不兼容的接口融合到一起工作。
实际开发中,实现起来不拘泥于我们讲解的三种经典形式
在现实生活中,某些类具有两个或多个维度的变化,如图形既可按形状分,又可按颜色分。如何设计类似于 Photoshop 这样的软件,能画不同形状和不同颜色的图形呢?如果用继承方式,m 种形状和 n 种颜色的图形就有 m×n 种,不但对应的子类很多,而且扩展困难。
当然,这样的例子还有很多,如不同颜色和字体的文字、不同品牌和功率的汽车、不同性别和职业的男女、支持不同平台和不同文件格式的媒体播放器等。如果用桥接模式就能很好地解决这些问题。
桥接(Bridge)模式的定义如下:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
通过上面的讲解,我们能很好的感觉到桥接模式遵循了里氏替换原则和依赖倒置原则,最终实现了开闭原则,对修改关闭,对扩展开放。这里将桥接模式的优缺点总结如下。
桥接(Bridge)模式的优点是:
缺点是:由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度。
将抽象和实现解耦,使得两者可以独立地变化。
是一种结构型设计模式。
可以将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系。
1. 模式的结构
桥接(Bridge)模式包含以下主要角色。
传统方式
案例
现在对不同手机类型的不同品牌实现编程操作,比如开机、关机、打电话等。
思路分析
问题分析
扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本。
解决方案:使用桥接模式
public interface Brand {
void open();
void close();
void call();
}
public class Vivo implements Brand {
@Override
public void open() {
System.out.println("Vivo手机开机");
}
@Override
public void close() {
System.out.println("Vivo手机关机");
}
@Override
public void call() {
System.out.println("Vivo手机打电话");
}
}
public class XiaoMi implements Brand {
@Override
public void open() {
System.out.println("小米手机开机了");
}
@Override
public void close() {
System.out.println("小米手机关机了");
}
@Override
public void call() {
System.out.println("小米手机打电话");
}
}
public abstract class Phone {
private Brand brand;//组合品牌
public Phone(Brand brand) {//构造器
this.brand = brand;
}
protected void open() {
this.brand.open();
}
protected void close() {
this.brand.close();
}
protected void call() {
this.brand.call();
}
}
public class FoldedPhone extends Phone {
public FoldedPhone(Brand brand) {
super(brand);
}
public void open(){
super.open();
System.out.println("折叠样式手机");
}
public void close(){
super.close();
System.out.println("折叠样式手机");
}
public void call(){
super.call();
System.out.println("折叠样式手机");
}
}
public class Client {
public static void main(String[] args) {
Phone phone1 = new FoldedPhone(new XiaoMi());
phone1.open();
phone1.call();
phone1.close();
System.out.println("=========================");
Phone phone2 = new FoldedPhone(new Vivo());
phone2.open();
phone2.call();
phone2.close();
}
}
1) Jdbc 的 Driver接口,如果从桥接模式来看,Driver就是一个接口,下面可以有 MySQL的Driver,Oracle的Driver,这些就可以当做实现接口类
2) 代码分析+Debug源码
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用
1) 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥 接模式尤为适用.
2) 常见的应用场景:
-JDBC驱动程序
-银行转账系统
转账分类: 网上转账,柜台转账,AMT转账
转账用户类型:普通用户,银卡用户,金卡用户..
-消息管理
消息类型:即时消息,延时消息
消息分类:手机短信,邮件消息,QQ消息...
上班族大多都有睡懒觉的习惯,每天早上上班时间都很紧张,于是很多人为了多睡一会,就会用方便的方式解决早餐问题。有些人早餐可能会吃煎饼,煎饼中可以加鸡蛋,也可以加香肠,但是不管怎么“加码”,都还是一个煎饼。在现实生活中,常常需要对现有产品增加新的功能或美化其外观,如房子装修、相片加相框等,都是装饰器模式。
在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰模式来实现。
装饰(Decorator)模式的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
装饰(Decorator)模式的主要优点有:
其主要缺点是:装饰模式会增加许多子类,过度使用会增加程序得复杂性。
通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰模式的目标。下面来分析其基本结构和实现方法。
1. 模式的结构
装饰模式主要包含以下角色。
1) 装饰者模式就像打包一个快递
主体:比如:陶瓷、衣服 (Component) // 被装饰者
包装:比如:报纸填充、塑料泡沫、纸板、木板(Decorator)
2) Component
主体:比如类似前面的Drink
3) ConcreteComponent和Decorator
ConcreteComponent:具体的主体,
比如前面的各个单品咖啡
Decorator: 装饰者,比如各调料.
4) 在如图的Component与ConcreteComponent之间,如果
ConcreteComponent类很多,还可以设计一个缓冲层,将共有的部分提取出来, 抽象层一个类。
传统方式
案例
星巴克咖啡订单项目:
方案一分析:
方案二分析:
装饰者模式解决星巴克咖啡订单
抽象类
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();
}
抽象构件(Component)角色
public class Coffee extends Drink {
@Override
public float cost() {
return super.getPrice();
}
}
具体构件(ConcreteComponent)角色
public class LongBlack extends Coffee {
public LongBlack() {
setDes("longBlack");
setPrice(5.0f);
}
}
public class Espresso extends Coffee {
public Espresso(){
setDes("意大利咖啡");
setPrice(6.0f);
}
}
public class ShortBlack extends Coffee {
public ShortBlack(){
setDes("shortBlack");
setPrice(4.0f);
}
}
抽象装饰(Decorator)角色
public class Decorator extends Drink {
private Drink obj;
public Decorator(Drink obj) {
this.obj = obj;
}
@Override
public float cost() {
return super.getPrice() + obj.cost();
}
@Override
public String getDes() {
return super.getDes() + " " + super.getPrice() + "&&" + obj.getDes();
}
}
具体装饰(ConcreteDecorator)角色(调味品)
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);
}
}
public class Soy extends Decorator {
public Soy(Drink obj) {
super(obj);
setDes("豆浆");
setPrice(1.5f);
}
}
public class Client {
public static void main(String[] args) {
//装饰者模式下的订单:1份巧克力+1份牛奶的LongBlack
//1、点一份LongBlack
Drink order = new LongBlack();
System.out.println("费用="+order.cost());
System.out.println("描述="+order.getDes());
//2、加入一份牛奶
order = new Milk(order);
System.out.println("order 加入一份牛奶 费用="+order.cost());
System.out.println("order 加入一份牛奶 描述="+order.getDes());
//3、order加入一份巧克力
order=new Chocolate(order);
System.out.println("order 加入一份巧克力 费用="+order.cost());
System.out.println("order 加入一份巧克力 描述="+order.getDes());
}
}
Java的IO结构,FilterInputStream就是一个装饰者
//是一个抽象类,即Component
public abstract class InputStream implements Closeable{}
//是一个装饰者类Decorator
public class FilterInputStream extends InputStream {
//被装饰的对象
protected volatile InputStream in;
}
//FilterInputStream 子类,
class DataInputStream extends FilterInputStream implements DataInput { }
在现实生活中,存在很多“部分-整体”的关系,例如,大学中的部门与学院、总公司中的部门与分公司、学习用品中的书与书包、生活用品中的衣服与衣柜、以及厨房中的锅碗瓢盆等。在软件开发中也是这样,例如,文件系统中的文件与文件夹、窗体程序中的简单控件与容器控件等。对这些简单对象与复合对象的处理,如果用组合模式来实现会很方便。
组合(Composite Pattern)模式的定义:有时又叫作整体-部分(Part-Whole)模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性,属于结构型设计模式。
组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,顶层的节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点,树形结构图如下。
由上图可以看出,其实根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶子节点与树枝节点在语义上不属于用一种类型。但是在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。
这样,在组合模式中,整个树形结构中的对象都属于同一种类型,带来的好处就是用户不需要辨别是树枝节点还是叶子节点,可以直接进行操作,给用户的使用带来极大的便利。
组合模式的主要优点有:
其主要缺点是:
组合模式的结构不是很复杂,下面对它的结构和实现进行分析。
1. 模式的结构
组合模式包含以下主要角色。
传统方式
案例
编写程序展示一个学校院系结构,需求是这样的:要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。如图:
传统方案解决学校院系展示(类图)
分析:
思路分析
代码实现
public abstract class OrganizationComponent {
private String name;//名字
private String des;//描述
public OrganizationComponent(String name, String des) {
this.name = name;
this.des = des;
}
protected void add(OrganizationComponent organizationComponent) {
//默认实现
throw new UnsupportedOperationException();
}
protected void remove(OrganizationComponent organizationComponent) {
//默认实现
throw new UnsupportedOperationException();
}
protected abstract void print();
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;
}
}
public class University extends OrganizationComponent {
List organizationComponents = new ArrayList();
public University(String name, String des) {
super(name, des);
}
@Override//重写
protected void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}
@Override//重写
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override//重写
public String getName() {
return super.getName();
}
@Override//重写
public String getDes() {
return super.getDes();
}
@Override
protected void print() {
System.out.println("---------------" + getName() + "-------------------");
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
}
public class College extends OrganizationComponent {
//List 中 存放的Department
List organizationComponents = new ArrayList();
// 构造器
public College(String name, String des) {
super(name, des);
// TODO Auto-generated constructor stub
}
// 重写add
@Override
protected void add(OrganizationComponent organizationComponent) {
// TODO Auto-generated method stub
// 将来实际业务中,Colleage 的 add 和 University add 不一定完全一样
organizationComponents.add(organizationComponent);
}
// 重写remove
@Override
protected void remove(OrganizationComponent organizationComponent) {
// TODO Auto-generated method stub
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {
// TODO Auto-generated method stub
return super.getName();
}
@Override
public String getDes() {
// TODO Auto-generated method stub
return super.getDes();
}
// print方法,就是输出University 包含的学院
@Override
protected void print() {
// TODO Auto-generated method stub
System.out.println("--------------" + getName() + "--------------");
//遍历 organizationComponents
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
}
public class Department extends OrganizationComponent {
//没有集合
public Department(String name, String des) {
super(name, des);
// TODO Auto-generated constructor stub
}
//add , remove 就不用写了,因为他是叶子节点
@Override
public String getName() {
// TODO Auto-generated method stub
return super.getName();
}
@Override
public String getDes() {
// TODO Auto-generated method stub
return super.getDes();
}
@Override
protected void print() {
// TODO Auto-generated method stub
System.out.println(getName());
}
}
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
//从大到小创建对象 学校
OrganizationComponent university = new University("清华大学", " 中国顶级大学 ");
//创建 学院
OrganizationComponent computerCollege = new College("计算机学院", " 计算机学院 ");
OrganizationComponent infoEngineercollege = new College("信息工程学院", " 信息工程学院 ");
//创建各个学院下面的系(专业)
computerCollege.add(new Department("软件工程", " 软件工程不错 "));
computerCollege.add(new Department("网络工程", " 网络工程不错 "));
computerCollege.add(new Department("计算机科学与技术", " 计算机科学与技术是老牌的专业 "));
//
infoEngineercollege.add(new Department("通信工程", " 通信工程不好学 "));
infoEngineercollege.add(new Department("信息工程", " 信息工程好学 "));
//将学院加入到 学校
university.add(computerCollege);
university.add(infoEngineercollege);
university.print();
// infoEngineercollege.print();
}
}
1) Java的集合类-HashMap就使用了组合模式
2) 代码分析+Debug 源码
1) 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
2) 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系, 客户端不用做出任何改动.
3) 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
4) 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式.
5) 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式
软件设计也是这样,当一个系统的功能越来越强,子系统会越来越多,客户对系统的访问也变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。
图 1 给出了客户去当地房产局办理房产证过户要遇到的相关部门。
图1 办理房产证过户的相关部门
外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
在日常编码工作中,我们都在有意无意的大量使用外观模式。只要是高层模块需要调度多个子系统(2个以上的类对象),我们都会自觉地创建一个新的类封装这些子系统,提供精简的接口,让高层模块可以更加容易地间接调用这些子系统的功能。尤其是现阶段各种第三方SDK、开源类库,很大概率都会使用外观模式。
外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点。
外观(Facade)模式的主要缺点如下。
外观(Facade)模式的结构比较简单,主要是定义了一个高层接口。它包含了对各个子系统的引用,客户端可以通过它访问各个子系统的功能。现在来分析其基本结构和实现方法。
1. 模式的结构
外观(Facade)模式包含以下主要角色。
其结构图如图 2 所示。
2. 模式的实现
外观模式的实现代码如下:
public class FacadePattern {
public static void main(String[] args) {
Facade f = new Facade();
f.method();
}
}
//外观角色
class Facade {
private SubSystem01 obj1 = new SubSystem01();
private SubSystem02 obj2 = new SubSystem02();
private SubSystem03 obj3 = new SubSystem03();
public void method() {
obj1.method1();
obj2.method2();
obj3.method3();
}
}
//子系统角色
class SubSystem01 {
public void method1() {
System.out.println("子系统01的method1()被调用!");
}
}
//子系统角色
class SubSystem02 {
public void method2() {
System.out.println("子系统02的method2()被调用!");
}
}
//子系统角色
class SubSystem03 {
public void method3() {
System.out.println("子系统03的method3()被调用!");
}
}
程序运行结果如下:
子系统01的method1()被调用!
子系统02的method2()被调用!
子系统03的method3()被调用!
传统方式
案例
影院管理项目:组建一个家庭影院:DVD播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的功能,其过程为使用遥控器统筹各设备开关。
思路分析
问题分析
在ClientTest的main方法中,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程混乱,没有清晰的过程。
不利于在ClientTest中,去维护对子系统的操作。
解决思路:定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如在高层接口提供三个方法ready,play,end),用来访问子系统中的一群接口。
也就是说,就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节(外观模式)。
思路分析
代码实现
public class DVDPlayer {
//使用单例模式,使用饿汉式
private static DVDPlayer instance=new DVDPlayer();
private DVDPlayer(){//构造器私有化
}
public static DVDPlayer getInstance(){
return instance;
}
public void on(){
System.out.println("DVD打开");
}
public void off(){
System.out.println("DVD关闭");
}
public void play(){
System.out.println("DVD正在播放");
}
}
public class Popcorn {
private static Popcorn instance=new Popcorn();
private Popcorn(){
}
public static Popcorn getInstance(){
return instance;
}
public void on(){
System.out.println("爆米花机打开");
}
public void off(){
System.out.println("爆米花机关闭");
}
public void pop(){
System.out.println("爆米花机正在制作爆米花");
}
}
public class Projector {
private static Projector instance=new Projector();
private Projector(){
}
public static Projector getInstance(){
return instance;
}
public void on(){
System.out.println("投影仪打开");
}
public void off(){
System.out.println("投影仪关闭");
}
public void focus(){
System.out.println("投影仪正在聚焦");
}
}
public class Screen {
private static Screen instance=new Screen();
private Screen(){
}
public static Screen getInstance(){
return instance;
}
public void up(){
System.out.println("屏幕上升");
}
public void down(){
System.out.println("屏幕下降");
}
}
public class Stereo {
private static Stereo instance=new Stereo();
private Stereo(){
}
public static Stereo getInstance(){
return instance;
}
public void on(){
System.out.println("立体声打开");
}
public void off(){
System.out.println("立体声关闭");
}
}
public class TheaterLight {
private static TheaterLight instance=new TheaterLight();
private TheaterLight(){
}
public static TheaterLight getInstance(){
return instance;
}
public void on(){
System.out.println("灯光打开");
}
public void off(){
System.out.println("灯光关闭");
}
}
public class HomeTheaterFacade {
//定义各个子系统对象
private TheaterLight theaterLight;
private Popcorn popcorn;
private Stereo stereo;
private Projector projector;
private Screen screen;
private DVDPlayer dvdPlayer;
public HomeTheaterFacade() {
this.theaterLight = TheaterLight.getInstance();
this.popcorn = Popcorn.getInstance();
this.stereo = Stereo.getInstance();
this.projector = Projector.getInstance();
this.screen = Screen.getInstance();
this.dvdPlayer = DVDPlayer.getInstance();
}
//操作分为四步
public void ready(){
theaterLight.on();
projector.on();
popcorn.on();
stereo.on();
screen.up();
dvdPlayer.on();
}
public void play(){
dvdPlayer.play();
}
public void end(){
theaterLight.off();
projector.off();
popcorn.off();
stereo.off();
screen.down();
dvdPlayer.off();
}
}
1) MyBatis 中的Configuration 去创建MetaObject 对象使用到外观模式
2) 代码分析+Debug源码+示意图
1) 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
2) 外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展
3) 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
4) 当系统需要进行分层设计时,可以考虑使用Facade模式
5) 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口, 让新系统与Facade类交互,提高复用性
6) 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维护为目的。
在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈。
例如,围棋和五子棋中的黑白棋子,图像中的坐标点或颜色,局域网中的路由器、交换机和集线器,教室里的桌子和凳子等。这些对象有很多相似的地方,如果能把它们相同的部分提取出来共享,则能节省大量的系统资源,这就是享元模式的产生背景。
享元(Flyweight)模式的定义:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个。
享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时,不需总是创建新对象,可以从缓冲池里拿,这样可以降低系统内存,同时提高效率。
享元模式经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式。
享元模式的主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
其主要缺点是:
内部状态和外部状态
比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态。
享元模式提出了两个要求:细粒度和共享对象。
这里就涉及到内部状态和外部状态了,即对象的信息分为两个部分:
内部状态和外部状态。内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变。
外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态。
比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态。而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态。
享元模式的本质是缓存共享对象,降低内存消耗。
1. 模式的结构
享元模式的主要角色有如下。
传统方式
案例
一个小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求有些不同:有客户要去以新闻的形式发布;有客户要求以博客的形式发布;有客户希望以微信公众号的形式发布。
思路分析
直接复制粘贴一份,然后根据客户不同要求,进行定制修改,然后给每个网站租用一个空间。
问题分析
需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费。
用享元模式来解决这个问题
思路分析
整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源。对于代码来说,由于是一份实例,维护和扩展都更加容易。即使用享元模式来解决。
代码实现
// 外部状态
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User(String name) {
this.name = name;
}
}
public abstract class WebSite {
public abstract void use(User user);
}
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() + "用户在使用");
}
}
public class WebSiteFactory {
//集合,充当池的作用
private HashMap pool = new HashMap<>();
//根据网站的类型,返回一个网站,如果没有就创建一个网站,并放入到池中,并返回
public WebSite getWebSiteCategory(String type) {
if (!pool.containsKey(type)) {
pool.put(type, new ConcreteWebSite(type));
}
return (WebSite) pool.get(type);
}
//获取网站分类的总数
public int getWebSiteCount() {
return pool.size();
}
}
public class Client {
public static void main(String[] args) {
//创建一个工厂类
WebSiteFactory webSiteFactory = new WebSiteFactory();
//客户要一个以新闻形式发布的网站
WebSite webSite1 = webSiteFactory.getWebSiteCategory("新闻");
webSite1.use(new User("新浪"));
//客户要一个以博客形式发布的网站
WebSite webSite2 = webSiteFactory.getWebSiteCategory("博客");
webSite2.use(new User("谷歌"));
//客户要一个以博客形式发布的网站
WebSite webSite3 = webSiteFactory.getWebSiteCategory("博客");
webSite3.use(new User("百度"));
System.out.println("网站的分类共有:"+webSiteFactory.getWebSiteCount());
}
}
1) Integer中的享元模式
2) 代码分析+Debug源码+说明
如果Integer.valueOf(x) x 在 -128 --- 127 直接,就是使用享元模式返回,如果不在范围内,则仍然new
小结:
1、在valueOf()方法中,先判断值是否在IntegerCache中,如果不在,就创建新的Integer(new),否则,就直接从缓存池中直接返回
2、valueOf() 方法就使用到享元模式
3、如果valueOf()方法得到一个Integer实例,范围在-128 - 127,执行速度比new的快
public class SourceCode {
public static void main(String[] args) {
Integer x = Integer.valueOf(127);
Integer y = new Integer(127);
Integer z = Integer.valueOf(127);
Integer w = new Integer(127);
System.out.println(x.equals(y)); // ? true ----> 比较的内容
System.out.println(x == y ); // ? false ----> 比较的对象
System.out.println(x == z ); // ? true ----> 比较的对象
System.out.println(w == x ); // ? false ----> 比较的对象
System.out.println(w == y ); // ? false ----> 比较的对象
}
}
享元模式这样理解,“享”就表示共享,“元”表示对象。
系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式。
用唯一标识吗判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储。
享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率。
享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方。
使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
享元模式经典的应用场景是需要缓冲池的场景,比如String常量池、数据库连接池。
在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。
在软件设计中,使用代理模式的例子也很多,例如,要访问的远程对象比较大(如视频或大图像等),其下载要花很多时间。还有因为安全原因需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
代理模式的主要优点有:
其主要缺点是:
那么如何解决以上提到的缺点呢?答案是可以使用动态代理方式
代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问,下面来分析其基本结构和实现方法。
1. 模式的结构
代理模式的主要角色如下。
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类。
应用实例
具体要求:
思路分析
代码实现
public interface ITeacherDao {
void teach();//授课的方法
}
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师正在授课...");
}
}
public class TeacherDaoProxy implements ITeacherDao {
private ITeacherDao target; // 目标对象通过接口来聚合
// 构造器
public TeacherDaoProxy(ITeacherDao target) {
this.target = target;
}
@Override
public void teach() {
System.out.println("开始代理,完成某些操作。。。");
target.teach();
System.out.println("完成代理...............");
}
}
public class Client {
public static void main(String[] args) {
// 创建目标对象(被代理对象)
TeacherDao teacherDao = new TeacherDao();
// 创建代理对象,同时将被代理对象传递给代理对象
TeacherDaoProxy proxy = new TeacherDaoProxy(teacherDao);
// 通过被代理对象,调用到被代理对象的方法
// 即:执行的是代理对象的方法,代理对象再调用目标对象的方法
proxy.teach();
}
}
静态代理的优缺点:
1) 优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
2) 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
3) 一旦接口增加方法,目标对象与代理对象都要维护
代理对象不需要实现接口,但是目标对象要实现接口,否则不能用动态代理。
代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象。
应用实例
将前面的静态代理改成JDK动态代理。
思路分析
代码实现
public interface ITeacherDao {
void teach();//授课的方法
}
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师正在授课...");
}
}
给目标对象生成一个代理对象
newProxyInstance方法参数说明
* 第一个参数:指定当前目标对象使用的类加载器;
* 第二个参数:目标对象实现的接口类型;
* 第三个参数:事件处理,执行目标对象的方式时,会触发事件处理器方法
public class ProxyFactory {
//目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象生成一个代理对象
public Object getProxyInstance() {
/**
* newProxyInstance方法参数说明
* 第一个参数:指定当前目标对象使用的类加载器;
* 第二个参数:目标对象实现的接口类型;
* 第三个参数:事件处理,执行目标对象的方式时,会触发事件处理器方法
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK代理开始...");
//反射机制调用目标对象的方法
Object invoke = method.invoke(target, args);
System.out.println("JDK代理提交...");
return invoke;
}
}
);
}
}
public class Client {
public static void main(String[] args) {
// 创建目标对象
ITeacherDao target = new TeacherDao();
// 给目标对象创建代理对象,可以转成ITeacherDao
ITeacherDao proxyInstance = (ITeacherDao) new ProxyFactory(target).getProxyInstance();
// org.springframework.cglib.proxy.Proxy$ProxyImpl$$EnhancerByCGLIB$$98299aad 内存中动态生成了代理对象
System.out.println("proxyInstance" + proxyInstance.getClass());
// 通过代理对象,调用目标对象的方法
proxyInstance.teach();
}
}
1)、静态代理和JDK代理都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理,这就是CGLIB代理。CGLIB包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类。
2)、CGLIB代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展。
3)、CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口,它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截。
4)、在AOP编程中如何选择代理模式:如果目标对象需要实现接口,用JDK代理;如果目标对象不需要实现接口,用CGLIB代理。
5)、Cglib包的底层是通过使用字节码处理框架ASM来转换字节码生成新的类
注意事项
思路分析
代码实现
public class TeacherDao {
public String teach(){
System.out.println("老师授课中...");
return "hello";
}
}
public class ProxyFactory implements MethodInterceptor {
// 维护一个目标对象
private Object target;
// 构造器,传入被代理对象
public ProxyFactory(Object target) {
this.target = target;
}
// 返回一个代理对象
public Object getProxyInstance(){
// 1、创建一个工具类
Enhancer enhancer = new Enhancer();
// 2、设置父类
enhancer.setSuperclass(target.getClass());
// 3、设置回调函数
enhancer.setCallback(this);
// 4、创建子类对象,即代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理开始...");
Object invoke = method.invoke(target, args);
System.out.println("完成cglib代理");
return invoke;
}
}
public class Client {
public static void main(String[] args) {
//创建目标对象
TeacherDao teacherDao = new TeacherDao();
//创建代理对象,返回被代理的目标对象(必须强制类型转换)
TeacherDao proxyInstance = (TeacherDao) new ProxyFactory(teacherDao).getProxyInstance();
// 通过代理对象,调用目标对象的方法
String result = proxyInstance.teach();
System.out.println(result);
}
}
防火墙代理:内网通过代理穿透防火墙,实现对公网的访问。
缓存代理:比如,当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok,如果取不到资源,再到公网或者数据库取,然后缓存。
远程代理:远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息。
同步代理:主要使用在多线程编程中,完成多线程间同步工作。
未完:请观看 23种设计模式-个人笔记(三): https://mp.csdn.net/editor/html?spm=1011.2124.3001.5352