1.设计模式分类
1.创建型模式
用于描述“怎样创建对象”,主要特点是“将对象的创建与使用分离”。
单例,原型,工厂方法,抽象工厂,建造者
2.结构型模式
用于描述如何将类或对象按某种布局组成更大的结构
代理,适配器,桥接,装饰,外观,享元,组合
3.行为型模式
用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责
模板方法,策略,命令,职责链,状态,观察者,中介者,迭代器,访问者,备忘录,解释器
2.UML图
统一建模语言(Unified Modeling Language, UML)是用来设计软件的可视化建模语言。
特点:简单,统一,图形化,能表达软件设计中的动态与静态信息。
分类:例图,类图,对象图,状态图,活动图,时序图,协作图,构件图,部署图
1.类图(Class diagram)
类图是显示了模型的静态结构,特别是模型中存在的类,类的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。
1.类的表示方式
在UML类图中,类使用包含类名,属性(field)和方法(method)且带有分割线的矩形来表示。
Employee(类名) |
-name:String -age:int -address:String |
+work():void |
属性/方法名称前加的加号和减号表示了这个属性/方法的可见性
+:public
-:private
#:protected
属性的完整表达方式是: 可见性 名称 : 类型 [ = 缺省值(初始化值)]
方法的完整表达方式是: 可见性 名称(参数列表) [ : 返回类型]
注:1.中括号中的内容表示是可选的。2.也有将类型放在变量名前面,返回值类型放在方法名前面
2.类与类之间关系的表示方式
1.关联关系(一对一)
关联关系是对象之间的一种引用关系(在一个类中声明了另一个类的成员变量),用于表示一类对象与另一类对象之间的联系,如老师和学生,师傅和徒弟。关联关系是类与类之间最常用的一种关系,分为一般关联关系,聚合关系和组合关系。
一般关联关系又分为单向关联,双向关联,自关联。
1.单向关联
在UML类图这单向关联用一个带箭头的实线表示。一个类持有另一个类的成员变量
2.双向关联
在UML类图种,双向关联用一个不带箭头的直线表示。双方各自持有对方类型的成员变量。
3.自关联
在UML类图这,自关联用一个带有箭头且指向自身的线表示。自己包含自己。
2.聚合关系
强关联关系,是整体和部分之间的关系。聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。如:学校与老师的关系
在UML类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。
3.组合关系
组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。如:头和嘴的关系。
在UML类图中,组合关系用带菱形的实线来表示,菱形指向整体。
4.依赖关系
依赖关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码只中,某个类的方法通过局部变量,方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。
在UML类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。
5.继承(泛化)关系
继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系。
在UML类图中,泛化关系用带空心三角箭头的实线表示,箭头从子类指向父类。代码实现时,使用面向对象的继承机制来实现泛化关系。如:Student和Teacher类都是Person的子类。
6实现关系
接口和实现类之间的关系。类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。
在UML类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。如:汽车和船实现了交通工具。
2.设计原则
1.开闭原则
对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果(类似USB接口)。
要达到这样的效果,需要使用接口和抽象类。因为抽象类灵活性好,适应性广,只要抽象的合理,可以基本保证软件架构的稳定。而软件中易变的细节可以从抽象派生出来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。
例:
public abstract class AbstractSkin {
public abstract void display();
}
public class DefaultSkin extends AbstractSkin {
public void display() {
System.out.println("默认");
}
}
public class HzjSkin extends AbstractSkin{
public void display() {
System.out.println("新");
}
}
public class SougoInput {
private AbstractSkin skin;
public void setSkin(AbstractSkin skin) {
this.skin = skin;
}
public void display() {
skin.display();
}
}
public class Client {
public static void main(String[] args) {
// 创建输入法对象
SougoInput input = new SougoInput();
// 创建皮肤对象
DefaultSkin skin = new DefaultSkin();
// 将皮肤设置到输入法中
input.setSkin(skin);
// 显示皮肤
input.display();
}
}
当功能有新的扩展时,只需要在写一个继承类实现方法,在Client类中进行修改即可,无需修改原型实现类。
2.里氏代换原则
任何基类可以出现的地方,子类一定可以出现。即子类可以扩展父类的功能,但不能改变父类原有的功能。子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。(除非抽象方法)
如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁是,程序运行出错的概率会非常大。
例:
/**
* 四边形类接口
*
*/
public interface Quadrilateral {
double getLength();
double getWidth();
}
/**
* 长方形类
*
*/
public class Rectangle implements Quadrilateral{
private double length;
private double width;
public void setLength(double length) {
this.length = length;
}
public void setWidth(double width) {
this.width = width;
}
@Override
public double getLength() {
return length;
}
@Override
public double getWidth() {
return width;
}
}
/**
* 正方形类
*
*/
public class Square implements Quadrilateral {
private double side;
public double getSide() {
return side;
}
public void setSide(double side) {
this.side = side;
}
@Override
public double getLength() {
return side;
}
@Override
public double getWidth() {
return side;
}
}
/**
* 调用类
*
*/
public class RectangleDemo {
public static void main(String[] args) {
// 创建长方形对象
Rectangle r = new Rectangle();
// 设置长和宽
r.setWidth(10);
r.setLength(20);
// 调用resize进行扩宽
resize(r);
printLengthAndWidth(r);
System.out.println("=====================");
// 创建正方形对象
Square s = new Square();
// 设置长和宽
s.setSide(10);
printLengthAndWidth(s);
}
// 扩宽的方法
public static void resize(Rectangle rectangle) {
// 判断宽如果比长小,进行扩宽的操作
while (rectangle.getWidth() <= rectangle.getLength()) {
rectangle.setWidth(rectangle.getWidth() + 1);
}
}
// 打印长和宽
public static void printLengthAndWidth(Quadrilateral quadrilateral) {
System.out.println(quadrilateral.getLength());
System.out.println(quadrilateral.getWidth());
}
}
3.依赖倒转原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。要求对抽象进行编程,不要对实现进行编程,降低客户与实现模块间的耦合。
/**
* cpu接口
*
*/
public interface Cpu {
// 运行cpu
public void run();
}
/**
* Intel处理器
*
*/
public class IntelCpu implements Cpu {
@Override
public void run() {
System.out.println("使用Intel处理器");
}
}
/**
* 硬盘接口
*
*/
public interface HardDisk {
// 存储数据
public void save(String data);
// 读取数据
public String get();
}
/**
* 希捷硬盘类
*
*/
public class XiJieHardDisk implements HardDisk {
@Override
public void save(String data) {
System.out.println("希捷硬盘保存数据");
}
@Override
public String get() {
System.out.println("希捷硬盘读取数据");
return "数据";
}
}
/**
* 内存条接口
*
*/
public interface Memory {
public void save();
}
/**
* 金士顿内存条
*
*/
public class KingstonMemory implements Memory {
@Override
public void save() {
System.out.println("使用金士顿内存条");
}
}
/**
* 电脑
*
*/
public class Computer {
private HardDisk hardDisk;
private Cpu cpu;
private Memory memory;
public HardDisk getHardDisk() {
return hardDisk;
}
public void setHardDisk(HardDisk hardDisk) {
this.hardDisk = hardDisk;
}
public Cpu getCpu() {
return cpu;
}
public void setCpu(Cpu cpu) {
this.cpu = cpu;
}
public Memory getMemory() {
return memory;
}
public void setMemory(Memory memory) {
this.memory = memory;
}
public void run() {
System.out.println("运行计算机");
String data = hardDisk.get();
System.out.println("从硬盘读取的数据是" + data);
cpu.run();
memory.save();
}
}
/**
* TODO
*
*/
public class ComputerDemo {
public static void main(String[] args) {
// 创建计算机的组件对象
HardDisk hardDisk = new XiJieHardDisk();
Cpu cpu = new IntelCpu();
Memory memory = new KingstonMemory();
// 创建计算机对象
Computer c = new Computer();
// 组装计算机
c.setCpu(cpu);
c.setHardDisk(hardDisk);
c.setMemory(memory);
c.run();
}
}
4.接口隔离原则
客户端不应该被迫依赖于它不使用的方法,一个类对另一个类的依赖应该建立在最小的接口上。
/**
* 防盗接口
*
* @see [相关类/方法]
* @since 2024/9/10
*/
public interface AntiTheft {
void antiTheft();
}
/**
* 防火接口
*
* @see [相关类/方法]
* @since 2024/9/10
*/
public interface Fireproof {
void fireProof();
}
/**
* 防水接口
*
* @see [相关类/方法]
* @since 2024/9/10
*/
public interface Waterproof {
void waterproof();
}
/**
* 全能安全门
*
* @see [相关类/方法]
* @since 2024/9/10
*/
public class AlmightySafetyDoor implements AntiTheft, Fireproof, Waterproof {
@Override
public void antiTheft() {
System.out.println("防盗");
}
@Override
public void fireProof() {
System.out.println("防火");
}
@Override
public void waterproof() {
System.out.println("防水");
}
}
/**
* 普通安全门
*
* @see [相关类/方法]
* @since 2024/9/10
*/
public class CommonSafetyDoor implements AntiTheft, Fireproof {
@Override
public void antiTheft() {
System.out.println("防盗");
}
@Override
public void fireProof() {
System.out.println("防火");
}
}
/**
* TODO
*
* @see [相关类/方法]
* @since 2024/9/10
*/
public class Client {
public static void main(String[] args) {
// 创建全能安全门对象
AlmightySafetyDoor door = new AlmightySafetyDoor();
// 调用功能
door.antiTheft();
door.fireProof();
door.waterproof();
System.out.println("==========================");
// 创建普通安全门
CommonSafetyDoor door1 = new CommonSafetyDoor();
// 调用功能
door1.antiTheft();
door1.fireProof();
}
}
5.迪米特法则
最少知识原则。
只和你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)
如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。目的是降低类之间的耦合度,提供模块的相对独立性。
朋友: 当前对象本身,当前对象的成员对象,当前对象所创建的对象,当前对象的方法参数等,这些对象同当前对象存在关联,聚合或组合关系,可以直接访问这些对象的方法。
/**
* 明星类
*
* @see [相关类/方法]
* @since 2024/9/10
*/
public class Star {
private String name;
public Star(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
/**
* 粉丝类
*
* @see [相关类/方法]
* @since 2024/9/10
*/
public class Fans {
private String name;
public Fans(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
/**
* 公司类
*
* @see [相关类/方法]
* @since 2024/9/10
*/
public class Company {
private String name;
public Company(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
/**
* 经纪人类
*
* @see [相关类/方法]
* @since 2024/9/10
*/
public class Agent {
private Star star;
private Fans fans;
private Company company;
public void setStar(Star star) {
this.star = star;
}
public void setFans(Fans fans) {
this.fans = fans;
}
public void setCompany(Company company) {
this.company = company;
}
// 和粉丝见面的方法
public void meeting() {
System.out.println(star.getName() + "和粉丝" + fans.getName() + "见面");
}
// 和媒体公司洽谈的方法
public void business() {
System.out.println(star.getName() + "和" + company.getName() + "公司洽谈");
}
}
/**
* TODO
*
* @see [相关类/方法]
* @since 2024/9/10
*/
public class Client {
public static void main(String[] args) {
// 创建经纪人类
Agent agent = new Agent();
// 创建明星对象
Star star = new Star("林青霞");
agent.setStar(star);
// 创建粉丝类
Fans fans = new Fans("李四");
agent.setFans(fans);
// 创建媒体公司对象
Company company = new Company("麻豆传媒");
agent.setCompany(company);
agent.meeting();
agent.business();
}
}
6.合成复用原则
尽量先使用组合或聚合等关联关系来实现,其次才考虑使用继承关系来实现。
通常类的复用分为继承复用和合成复用两种。
继承复用虽然简单易实现,但也存在缺点:
1.继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
2.子类和父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展和维护。
3.它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。
采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,优点:
1.它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
2.对象的耦合度低。可以在类的成员位置声明抽象。
3.复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态的引用于成分对象类型相同的对象。
3.创建者模式
1.单例模式(Singleton Pattern)
提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象。
1.结构
单例类:只能创建一个实例的类
访问类:使用单例类
2.实现
1.饿汉式
类加载就会导致该单实例对象被创建
1.静态变量方式
/**
* 饿汉式:静态成员变量式
*
*/
public class Singleton {
// 1.私有构造方法
private Singleton() {}
// 2.在本类中创建本类对象
private static Singleton instance = new Singleton();
// 3.提供一个公共的访问方式,让外界获取该对象
public static Singleton getInstance() {
return instance;
}
}
2. 静态代码块方式
/**
* 饿汉式:静态代码块
*
*/
public class Singleton {
// 私有构造方法
private Singleton() {}
// 声明Singleton类型的变量
private static Singleton instance;
// 在静态代码块中进行赋值
static {
instance = new Singleton();
}
// 对外提供获取该类对象的方法
public static Singleton getInstance() {
return instance;
}
}
2.懒汉式
类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
1.线程不安全
/**
* 懒汉式:线程不安全
*
*/
public class Singleton {
// 私有构造方法
private Singleton() {}
// 声明Singleton类型的变量
private static Singleton instance;
// 对外提供访问方式
public static Singleton getInstance() {
// 判断instance是否为null,如果为null,说明还没有创建Singleton对象
if (instance == null) {
// 线程1等待,线程2获取到cpu的执行权,也会进入到该判断里面
return new Singleton();
}
return instance;
}
}
2.线程安全
方法上面加个锁
/**
* 懒汉式:线程不安全
*
*/
public class Singleton {
// 私有构造方法
private Singleton() {}
// 声明Singleton类型的变量
private static Singleton instance;
// 对外提供访问方式
public static synchronized Singleton getInstance() {
// 判断instance是否为null,如果为null,说明还没有创建Singleton对象
if (instance == null) {
// 线程1等待,线程2获取到cpu的执行权,也会进入到该判断里面, 在方法上面加个锁
return new Singleton();
}
return instance;
}
}
3.双重检查锁(推荐)
对于getInstance()方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以没必要让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。故产生双重检查锁。
volatile可以保证可见性和有序性。可以保证在多线程情况下线程安全也不会有性能问题。
/**
* 双重检查锁
*
*/
public class Singleton {
private Singleton() {}
// volatile保证操作的有序性,防止多线程情况下,JVM实例化对象时进行优化和指令重排导致空指针
private static volatile Singleton instance;
public static Singleton getInstance() {
// 第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象
if (instance == null) {
synchronized (Singleton.class) {
// 第二次判断
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
4.静态内部类(推荐)
静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序。
/**
* 静态内部类方式
*
*/
public class Singleton {
private Singleton() {}
// 定义一个静态内部类
private static class SingletonHolder {
// 在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
// 提供公共访问方式
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证Singleton类的唯一性。
静态内部类单例模式在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
7.枚举方式(推荐)(饿汉)
极力推荐,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所有单例实现中唯一不会被破坏的单例模式
public enum Singleton {
INSTANCE;
}
3.存在的问题
破坏单例模式
1.序列化破坏单例模式
每次从文件读取出来的对象都是新对象,破坏了单例模式
public class Client {
public static void main(String[] args) throws IOException, ClassNotFoundException {
writeObject2File();
readObjectFromFile(); // 每执行一次都是新的对象,破坏了单例模式
readObjectFromFile();
}
// 从文件读取数据(对象)
public static void readObjectFromFile() throws IOException, ClassNotFoundException {
// 创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\learn\\a.txt"));
// 读取对象
Singleton instance = (Singleton) ois.readObject();
// 释放资源
ois.close();
System.out.println(instance);
}
// 向文件中写数据(对象)
public static void writeObject2File() throws IOException {
// 获取Singleton对象
Singleton instance = Singleton.getInstance();
// 创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\learn\\a.txt"));
// 写对象
oos.writeObject(instance);
// 释放资源
oos.close();
}
}
2.反射破坏单例模式
每次通过反射创建的对象都是新对象
public class Client {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 获取Singleton的字节码对象
Class clazz = Singleton.class;
// 获取无参构造方法对象
Constructor cons = clazz.getDeclaredConstructor();
// 取消访问检查
cons.setAccessible(true);
// 创建Singleton对象
Singleton s1 = (Singleton) cons.newInstance();
Singleton s2 = (Singleton) cons.newInstance();
System.out.println(s1 == s2);
}
}
3.序列化解决方法
在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
原理:反序列化读取文件时调用的readObject方法,内部会检查是否存在readResolve方法,如果存在则会调用该方法。在类中重写该方法后,即可避免反序列化破坏单例。
public class Singleton implements Serializable {
// 私有构造方法
private Singleton() {}
// 声明Singleton类型的变量
private static Singleton instance;
// 在静态代码块中进行赋值
static {
instance = new Singleton();
}
// 对外提供获取该类对象的方法
public static Singleton getInstance() {
return instance;
}
// 当进行反序列化是,会自动调用该方法,将该方法的返回值直接返回
public Object readResolve() {
return instance;
}
}
部分源码
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
passHandle = handles.assign(unshared ? unsharedMarker : obj);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(passHandle, resolveEx);
}
if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc);
} else {
readSerialData(obj, desc);
}
handles.finish(passHandle);
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
4.反射解决方法
public class Singleton {
private static boolean flag = false;
private Singleton() {
// 判断flag的值是否是true,如果是true,说明非第一次访问,直接抛一个异常,如果是false的话,说明是第一次访问
synchronized (Singleton.class) {
if (flag) {
throw new RuntimeException("不能创建多个对象");
}
flag = true;
}
}
// 定义一个静态内部类
private static class SingletonHolder {
// 在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
// 提供公共访问方式
public static Singleton getInstance() {
return Singleton.SingletonHolder.INSTANCE;
}
}
2.工厂模式
在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,显然违背了软件设计的开闭原则。如果使用工厂来生产对象,就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的。工厂模式最大优点:解耦
1.简单工厂模式
简单工厂不是一种设计模式,反而比较像是一种编程习惯
1.结构
抽象产品:定义了产品的规范,描述了产品的主要特性和功能
具体产品:实现或继承抽象产品的子类
具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品
2.实现
public abstract class Coffee {
public abstract String getName();
public void addSugar() {
System.out.println("加糖");
}
public void addMilk() {
System.out.println("加奶");
}
}
/**
* 美式咖啡
*
*/
public class AmericanCoffee extends Coffee {
public String getName() {
return "美式";
}
}
/**
* 拿铁咖啡
*
*/
public class LatteCoffee extends Coffee {
public String getName() {
return "拿铁";
}
}
/**
* 简单咖啡工厂,用来生成咖啡
*
*/
public class SimpleCoffeeFactory {
public Coffee createCoffee(String type) {
// 声明coffee类型的变量,根据不同类型创建不同的coffee子类对象
Coffee coffee = null;
if ("american".equals(type)) {
coffee = new AmericanCoffee();
} else if ("latte".equals(type)) {
coffee = new LatteCoffee();
} else {
throw new RuntimeException("没有");
}
return coffee;
}
}
/**
* 咖啡店
*
*/
public class CoffeeStore {
public Coffee orderCoffee(String type) {
// 工厂类
SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
// 调用生成咖啡的方法
Coffee coffee = factory.createCoffee(type);
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
public class Client {
public static void main(String[] args) {
// 创建咖啡店类
CoffeeStore coffeeStore = new CoffeeStore();
Coffee latte = coffeeStore.orderCoffee("latte");
System.out.println(latte.getName());
}
}
3.优缺点
优点:封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
4.扩展-静态工厂
将工厂类中创建对象的功能定义为静态的
/**
* 简单咖啡工厂,用来生成咖啡
*
*/
public class SimpleCoffeeFactory {
public static Coffee createCoffee(String type) {
// 声明coffee类型的变量,根据不同类型创建不同的coffee子类对象
Coffee coffee = null;
if ("american".equals(type)) {
coffee = new AmericanCoffee();
} else if ("latte".equals(type)) {
coffee = new LatteCoffee();
} else {
throw new RuntimeException("没有");
}
return coffee;
}
}
2.工厂方法模式
定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类
1.结构
抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂一一对应。
2.实现
/**
* 咖啡类
*
*/
public abstract class Coffee {
public abstract String getName();
public void addSugar() {
System.out.println("加糖");
}
public void addMilk() {
System.out.println("加奶");
}
}
/**
* 美式咖啡
*
*/
public class AmericanCoffee extends Coffee {
public String getName() {
return "美式";
}
}
/**
* 拿铁咖啡
*
*/
public class LatteCoffee extends Coffee {
public String getName() {
return "拿铁";
}
}
/**
* 抽象工厂
*
*/
public interface CoffeeFactory {
// 创建咖啡对象的方法
Coffee createCoffee();
}
/**
* 美式咖啡工厂对象,专门用来生成美式咖啡
*
*/
public class AmericanCoffeeFactory implements CoffeeFactory{
@Override
public Coffee createCoffee() {
return new AmericanCoffee();
}
}
/**
* 拿铁咖啡工厂
*
*/
public class LatteCoffeeFactory implements CoffeeFactory{
@Override
public Coffee createCoffee() {
return new LatteCoffee();
}
}
/**
* 咖啡店
*
*/
public class CoffeeStore {
private CoffeeFactory factory;
public void setFactory(CoffeeFactory factory) {
this.factory = factory;
}
// 点咖啡
public Coffee orderCoffee() {
Coffee coffee = factory.createCoffee();
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
public class Client {
public static void main(String[] args) {
// 创建咖啡店对象
CoffeeStore store = new CoffeeStore();
// 创建对象
CoffeeFactory factory = new AmericanCoffeeFactory();
store.setFactory(factory);
// 点咖啡
Coffee coffee = store.orderCoffee();
System.out.println(coffee.getName());
}
}
3.优缺点
优点:1.用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。2.在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则
缺点:每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,增加了系统的复杂度。
3.抽象工厂模式
同种类产品称为同等级产品,同一个工厂生产的不同等级的一组产品称为一个产品族。上面的工厂模式只支持生成同等级的产品,抽象工厂将考虑生产不同等级的产品族。
1.概念
是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
2.结构
抽象工厂(Abstract Factory):提供了创建产品的接口,包含多个创建产品的方法,可以创建多个不同等级的产品。
具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,同具体工厂之间是多对一的关系。
3.实现
/**
* 咖啡类
*
*/
public abstract class Coffee {
public abstract String getName();
public void addSugar() {
System.out.println("加糖");
}
public void addMilk() {
System.out.println("加奶");
}
}
/**
* 美式咖啡
*
*/
public class AmericanCoffee extends Coffee {
public String getName() {
return "美式";
}
}
/**
* 拿铁咖啡
*
*/
public class LatteCoffee extends Coffee {
public String getName() {
return "拿铁";
}
}
/**
* 甜品抽象类
*
*/
public abstract class Dessert {
public abstract void show();
}
/**
* 提拉米苏类
*
*/
public class Trimisu extends Dessert{
@Override
public void show() {
System.out.println("提拉米苏");
}
}
/**
* 抹茶慕斯
*
*/
public class MatchaMousse extends Dessert{
@Override
public void show() {
System.out.println("抹茶慕斯");
}
}
/**
* 抽象工厂类
*
*/
public interface DessertFactory {
// 生产咖啡
Coffee createCoffee();
// 生产甜品
Dessert createDessert();
}
/**
* 美式风味的工厂类
* 生产美式咖啡和抹茶慕斯
*
*/
public class AmericanDessertFactory implements DessertFactory{
@Override
public Coffee createCoffee() {
return new AmericanCoffee();
}
@Override
public Dessert createDessert() {
return new MatchaMousse();
}
}
/**
* 意大利风味工厂
* 生产拿铁和提拉米苏
*
*/
public class ItalyDessertFactory implements DessertFactory{
@Override
public Coffee createCoffee() {
return new LatteCoffee();
}
@Override
public Dessert createDessert() {
return new Trimisu();
}
}
public class Client {
public static void main(String[] args) {
// 创建意大利风味甜品工厂
ItalyDessertFactory factory = new ItalyDessertFactory();
// 获取拿铁和提拉米苏
Coffee coffee = factory.createCoffee();
Dessert dessert = factory.createDessert();
System.out.println(coffee.getName());
dessert.show();
}
}
4.优缺点
优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:当产品族中需要增加一个新的产品时,所有的工厂类都需要修改
5.使用场景
当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机,洗衣机等。
系统中有多个产品族,但每次只使用其中的某一族产品。如只穿某一个品牌的衣服和鞋。
系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
如:输入法换皮肤,一整套一起换,生成不同操作系统的程序。
4.模式扩展
简单工厂+配置文件 解除耦合
解除工厂对象和产品对象的耦合。在配置文件中配置文件的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。
静态成员变量用来存储创建的对象(键存储的是名称,值存储的是对应的对象),而读取配置文件以及创建对象写在静态代码块中,目的就是只执行一次。
american=com.hzj.pattern.factory.config_factory.AmericanCoffee
latte=com.hzj.pattern.factory.config_factory.LatteCoffee
/**
* 美式咖啡
*
*/
public class AmericanCoffee extends Coffee {
public String getName() {
return "美式";
}
}
/**
* 拿铁咖啡
*
*/
public class LatteCoffee extends Coffee {
public String getName() {
return "拿铁";
}
}
/**
* 工厂类
*
*/
public class CoffeeFactory {
// 加载配置文件,获取配置文件中配置的全类名,并创建该类的对象进行存储
// 定义容器对象存储咖啡对象
private static HashMap<String, Coffee> map = new HashMap<>();
// 加载配置文件,只需要加载一次
static {
// 创建Properties对象
Properties p = new Properties();
// 调用p对象中的load方法进行配置文件的加载
InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
p.load(is);
// 从p集合中获取全类名并创建对象
Set<Object> keySet = p.keySet();
for (Object key : keySet) {
String className = p.getProperty((String) key);
// 通过反射创建对象
Class clazz = Class.forName(className);
Coffee coffee = (Coffee) clazz.newInstance();
// 将名称和对象存储到容器中
map.put((String) key, coffee);
}
} catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException exception) {
exception.printStackTrace();
}
}
// 根据名称获取对象
public static Coffee createCoffee(String name) {
return map.get(name);
}
}
public class Client {
public static void main(String[] args) {
Coffee coffee = CoffeeFactory.createCoffee("american");
System.out.println(coffee.getName());
}
}
3.原型模式
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。原型模式就是克隆。
1.结构
抽象原型类:规定了具体原型对象必须实现的clone()方法
具体原型类:实现抽象原型类的clone()方法,它是可被复制的对象
访问类:使用具体原型类中的clone()方法来复制新的对象
2.实现
原型模式的克隆分为浅克隆和深克隆(默认是浅拷贝):
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其它对象也会被克隆,不再指向原有对象地址。
Java中的Object类中提供了clone()方法来实现浅克隆。Cloneable接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。代码如下
/**
* 具体的原型类
*
* @see [相关类/方法]
* @since 2024/9/14
*/
public class Realizetype implements Cloneable{
public Realizetype() {
System.out.println("具体的原型对象创建完成");
}
@Override
public Realizetype clone() {
try {
System.out.println("具体原型复制成功");
return (Realizetype) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
/**
* TODO
*
* @see [相关类/方法]
* @since 2024/9/14
*/
public class Client {
public static void main(String[] args) {
// 创建一个原型类对象
Realizetype realizetype = new Realizetype();
// 调用原型类中的clone方法进行对象的克隆
final Realizetype clone = realizetype.clone();
System.out.println(realizetype == clone); // false
}
}
3.案例
用原型模式生成”三好学生”奖状
用以学校的“三好学生”奖状除了获奖人姓名不同,其它都相同,可以使用原型模式复制多个“三好学生”奖状出来,然后在修改奖状上的名字即可。
类图如下:
代码如下:
/**
* 奖状类
*
* @see [相关类/方法]
* @since 2024/9/18
*/
public class Citation implements Cloneable {
// 三好学生的姓名
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Citation clone() {
try {
return (Citation) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
public void show() {
System.out.println("name = " + name);
}
}
public class CitationTest {
public static void main(String[] args) {
// 创建原型对象
Citation citation = new Citation();
// 克隆奖状对象
Citation citation1 = citation.clone();
citation.setName("张三");
citation1.setName("李四");
// 调用show方法展示
citation.show();
citation1.show();
}
}
4.使用场景
对象的创建非常复杂,可以使用原型模式快捷的创建对象。
性能和安全要求比较高
5.扩展(深克隆)
通过序列号和反序列的方式实现深克隆
/**
* 奖状类
*
* @see [相关类/方法]
* @since 2024/9/18
*/
public class Citation implements Cloneable, Serializable {
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
@Override
public Citation clone() {
try {
return (Citation) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
public void show() {
System.out.println("name = " + student.getName());
}
}
/**
* TODO
*
* @see [相关类/方法]
* @since 2024/9/18
*/
public class Student implements Serializable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
public class CitationTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 创建原型对象
Citation citation = new Citation();
// 创建张三学生对象
Student student = new Student();
student.setName("张三");
citation.setStudent(student);
// 创建对象输出类对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("文件路径"));
// 写对象
oos.writeObject(citation);
// 释放资源
oos.close();
// 创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("文件路径"));
// 读取对象
final Citation citation1 = (Citation) ois.readObject();
// 释放资源
ois.close();
final Student student1 = citation1.getStudent();
student1.setName("李四");
// 调用show方法展示
citation.show();
citation1.show();
}
}
4.建造者模式
1.概述
将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
分离了部件的构造(由Builder来负责)和装配(由Director负责)。从而可以构建出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法,装配算法的解耦,实现了更好的复用。
建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
2.结构
抽象建造者类(Builder):这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的部件对象的创建。
具体建造者类(ConcreteBuilder):实现了Builder接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
产品类(Product):要创建的复杂对象。
指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
3.实现
创建共享单车
生成自行车是一个复杂的过程,它包含了车架,车座等组件的生成。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生成就可以使用建造者模式。
这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和OfoBuilder是具体的建造者;Director是指挥者。
/**
* 自行车产品类
*
* @see [相关类/方法]
* @since 2024/9/18
*/
public class Bike {
// 车架
private String frame;
// 车座
private String seat;
public String getFrame() {
return frame;
}
public void setFrame(String frame) {
this.frame = frame;
}
public String getSeat() {
return seat;
}
public void setSeat(String seat) {
this.seat = seat;
}
}
/**
* 抽象建造者类
*
* @see [相关类/方法]
* @since 2024/9/18
*/
public abstract class Builder {
// 声明Bike类型的变量并进行赋值
protected Bike bike = new Bike();
// 构建车架
public abstract void buildFrame();
// 构建车座
public abstract void buildSeat();
// 创建自行车的方法
public abstract Bike createBike();
}
/**
* 具体的构建者,用来构建摩拜单车对象
*
* @see [相关类/方法]
* @since 2024/9/18
*/
public class MobileBuilder extends Builder {
@Override
public void buildFrame() {
bike.setFrame("碳纤维车架");
}
@Override
public void buildSeat() {
bike.setSeat("真皮车座");
}
@Override
public Bike createBike() {
return bike;
}
}
/**
* Ofo单车构建者
*
* @see [相关类/方法]
* @since 2024/9/19
*/
public class OfoBuilder extends Builder {
@Override
public void buildFrame() {
bike.setFrame("铝合金车架");
}
@Override
public void buildSeat() {
bike.setSeat("橡胶车座");
}
@Override
public Bike createBike() {
return bike;
}
}
/**
* 指挥者类
*
* @see [相关类/方法]
* @since 2024/9/19
*/
public class Director {
// 声明builder类型的变量
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
// 组装自行车的功能
public Bike construct() {
builder.buildFrame();
builder.buildSeat();
return builder.createBike();
}
}
public class Client {
public static void main(String[] args) {
// 创建指挥者对象
Director director = new Director(new MobileBuilder());
// 让指挥者指挥组装自行车
final Bike bike = director.construct();
System.out.println(bike.getFrame());
System.out.println(bike.getSeat());
}
}
4.优缺点
优点:
1.建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
2.在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身于产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
3.可以更加精细的控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
4.创建者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。
缺点:
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
5.使用场景
建造者模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们结合在一起的算法却相对稳定,所以它通常在以下场合使用:
1.创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
2.创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
6.模式扩展
建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时可以利用建造者模式进行重构。
重构前代码
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainBoard;
public Phone(String cpu, String screen, String memory, String mainBoard) {
this.cpu = cpu;
this.screen = screen;
this.memory = memory;
this.mainBoard = mainBoard;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getScreen() {
return screen;
}
public void setScreen(String screen) {
this.screen = screen;
}
public String getMemory() {
return memory;
}
public void setMemory(String memory) {
this.memory = memory;
}
public String getMainBoard() {
return mainBoard;
}
public void setMainBoard(String mainBoard) {
this.mainBoard = mainBoard;
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainBoard='" + mainBoard + '\'' +
'}';
}
}
重构之后
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainBoard;
// 私有构造方法
private Phone(Builder builder) {
this.cpu = builder.cpu;
this.screen = builder.screen;
this.memory = builder.memory;
this.mainBoard = builder.mainBoard;
}
public static final class Builder {
private String cpu;
private String screen;
private String memory;
private String mainBoard;
public Builder cpu(String cpu) {
this.cpu = cpu;
return this;
}
public Builder screen(String screen) {
this.screen = screen;
return this;
}
public Builder memory(String memory) {
this.memory = memory;
return this;
}
public Builder mainBoard(String mainBoard) {
this.mainBoard = mainBoard;
return this;
}
// 使用构建者创建phone对象
public Phone build() {
return new Phone(this);
}
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainBoard='" + mainBoard + '\'' +
'}';
}
}
public class Client {
public static void main(String[] args) {
// 创建手机对象
Phone phone = new Phone.Builder()
.cpu("intel")
.screen("三星")
.memory("内存条")
.mainBoard("主板")
.build();
System.out.println(phone);
}
}
在实体类上面添加@Builder注解可以达到相同效果,无需自己封装。
5.创建者模式对比
1.工厂方法模式VS建造者模式
工厂方法模式注重的是整体对象的创建方式,注重结果;而建造者模式注重的是部件构建的过程,意在通过一步一步的精确构造创建出一个复杂的对象,注重过程。
例:如要制造一个超人,使用工厂模式,直接产生出来的就是一个力大无穷,能飞,裤衩外穿的超人;使用建造者模式,则需要组装手,脚,躯干等部分,然后在穿内裤,最后一个超人诞生。
2.抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心产品用什么工厂生成即可。
建造者模式则是要求按照指定的蓝图建造产品,主要目的是通过组装零配件而产生一个新产品。
如果将抽象工厂看成汽车配件生成工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。
4.结构型模式
结构型模式描述如何将类或对象按某种布局组成更大的结构。分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者采用组合或聚合来组合对象。
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则“,所以对象结构型模式比类结构型模式具有更大的灵活性。
1.代理模式
1.概述
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
2.结构
1.抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
2.真实主题(Real Subject)类:实现了抽象主题中的具体业务,时代理对象所代表的真实对象,是最终要引用的对象。
3.代理(Proxy)类:提供了与真实主题相同的接口,起内部含有对真实主题的引用,它可以访问,控制或扩展真实主题的功能。
3.静态代理
案例:火车站卖票
如果要买火车票的话,需要取火车站买票,坐车去火车站,排队等一系列的操作,显然比较麻烦。而火车站在很多个地方都有代售点,我们去代售点买票就方便多了。即火车站是目标对象,代售点是代理对象。
/**
* 卖火车票的接口
*
* @see [相关类/方法]
* @since 2024/9/21
*/
public interface SellTickets {
void sell();
}
/**
* 火车站类
*
* @see [相关类/方法]
* @since 2024/9/21
*/
public class TrainStation implements SellTickets{
@Override
public void sell() {
System.out.println("火车站买票");
}
}
/**
* 代售点类
*
* @see [相关类/方法]
* @since 2024/9/21
*/
public class ProxyPoint implements SellTickets {
// 声明火车站对象
private TrainStation trainStation = new TrainStation();
@Override
public void sell() {
System.out.println("代售点收取一些服务费");
trainStation.sell();
}
}
public class Client {
public static void main(String[] args) {
// 创建代售点类对象
ProxyPoint proxyPoint = new ProxyPoint();
// 调用方法进行买票
proxyPoint.sell();
}
}
测试类直接访问ProxyPoint 类对象,ProxyPoint作为访问对象和目标对象的中介,避免了直接访问目标对象,同时也增强了sell方法。
4.JDK动态代理
Java中提供了应该动态代理类Proxy,该类提供了应该创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。目标对象必须实现接口。
案例同上。
/**
* 卖火车票的接口
*
* @see [相关类/方法]
* @since 2024/9/21
*/
public interface SellTickets {
void sell();
}
public class TrainStation implements SellTickets {
@Override
public void sell() {
System.out.println("火车站买票");
}
}
/**
* 获取代理对象的工厂类
* 代理类也实现了对于的接口
*
* @see [相关类/方法]
* @since 2024/9/23
*/
public class ProxyFactory {
// 声明目标对象
private TrainStation station = new TrainStation();
public SellTickets getProxyObject() {
// 返回代理对象即可
// ClassLoader loader: 类加载器,用于加载代理类。可以通过目标对象获取类加载器
// Class<?>[] interfaces: 代理类实现的接口的字节码对象
// InvocationHandler h: 代理对象的调用处理程序
/*
* Object proxy: 代理对象,和proxyObject对象是同一个对象,在invoke方法中基本不用
* Method method: 对接口中的方法进行封装的method对象
* Object[] args: 调用方法的实际参数
*
* 返回值: 方法的返回值
* */
final SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(
station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
(proxy, method, args) -> {
// System.out.println("invoke方法执行了");
System.out.println("代售点收取服务费(jdk动态代理)");
// 执行目标对象的方法
final Object invoke = method.invoke(station, args);
return invoke;
});
return proxyObject;
}
}
public class Client {
public static void main(String[] args) {
// 获取代理对象
// 1.创建代理工厂对象
ProxyFactory factory = new ProxyFactory();
// 2.使用factory对象的方法获取代理对象
final SellTickets proxyObject = factory.getProxyObject();
// 3.调用卖票的方法
proxyObject.sell();
}
}
ProxyFactory不是代理模式中的代理类,而代理类是程序在运行过程中动态的在内存中生成的类。可以通过Arthas的jad方法在程序运行时去观察代理类(proxyObject类)。
代理类($Proxy0)实现了SellTickets接口,将我们提供的匿名内部类对象传递给了父类。
执行流程:
1.在测试类中通过代理对象调用sell()方法
2.根据多态的特性,执行的是代理类中的sell()方法。
3.代理类中的sell()方法这又调用了InvocationHandler接口的子实现类对象的invoke方法
4.invoke方法通过反射执行了真实对象所属类中(TrainStation)的sell()方法
5.CGLIB动态代理
如果没有定义SellTickets接口,只定义了TrainStation类时,JDK代理则无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。
CGLIB为没有实现接口的类提供代理,为JDK动态代理提供了很好的补充。
案例如上:
引入maven
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
/**
* 火车站类
*
* @see [相关类/方法]
* @since 2024/9/21
*/
public class TrainStation {
public void sell() {
System.out.println("火车站买票");
}
}
/**
* 代理对象工厂,用来获取代理对象
*
* @see [相关类/方法]
* @since 2024/9/23
*/
public class ProxyFactory implements MethodInterceptor {
// 声明火车站对象
private TrainStation station = new TrainStation();
public TrainStation getProxyObject() {
// 创建Enhancer对象,类似于jdk代理中的Proxy类
Enhancer enhancer = new Enhancer();
// 设置父类的字节码对象,指定父类
enhancer.setSuperclass(TrainStation.class);
// 设置回调函数
enhancer.setCallback(this);
// 创建代理对象
final TrainStation proxyObject = (TrainStation) enhancer.create();
return proxyObject;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// System.out.println("方法执行了");
System.out.println("代售点收取服务费(CGLIB代理)");
// 要调用目标对象的方法
final Object invoke = method.invoke(station, objects);
return invoke;
}
}
public class Client {
public static void main(String[] args) {
// 创建代理工厂对象
ProxyFactory factory = new ProxyFactory();
// 获取代理对象 获取到的类是TrainStation的子类对象
final TrainStation proxyObject = factory.getProxyObject();
// 调用代理对象中的卖票方法
proxyObject.sell();
}
}
代理工厂必须实现MethodInterceptor接口。使用Enhancer类代理Proxy类进行代理对象的创建。
6.三种代理的对比
1.jdk代理和CGLIB代理
使用CGLIB实现动态代理,CGLIB底层采用AMS字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用JAVA反射效率要高。CGLIB不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。使用时,有接口则用jdk动态代理,没有接口则用CGLib代理。
2.静态代理和动态代理
动态代理最大的好处是接口或类中声明的所用方法都被转移到调用处理器一个集中的方法中处理。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题。
7.优缺点
优点:
1.代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
2.代理对象可以扩展目标对象的功能
3.代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度
缺点:
增加了系统的复杂度
8.使用场景
1.远程代理(Remote)
本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代理设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程访问提供的功能,而不必过多关心通信部分的细节。如:RPC调用。
2.防火墙代理(Firewall)
当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网反应响应时,代理服务器再把它转给你的浏览器。如:VPN
3.保护代理(Protect or Access)
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
2.适配器(Adapter)模式
1.概述
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配器模式分为适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对少一些。
2.结构
目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
3.类适配器模式
实现方式:定义一个适配器类来实现当前系统的业务接口(目标接口),同时又继承现有组件库中已经存在的组件(适配者类)。
案例:读卡器。现有一台电脑指定读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来。
电脑要的是SD卡,所以目标接口是SD卡。要把TF卡适配为SD卡,所以TF是适配者类。
/**
* 目标接口
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public interface SDCard {
// 从SD卡中读取数据
String readSD();
// 往SD卡中写数据
void writeSD(String msg);
}
/**
* 具体的SD卡类
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public class SDCardImpl implements SDCard {
@Override
public String readSD() {
return "SDCard read msg : hello word SDCard";
}
@Override
public void writeSD(String msg) {
System.out.println("SDCard write msg: " + msg);
}
}
/**
* 适配者类接口
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public interface TFCard {
// 从TF卡中读取数据
String readTF();
// 往TF卡中写数据
void writeTF(String msg);
}
/**
* 适配者类
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public class TFCardImpl implements TFCard{
@Override
public String readTF() {
return "TFCard read msg: hello word TFCard";
}
@Override
public void writeTF(String msg) {
System.out.println("TFCard write msg: " + msg);
}
}
/**
* 计算机类
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public class Computer {
// 从SD卡中读取数据
public String readSD(SDCard sdCard) {
if (sdCard == null) {
throw new NullPointerException("SDCard is not null");
}
return sdCard.readSD();
}
}
/**
* 适配器类
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public class SDAdapterTF extends TFCardImpl implements SDCard {
@Override
public String readSD() {
System.out.println("adapter read tf card");
return readTF();
}
@Override
public void writeSD(String msg) {
System.out.println("adapter write tf card");
writeTF(msg);
}
}
public class Client {
public static void main(String[] args) {
// 创建计算机对象
Computer computer = new Computer();
// 读取SD卡中的数据
final String readSD = computer.readSD(new SDCardImpl());
System.out.println(readSD);
System.out.println("======================================");
// 使用该电脑读取TF卡中的数据
// 定义适配类
final String readTF = computer.readSD(new SDAdapterTF());
System.out.println(readTF);
}
}
类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用,因为类只能单继承,适配器类需要同时继承和实现,所以必须有一个接口。
4.对象适配器模式
实现方式:对象适配器模式可采用将现有组件库中已经实现的组件引入适配器类中(将类适配者模式的继承改为聚合),该类同时实现当前系统的业务接口。
案例同上
/**
* 目标接口
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public interface SDCard {
// 从SD卡中读取数据
String readSD();
// 往SD卡中写数据
void writeSD(String msg);
}
/**
* 具体的SD卡类
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public class SDCardImpl implements SDCard {
@Override
public String readSD() {
return "SDCard read msg : hello word SDCard";
}
@Override
public void writeSD(String msg) {
System.out.println("SDCard write msg: " + msg);
}
}
/**
* 适配者类接口
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public interface TFCard {
// 从TF卡中读取数据
String readTF();
// 往TF卡中写数据
void writeTF(String msg);
}
/**
* 适配者类
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public class TFCardImpl implements TFCard{
@Override
public String readTF() {
return "TFCard read msg: hello word TFCard";
}
@Override
public void writeTF(String msg) {
System.out.println("TFCard write msg: " + msg);
}
}
/**
* 计算机类
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public class Computer {
// 从SD卡中读取数据
public String readSD(SDCard sdCard) {
if (sdCard == null) {
throw new NullPointerException("SDCard is not null");
}
return sdCard.readSD();
}
}
/**
* 适配器类
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public class SDAdapterTF implements SDCard {
// 声明适配者类
private TFCard tfCard;
public SDAdapterTF(TFCard tfCard) {
this.tfCard = tfCard;
}
@Override
public String readSD() {
System.out.println("adapter read tf card");
return tfCard.readTF();
}
@Override
public void writeSD(String msg) {
System.out.println("adapter write tf card");
tfCard.writeTF(msg);
}
}
public class Client {
public static void main(String[] args) {
// 创建计算机对象
Computer computer = new Computer();
// 读取SD卡中的数据
final String readSD = computer.readSD(new SDCardImpl());
System.out.println(readSD);
System.out.println("======================================");
// 使用该电脑读取TF卡中的数据
// 创建适配器类对象
SDAdapterTF sdAdapterTF = new SDAdapterTF(new TFCardImpl());
final String readTF = computer.readSD(sdAdapterTF);
System.out.println(readTF);
}
}
5.接口适配器模式
当不希望实现一个接口中所用的方法时,可以创建一个抽象类Adapter,实现所有方法(空方法)。我们只需要继承该抽象类并重写需要的方法即可。
6.应用场景
以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
3.装饰者模式
1.概述
在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
2.结构
抽象构件(Component)角色: 定义一个抽象接口以规定准备接收附加责任的对象。
具体构件(Concrete Component)角色: 实现抽象构件,通过装饰角色为其添加一些职责。
抽象装饰(Decorator)角色: 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
具体装饰(ConcreteDecorator)角色: 实现抽象装饰的相关方法,并给具体构件对象添加附件的责任。
3.实现
案例:快餐店,快餐店有炒面,炒饭这些快餐,可以额外附加鸡蛋,火腿,培根等配菜,加配菜需要额外加钱,没个配菜的价格通常不一样,那么计算总价时就会比较麻烦。
/**
* 快餐类(抽象构件角色)
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public abstract class FastFood {
// 价格
private float price;
// 描述
private String desc;
public FastFood() {
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public FastFood(float price, String desc) {
this.price = price;
this.desc = desc;
}
public abstract float cast();
}
/**
* 炒饭类(具体构件角色)
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public class FriedRice extends FastFood {
public FriedRice() {
super(10, "炒饭");
}
@Override
public float cast() {
return getPrice();
}
}
/**
* 炒面(具体构件类)
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public class FriedNoodles extends FastFood {
public FriedNoodles() {
super(12, "炒面");
}
@Override
public float cast() {
return getPrice();
}
}
/**
* 装饰者类(抽象装饰角色)
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public abstract class Garnish extends FastFood {
// 声明快餐类的变量
private FastFood fastFood;
public FastFood getFastFood() {
return fastFood;
}
public void setFastFood(FastFood fastFood) {
this.fastFood = fastFood;
}
public Garnish(float price, String desc, FastFood fastFood) {
super(price, desc);
this.fastFood = fastFood;
}
}
/**
* 鸡蛋类(具体的装饰者角色)
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public class Egg extends Garnish {
public Egg(FastFood fastFood) {
super(1, "鸡蛋", fastFood);
}
@Override
public float cast() {
return getPrice() + getFastFood().cast();
}
@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}
/**
* 培根类(具体装饰者类)
*
* @see [相关类/方法]
* @since 2024/9/25
*/
public class Bacon extends Garnish {
public Bacon(FastFood fastFood) {
super(1, "培根", fastFood);
}
@Override
public float cast() {
return getPrice() + getFastFood().cast();
}
@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}
public class Client {
public static void main(String[] args) {
// 点一份炒饭
FastFood friedRice = new FriedRice();
System.out.println(friedRice.getDesc() + ":" + friedRice.cast());
System.out.println("===============================");
// 在上面的炒饭中加一个鸡蛋
friedRice = new Egg(friedRice);
System.out.println(friedRice.getDesc() + ":" + friedRice.cast());
System.out.println("============================");
// 再加一个鸡蛋
friedRice = new Egg(friedRice);
System.out.println(friedRice.getDesc() + ":" + friedRice.cast());
// 加一个培根
friedRice = new Bacon(friedRice);
System.out.println(friedRice.getDesc() + ":" + friedRice.cast());
}
}
4.优点
装饰着模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰着模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附件责任,装饰着则是动态的附件责任。
装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
5.使用场景
1.当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
不能采用继承的情况:1.系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。2.类定义不能继承(如final类)
2.在不影响其它对象的情况下,以动态,透明的方式给单个对象添加职责。
3.当对象的功能要求可以动态地添加,也可以在动态的撤销时。
6.代理模式和装饰者的区别
静态代理和装饰者模式的区别:
相同点:
都要实现与目标类相同的业务接口
在两个类中都要声明目标对象
都可以在不修改目标类的前提下增强目标方法
不同点:
目的不同:装饰者是为了增强目标对象,静态代理是为了保护和隐藏目标对象
获取目标对象构建的地方不同:装饰者是由外界传递进来,可以通过构造方法传递;静态代理是在代理类内部创建,以此来隐藏目标对象。
4.桥接模式
1.概述
将抽象与实现分离,是它们可以独立变化。用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
2.结构
抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
扩展抽象化(Refind Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合或聚合关系调用实现化角色中的业务方法。
实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。
3.实现
案例:需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows,Mac,Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB,AVI,WMV等。该播放器包含了两个维度,适合使用桥接模式。
/**
* 视频文件(实现化角色)
*
* @see [相关类/方法]
* @since 2024/9/26
*/
public interface VideoFile {
// 解码功能
void decode(String fileName);
}
/**
* Avi视频文件(具体实现化角色)
*
* @see [相关类/方法]
* @since 2024/9/26
*/
public class AviFile implements VideoFile{
@Override
public void decode(String fileName) {
System.out.println("avi视频文件: " + fileName);
}
}
/**
* rmvb视频文件(具体的实现化角色)
*
* @see [相关类/方法]
* @since 2024/9/26
*/
public class RmvbFile implements VideoFile {
@Override
public void decode(String fileName) {
System.out.println("rmvb视频文件: " + fileName);
}
}
/**
* 抽象的操作系统(抽象化角色)
*
* @see [相关类/方法]
* @since 2024/9/26
*/
public abstract class OperatingSystem {
// 声明videoFile变量
protected VideoFile videoFile;
public OperatingSystem(VideoFile videoFile) {
this.videoFile = videoFile;
}
public abstract void play(String fileName);
}
/**
* windows操作系统(扩展抽象化角色)
*
* @see [相关类/方法]
* @since 2024/9/26
*/
public class Windows extends OperatingSystem {
public Windows(VideoFile videoFile) {
super(videoFile);
}
@Override
public void play(String fileName) {
videoFile.decode(fileName);
}
}
/**
* Mac操作系统(扩展抽象化角色)
*
* @see [相关类/方法]
* @since 2024/9/26
*/
public class Mac extends OperatingSystem {
public Mac(VideoFile videoFile) {
super(videoFile);
}
@Override
public void play(String fileName) {
videoFile.decode(fileName);
}
}
4.优点
桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统